1) Structure
This view illustrates component structure.
The JavaScript syntax:
(() => {
/* code here */
})();
defines a function with no parameters. The outer () turns the anonymous
function definition into an expression. The trialing () causes the code
to be executed immediately.
The block of code starting at line 42 defines the Horizontal Spacer component.
The block of code starting at line 95 defines the Vertical Spacer component.
Code starting at line 141 defines the tag names associated with these components.
2) HSpace Definition
Horizontal component definition.
The horizontal spacer definition begins at line 44. That defines a
JavaScript class derived from HTMLElement.
Line 45 defines public attributes used to modify a specific instance's properties.
Lines 47-90 define component styles and structure: an element with no child elements
in a constructor that runs as soon as the code is parsed.
:host represents the root of the component element. slot defines a place to
insert content, e.g., size of the space.
Lines 71-85 define event handling for component.
Lines 87-90 handle size and thickness attributes.
2) VSpace Definition
Vertical component definition.
Vertical spacer definition begins at line 97. That defines a
JavaScript class derived from HTMLElement.
Line 98 defines public attributes used to modify a specific instance's properties.
Lines 100-120 define component styles and structure: an element with no child elements
in a constructor that runs as soon as the code is parsed.
:host represents the root of the component element. slot defines a place to
insert content, e.g., size of the space.
Lines 125-133 define event handling for component.
Lines 136 and 136 handle size attribute.
4) Helpers and Attributes
Lines 8-16 convert unitless specifier [v] to [v]rem;
Lines 18-31 evaluate the length which may be presented as:
- text node content
- attribute specified in opening tag
- style defined by host
and return as css value.
Lines 33-40 evaluate the vertical thickness which may be presented as:
- attribute specified in opening tag
- propertye value set by JavaScript
and return as css value.
Lines 142-145 define tag names associated with this component.
1 /* SpacerComponent.js
2 Horizontal and Vertical Spacer Components
3 - content may be provided in any of the conventional space metrics.
4 - space may be either declared as element content or as attribute.
5 */
6 (() => {
/* code elided */
42 function defineHSpace(tagName) {
43 if (customElements.get(tagName)) return;
44 class HSpace extends HTMLElement {
/* code elided */
91 }
92 customElements.define(tagName, HSpace);
93 }
94
95 function defineVSpace(tagName) {
96 if (customElements.get(tagName)) return;
97 class VSpace extends HTMLElement {
/* code elided
137 }
138 customElements.define(tagName, VSpace);
139 }
140
141 // Define primary tags and short aliases
142 defineHSpace('h-space');
143 defineHSpace('h-s');
144 defineVSpace('v-space');
145 defineVSpace('v-s');
146 })();
/* code elided */
41
42 function defineHSpace(tagName) {
43 if (customElements.get(tagName)) return;
44 class HSpace extends HTMLElement {
45 static get observedAttributes() { return ['size', 'block', 'thickness']; }
46 #mo;
47 constructor() {
48 super();
49 this.attachShadow({ mode: 'open' });
50 // Host paints the gap; optional thickness shows background/borders if desired.
51 this.shadowRoot.innerHTML = `
52 <style>
53 :host {
54 display: inline-block;
55 width: var(--_h-size, 1rem);
56 height: var(--_h-thickness, 0); /* default 0 to avoid layout shift */
57 line-height: 0;
58 color: inherit;
59 background-color: inherit;
60 font: inherit;
61 }
62 /* Hide any light-DOM children assigned to the default slot */
63 slot { display: none !important; }
64 </style>
65 <slot></slot>
66 `;
67 this.setAttribute('aria-hidden', 'true');
68 this.setAttribute('role', 'presentation');
69 this.#mo = new MutationObserver(() => this.#update());
70 }
71 connectedCallback() {
72 this.#mo.observe(this, { characterData: true, childList: true, subtree: true });
73 this.#update();
74 }
75 disconnectedCallback() { this.#mo.disconnect(); }
76 attributeChangedCallback() { this.#update(); }
77 #update() {
78 // Display mode toggle
79 this.style.display = this.hasAttribute('block') ? 'block' : 'inline-block';
80 // Size + thickness
81 const size = pickSize(this, 'h');
82 const thick = pickThickness(this);
83 this.shadowRoot.host.style.setProperty('--_h-size', size);
84 this.shadowRoot.host.style.setProperty('--_h-thickness', thick);
85 }
86 // JS property sugar
87 get size() { return this.getAttribute('size'); }
88 set size(v) { if (v == null) this.removeAttribute('size'); else this.setAttribute('size', v); }
89 get thickness() { return this.getAttribute('thickness'); }
90 set thickness(v) { if (v == null) this.removeAttribute('thickness'); else this.setAttribute('thickness', v); }
91 }
92 customElements.define(tagName, HSpace);
93 }
94 /* code elided */
146 })();
/* code elided */
94
95 function defineVSpace(tagName) {
96 if (customElements.get(tagName)) return;
97 class VSpace extends HTMLElement {
98 static get observedAttributes() { return ['size', 'inline']; }
99 #mo;
100 constructor() {
101 super();
102 this.attachShadow({ mode: 'open' });
103 // Host carries the height directly.
104 this.shadowRoot.innerHTML = `
105 <style>
106 :host {
107 display: block;
108 height: var(--_v-size, 1rem);
109 color: inherit;
110 background-color: inherit;
111 font: inherit;
112 }
113 slot { display: none !important; }
114 </style>
115 <slot></slot>
116 `;
117 this.setAttribute('aria-hidden', 'true');
118 this.setAttribute('role', 'presentation');
119 this.#mo = new MutationObserver(() => this.#update());
120 }
121 connectedCallback() {
122 this.#mo.observe(this, { characterData: true, childList: true, subtree: true });
123 this.#update();
124 }
125 disconnectedCallback() { this.#mo.disconnect(); }
126 attributeChangedCallback() { this.#update(); }
127 #update() {
128 // Display mode toggle (inline vertical spacer)
129 this.style.display = this.hasAttribute('inline') ? 'inline-block' : 'block';
130 // Size
131 const size = pickSize(this, 'v');
132 this.shadowRoot.host.style.setProperty('--_v-size', size);
133 }
134 // JS property sugar
135 get size() { return this.getAttribute('size'); }
136 set size(v) { if (v == null) this.removeAttribute('size'); else this.setAttribute('size', v); }
137 }
138 customElements.define(tagName, VSpace);
139 }
140
/* code elided */
/* code elided */
8 const numberRE = new RegExp('^\s*\d+(?:\.\d+)?\s*$'); // 12 or 12.5 (no unit)
9
10 function asCssLength(val, fallback = '1rem') {
11 if (val == null) return fallback;
12 const s = String(val).trim();
13 if (s === '') return fallback;
14 if (numberRE.test(s)) return `${s}rem`; // unitless -> rems
15 return s; // assume valid CSS length or var()/calc()
16 }
17
18 function pickSize(el, kind /* 'h'|'v' */) {
19 // 1) attribute
20 const attr = el.getAttribute('size');
21 if (attr && attr.trim() !== '') return asCssLength(attr);
22 // 2) inner text (do NOT clear; hidden via shadow <slot>)
23 const inline = (el.textContent || '').trim();
24 if (inline) return asCssLength(inline);
25 // 3) CSS var on the host
26 const varName = (kind === 'h') ? '--h-space-size' : '--v-space-size';
27 const cssVar = el.style.getPropertyValue(varName) || getComputedStyle(el).getPropertyValue(varName);
28 if (cssVar && cssVar.trim() !== '') return asCssLength(cssVar);
29 // 4) default
30 return '1rem';
31 }
32
33 function pickThickness(el) {
34 // attribute > CSS var > default 0
35 const attr = el.getAttribute('thickness');
36 if (attr && attr.trim() !== '') return asCssLength(attr, '0');
37 const cssVar = el.style.getPropertyValue('--h-thickness') || getComputedStyle(el).getPropertyValue('--h-thickness');
38 if (cssVar && cssVar.trim() !== '') return asCssLength(cssVar, '0');
39 return '0';
40 }
41
/* code elided /
140
141 // Define primary tags and short aliases
142 defineHSpace('h-space');
143 defineHSpace('h-s');
144 defineVSpace('v-space');
145 defineVSpace('v-s');
146 })();