@ktbsh/ui 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/README.md +165 -0
  2. package/dist/custom-elements.json +1427 -0
  3. package/dist/index.d.ts +13 -0
  4. package/dist/index.js +12 -0
  5. package/dist/react/alert.d.ts +20 -0
  6. package/dist/react/alert.js +30 -0
  7. package/dist/react/badge.d.ts +17 -0
  8. package/dist/react/badge.js +30 -0
  9. package/dist/react/box.d.ts +22 -0
  10. package/dist/react/box.js +30 -0
  11. package/dist/react/breadcrumb.d.ts +17 -0
  12. package/dist/react/breadcrumb.js +30 -0
  13. package/dist/react/button.d.ts +20 -0
  14. package/dist/react/button.js +30 -0
  15. package/dist/react/checkbox.d.ts +22 -0
  16. package/dist/react/checkbox.js +30 -0
  17. package/dist/react/container.d.ts +18 -0
  18. package/dist/react/container.js +30 -0
  19. package/dist/react/field.d.ts +20 -0
  20. package/dist/react/field.js +30 -0
  21. package/dist/react/heading.d.ts +19 -0
  22. package/dist/react/heading.js +30 -0
  23. package/dist/react/input.d.ts +25 -0
  24. package/dist/react/input.js +30 -0
  25. package/dist/react/label.d.ts +18 -0
  26. package/dist/react/label.js +30 -0
  27. package/dist/react/link.d.ts +21 -0
  28. package/dist/react/link.js +30 -0
  29. package/dist/react/modal.d.ts +18 -0
  30. package/dist/react/modal.js +30 -0
  31. package/dist/react/nav.d.ts +17 -0
  32. package/dist/react/nav.js +30 -0
  33. package/dist/react/pagination.d.ts +19 -0
  34. package/dist/react/pagination.js +30 -0
  35. package/dist/react/progress.d.ts +19 -0
  36. package/dist/react/progress.js +30 -0
  37. package/dist/react/radio.d.ts +22 -0
  38. package/dist/react/radio.js +30 -0
  39. package/dist/react/select.d.ts +22 -0
  40. package/dist/react/select.js +30 -0
  41. package/dist/react/skeleton.d.ts +19 -0
  42. package/dist/react/skeleton.js +30 -0
  43. package/dist/react/spinner.d.ts +18 -0
  44. package/dist/react/spinner.js +30 -0
  45. package/dist/react/stack.d.ts +21 -0
  46. package/dist/react/stack.js +30 -0
  47. package/dist/react/switch.d.ts +21 -0
  48. package/dist/react/switch.js +30 -0
  49. package/dist/react/tab-panel.d.ts +18 -0
  50. package/dist/react/tab-panel.js +30 -0
  51. package/dist/react/tab.d.ts +19 -0
  52. package/dist/react/tab.js +30 -0
  53. package/dist/react/tabs.d.ts +17 -0
  54. package/dist/react/tabs.js +30 -0
  55. package/dist/react/text.d.ts +19 -0
  56. package/dist/react/text.js +30 -0
  57. package/dist/react/textarea.d.ts +24 -0
  58. package/dist/react/textarea.js +30 -0
  59. package/dist/react/toast.d.ts +19 -0
  60. package/dist/react/toast.js +30 -0
  61. package/dist/themes/dark.css +52 -0
  62. package/dist/themes/light.css +53 -0
  63. package/dist/tokens/index.d.ts +7 -0
  64. package/dist/tokens/index.js +7 -0
  65. package/dist/tokens/semantic.d.ts +209 -0
  66. package/dist/tokens/semantic.js +102 -0
  67. package/dist/types/index.d.ts +15 -0
  68. package/dist/types/index.js +2 -0
  69. package/dist/vanilla/alert.d.ts +2 -0
  70. package/dist/vanilla/alert.js +134 -0
  71. package/dist/vanilla/alert.src.js +257 -0
  72. package/dist/vanilla/badge.d.ts +2 -0
  73. package/dist/vanilla/badge.js +89 -0
  74. package/dist/vanilla/badge.src.js +212 -0
  75. package/dist/vanilla/box.d.ts +2 -0
  76. package/dist/vanilla/box.js +100 -0
  77. package/dist/vanilla/box.src.js +270 -0
  78. package/dist/vanilla/breadcrumb.d.ts +2 -0
  79. package/dist/vanilla/breadcrumb.js +93 -0
  80. package/dist/vanilla/breadcrumb.src.js +255 -0
  81. package/dist/vanilla/button.d.ts +2 -0
  82. package/dist/vanilla/button.js +135 -0
  83. package/dist/vanilla/button.src.js +260 -0
  84. package/dist/vanilla/checkbox.d.ts +2 -0
  85. package/dist/vanilla/checkbox.js +96 -0
  86. package/dist/vanilla/checkbox.src.js +296 -0
  87. package/dist/vanilla/container.d.ts +2 -0
  88. package/dist/vanilla/container.js +79 -0
  89. package/dist/vanilla/container.src.js +222 -0
  90. package/dist/vanilla/field.d.ts +2 -0
  91. package/dist/vanilla/field.js +113 -0
  92. package/dist/vanilla/field.src.js +278 -0
  93. package/dist/vanilla/heading.d.ts +2 -0
  94. package/dist/vanilla/heading.js +75 -0
  95. package/dist/vanilla/heading.src.js +241 -0
  96. package/dist/vanilla/input.d.ts +2 -0
  97. package/dist/vanilla/input.js +121 -0
  98. package/dist/vanilla/input.src.js +326 -0
  99. package/dist/vanilla/label.d.ts +2 -0
  100. package/dist/vanilla/label.js +75 -0
  101. package/dist/vanilla/label.src.js +232 -0
  102. package/dist/vanilla/link.d.ts +2 -0
  103. package/dist/vanilla/link.js +96 -0
  104. package/dist/vanilla/link.src.js +272 -0
  105. package/dist/vanilla/modal.d.ts +2 -0
  106. package/dist/vanilla/modal.js +125 -0
  107. package/dist/vanilla/modal.src.js +251 -0
  108. package/dist/vanilla/nav.d.ts +2 -0
  109. package/dist/vanilla/nav.js +94 -0
  110. package/dist/vanilla/nav.src.js +214 -0
  111. package/dist/vanilla/pagination.d.ts +2 -0
  112. package/dist/vanilla/pagination.js +114 -0
  113. package/dist/vanilla/pagination.src.js +262 -0
  114. package/dist/vanilla/progress.d.ts +2 -0
  115. package/dist/vanilla/progress.js +108 -0
  116. package/dist/vanilla/progress.src.js +244 -0
  117. package/dist/vanilla/radio.d.ts +2 -0
  118. package/dist/vanilla/radio.js +104 -0
  119. package/dist/vanilla/radio.src.js +326 -0
  120. package/dist/vanilla/select.d.ts +2 -0
  121. package/dist/vanilla/select.js +104 -0
  122. package/dist/vanilla/select.src.js +332 -0
  123. package/dist/vanilla/skeleton.d.ts +2 -0
  124. package/dist/vanilla/skeleton.js +99 -0
  125. package/dist/vanilla/skeleton.src.js +240 -0
  126. package/dist/vanilla/spinner.d.ts +2 -0
  127. package/dist/vanilla/spinner.js +108 -0
  128. package/dist/vanilla/spinner.src.js +222 -0
  129. package/dist/vanilla/stack.d.ts +2 -0
  130. package/dist/vanilla/stack.js +83 -0
  131. package/dist/vanilla/stack.src.js +259 -0
  132. package/dist/vanilla/switch.d.ts +2 -0
  133. package/dist/vanilla/switch.js +166 -0
  134. package/dist/vanilla/switch.src.js +299 -0
  135. package/dist/vanilla/tab-panel.d.ts +2 -0
  136. package/dist/vanilla/tab-panel.js +80 -0
  137. package/dist/vanilla/tab-panel.src.js +227 -0
  138. package/dist/vanilla/tab.d.ts +2 -0
  139. package/dist/vanilla/tab.js +99 -0
  140. package/dist/vanilla/tab.src.js +253 -0
  141. package/dist/vanilla/tabs.d.ts +2 -0
  142. package/dist/vanilla/tabs.js +66 -0
  143. package/dist/vanilla/tabs.src.js +237 -0
  144. package/dist/vanilla/text.d.ts +2 -0
  145. package/dist/vanilla/text.js +74 -0
  146. package/dist/vanilla/text.src.js +232 -0
  147. package/dist/vanilla/textarea.d.ts +2 -0
  148. package/dist/vanilla/textarea.js +97 -0
  149. package/dist/vanilla/textarea.src.js +316 -0
  150. package/dist/vanilla/toast.d.ts +2 -0
  151. package/dist/vanilla/toast.js +120 -0
  152. package/dist/vanilla/toast.src.js +245 -0
  153. package/package.json +111 -0
@@ -0,0 +1,278 @@
1
+
2
+ import { render, html } from 'uhtml';
3
+
4
+ const sheet = new CSSStyleSheet();
5
+ sheet.replaceSync(":host {\n--kb-color-bg-canvas: #ffffff;\n--kb-color-bg-surface: #f6f8fa;\n--kb-color-bg-subtle: #eef1f4;\n--kb-color-bg-inverse: #1f2328;\n--kb-color-fg-default: #1f2328;\n--kb-color-fg-muted: #656d76;\n--kb-color-fg-subtle: #8c959f;\n--kb-color-fg-on-accent: #ffffff;\n--kb-color-fg-on-inverse: #ffffff;\n--kb-color-border-default: #d0d7de;\n--kb-color-border-muted: #e6ebf0;\n--kb-color-border-focus: #0969da;\n--kb-color-accent-default: #0969da;\n--kb-color-accent-hover: #0860ca;\n--kb-color-accent-subtle: #ddf4ff;\n--kb-color-danger-default: #cf222e;\n--kb-color-danger-subtle: #ffebe9;\n--kb-color-success-default: #1a7f37;\n--kb-color-success-subtle: #dafbe1;\n--kb-color-warning-default: #9a6700;\n--kb-color-warning-subtle: #fff8c5;\n--kb-space-2xs: 2px;\n--kb-space-xs: 4px;\n--kb-space-sm: 8px;\n--kb-space-md: 16px;\n--kb-space-lg: 24px;\n--kb-space-xl: 32px;\n--kb-space-2xl: 48px;\n--kb-radius-sm: 4px;\n--kb-radius-md: 8px;\n--kb-radius-lg: 12px;\n--kb-radius-full: 9999px;\n--kb-font-family-sans: ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;\n--kb-font-size-sm: 0.875rem;\n--kb-font-size-md: 1rem;\n--kb-font-size-lg: 1.125rem;\n--kb-font-size-xl: 1.25rem;\n--kb-font-weight-regular: 400;\n--kb-font-weight-medium: 500;\n--kb-font-weight-semibold: 600;\n--kb-line-height-tight: 1.25;\n--kb-line-height-normal: 1.5;\n--kb-shadow-sm: 0 1px 2px rgba(31, 35, 40, 0.08);\n--kb-shadow-md: 0 4px 12px rgba(31, 35, 40, 0.1);\n--kb-focus-ring: 0 0 0 3px rgba(9, 105, 218, 0.35);\n}\n\n\n :host {\n display: block;\n font-family: var(--kb-font-family-sans);\n color: var(--kb-color-fg-default);\n }\n .root {\n display: flex;\n flex-direction: column;\n gap: var(--kb-space-xs);\n width: 100%;\n box-sizing: border-box;\n }\n .label {\n font-size: var(--kb-font-size-sm);\n font-weight: var(--kb-font-weight-medium);\n line-height: var(--kb-line-height-normal);\n }\n .req {\n color: var(--kb-color-danger-default);\n font-weight: var(--kb-font-weight-semibold);\n margin-left: var(--kb-space-2xs);\n }\n .control {\n width: 100%;\n }\n .hint {\n margin: 0;\n font-size: var(--kb-font-size-sm);\n color: var(--kb-color-fg-muted);\n line-height: var(--kb-line-height-normal);\n }\n .error {\n margin: 0;\n font-size: var(--kb-font-size-sm);\n color: var(--kb-color-danger-default);\n line-height: var(--kb-line-height-normal);\n font-weight: var(--kb-font-weight-medium);\n }\n .root[data-invalid] .label {\n color: var(--kb-color-danger-default);\n }\n .label {\n cursor: default;\n }\n ");
6
+
7
+ export class KitbashField extends HTMLElement {
8
+
9
+ static get observedAttributes() {
10
+ return ["label","hint","error","required"];
11
+ }
12
+
13
+ static get propTypes() {
14
+ return {"label":"String","hint":"String","error":"String","required":"Boolean"};
15
+ }
16
+
17
+
18
+ get label() {
19
+ return this._props['label'];
20
+ }
21
+ set label(val) {
22
+ // External / framework property writes: re-render, do not emit kitbash-change
23
+ // (caller already knows the value they set).
24
+ this._assignProp('label', val);
25
+ this.update();
26
+ }
27
+
28
+ get hint() {
29
+ return this._props['hint'];
30
+ }
31
+ set hint(val) {
32
+ // External / framework property writes: re-render, do not emit kitbash-change
33
+ // (caller already knows the value they set).
34
+ this._assignProp('hint', val);
35
+ this.update();
36
+ }
37
+
38
+ get error() {
39
+ return this._props['error'];
40
+ }
41
+ set error(val) {
42
+ // External / framework property writes: re-render, do not emit kitbash-change
43
+ // (caller already knows the value they set).
44
+ this._assignProp('error', val);
45
+ this.update();
46
+ }
47
+
48
+ get required() {
49
+ return this._props['required'];
50
+ }
51
+ set required(val) {
52
+ // External / framework property writes: re-render, do not emit kitbash-change
53
+ // (caller already knows the value they set).
54
+ this._assignProp('required', val);
55
+ this.update();
56
+ }
57
+
58
+
59
+ constructor() {
60
+ super();
61
+ this.attachShadow({ mode: 'open', delegatesFocus: false });
62
+
63
+ this.shadowRoot.adoptedStyleSheets = [sheet];
64
+
65
+ this._defaults = {"label":"","hint":"","error":"","required":false};
66
+
67
+ this._state = {};
68
+ this._props = { ...this._defaults };
69
+ this._eventHandlers = { 'click .label': function(e) {
70
+ const slot = e.currentTarget.getRootNode().querySelector("slot");
71
+ if (!slot)
72
+ return;
73
+ const assigned = slot.assignedElements({
74
+ flatten: !0
75
+ }), selector = "input, select, textarea, button, [tabindex], kitbash-input, kitbash-select, kitbash-textarea, kitbash-checkbox, kitbash-radio, kitbash-switch";
76
+ let target;
77
+ for (const el of assigned) {
78
+ if (el.matches?.(selector)) {
79
+ target = el;
80
+ break;
81
+ }
82
+ const nested = el.querySelector?.(selector);
83
+ if (nested) {
84
+ target = nested;
85
+ break;
86
+ }
87
+ }
88
+ if (target && typeof target.focus === "function")
89
+ target.focus();
90
+ } };
91
+ this._eventCleanup = [];
92
+ this._renderFn = function({ props, html }) {
93
+ const label = typeof props.label === "string" && props.label.length > 0 ? props.label : "", hint = typeof props.hint === "string" && props.hint.length > 0 ? props.hint : "", error = typeof props.error === "string" && props.error.length > 0 ? props.error : "", invalid = Boolean(error);
94
+ return html`
95
+ <div
96
+ part="field-root"
97
+ class="root"
98
+ data-invalid=${invalid ? "true" : null}
99
+ >
100
+ ${label ? html`
101
+ <div class="label" part="field-label">
102
+ ${label}${props.required ? html`<span class="req" aria-hidden="true">*</span>` : null}
103
+ </div>
104
+ ` : null}
105
+ <div class="control" part="field-control">
106
+ <slot></slot>
107
+ </div>
108
+ ${hint && !error ? html`<p class="hint" part="field-hint">${hint}</p>` : null}
109
+ ${error ? html`
110
+ <p class="error" part="field-error" role="alert">${error}</p>
111
+ ` : null}
112
+ </div>
113
+ `;
114
+ };
115
+ this._reflecting = false;
116
+ }
117
+
118
+ connectedCallback() {
119
+ // Initial attribute values are parsed by attributeChangedCallback before connectedCallback fires
120
+ this.update();
121
+
122
+ }
123
+
124
+ disconnectedCallback() {
125
+ this.cleanupEvents();
126
+ }
127
+
128
+ attributeChangedCallback(name, oldValue, newValue) {
129
+ // Skip when we caused the attribute write ourselves (avoids double update)
130
+ if (this._reflecting) return;
131
+ if (oldValue === newValue) return;
132
+
133
+ const type = this.constructor.propTypes[name];
134
+
135
+ let parsedValue = newValue;
136
+ if (newValue === null || newValue === undefined) {
137
+ parsedValue = this._defaults[name];
138
+ } else if (type === 'Number' && newValue !== '') {
139
+ // Match property coercion: empty string is not Number('') → 0
140
+ parsedValue = Number(newValue);
141
+ } else if (type === 'Boolean') {
142
+ parsedValue = newValue !== null && newValue !== 'false';
143
+ }
144
+
145
+ this._props[name] = parsedValue;
146
+
147
+ this.update();
148
+ }
149
+
150
+ /** Coerce + reflect a single prop; no re-render (caller decides). */
151
+ _assignProp(key, val) {
152
+ const type = this.constructor.propTypes[key];
153
+ let next = val;
154
+ if (type === 'Boolean') {
155
+ next = val === true || val === '';
156
+ } else if (type === 'Number' && val !== null && val !== undefined && val !== '') {
157
+ next = Number(val);
158
+ }
159
+ this._props[key] = next;
160
+
161
+ // Only reflect primitives — objects/arrays stay as properties (no [object Object] attrs)
162
+ const isPrimitive =
163
+ next === null ||
164
+ next === undefined ||
165
+ typeof next === 'string' ||
166
+ typeof next === 'number' ||
167
+ typeof next === 'boolean';
168
+
169
+ this._reflecting = true;
170
+ try {
171
+ if (type === 'Boolean') {
172
+ if (next) this.setAttribute(key, '');
173
+ else this.removeAttribute(key);
174
+ } else if (!isPrimitive) {
175
+ // property-only; drop stale attribute if any
176
+ this.removeAttribute(key);
177
+ } else if (next === null || next === undefined) {
178
+ this.removeAttribute(key);
179
+ } else {
180
+ this.setAttribute(key, String(next));
181
+ }
182
+ } finally {
183
+ this._reflecting = false;
184
+ }
185
+
186
+ }
187
+
188
+ _emitChange() {
189
+ // Shallow-copy both bags so listeners cannot mutate internal state/props via event.detail
190
+ this.dispatchEvent(new CustomEvent('kitbash-change', {
191
+ detail: {
192
+ state: { ...this._state },
193
+ props: { ...this._props },
194
+ },
195
+ bubbles: true,
196
+ composed: true
197
+ }));
198
+ }
199
+
200
+ /**
201
+ * Batch props and/or state: one uhtml update, one kitbash-change.
202
+ * Prefer this in event handlers (e.g. controlled inputs).
203
+ */
204
+ commit(patch) {
205
+ const p = patch || {};
206
+ if (p.props) {
207
+ const allowed = this.constructor.propTypes || {};
208
+ for (const key of Object.keys(p.props)) {
209
+ // Only declared props — ignore typos / stray keys
210
+ if (!Object.prototype.hasOwnProperty.call(allowed, key)) continue;
211
+ this._assignProp(key, p.props[key]);
212
+ }
213
+ }
214
+ if (p.state) {
215
+ this._state = { ...this._state, ...p.state };
216
+ }
217
+ this.update();
218
+ this._emitChange();
219
+ }
220
+
221
+ setState(newState) {
222
+ this.commit({ state: newState });
223
+ }
224
+
225
+ setProps(nextProps) {
226
+ this.commit({ props: nextProps });
227
+ }
228
+
229
+ _handlerCtx() {
230
+ // Snapshots so handlers/render cannot mutate internal bags by accident
231
+ return {
232
+ props: { ...this._props },
233
+ state: { ...this._state },
234
+ setState: this.setState.bind(this),
235
+ setProps: this.setProps.bind(this),
236
+ commit: this.commit.bind(this),
237
+ };
238
+ }
239
+
240
+ cleanupEvents() {
241
+ this._eventCleanup.forEach(cleanup => cleanup());
242
+ this._eventCleanup = [];
243
+ }
244
+
245
+ update() {
246
+ // Run the uhtml DOM diffing render cycle
247
+ render(this.shadowRoot, this._renderFn({
248
+ ...this._handlerCtx(),
249
+ html
250
+ }));
251
+ this.cleanupEvents();
252
+ this.bindEvents();
253
+
254
+ }
255
+
256
+
257
+
258
+ bindEvents() {
259
+ Object.keys(this._eventHandlers).forEach(eventSelector => {
260
+ const parts = eventSelector.split(' ');
261
+ const eventName = parts[0];
262
+ const selector = parts.slice(1).join(' ');
263
+
264
+ const targets = selector ? this.shadowRoot.querySelectorAll(selector) : [this];
265
+ targets.forEach(target => {
266
+ const handler = (e) => {
267
+ this._eventHandlers[eventSelector](e, this._handlerCtx());
268
+ };
269
+ target.addEventListener(eventName, handler);
270
+ this._eventCleanup.push(() => target.removeEventListener(eventName, handler));
271
+ });
272
+ });
273
+ }
274
+ }
275
+
276
+ if (!customElements.get('kitbash-field')) {
277
+ customElements.define('kitbash-field', KitbashField);
278
+ }
@@ -0,0 +1,2 @@
1
+ /** Side-effect import — registers the custom element. */
2
+ export {};
@@ -0,0 +1,75 @@
1
+ var p=(_,U,B,Y,I)=>{let Q=B.length,W=U.length,J=Q,X=0,z=0,Z=null;while(X<W||z<J)if(W===X){let $=J<Q?z?Y(B[z-1],-0).nextSibling:Y(B[J],0):I;while(z<J)_.insertBefore(Y(B[z++],1),$)}else if(J===z)while(X<W){if(!Z||!Z.has(U[X]))_.removeChild(Y(U[X],-1));X++}else if(U[X]===B[z])X++,z++;else if(U[W-1]===B[J-1])W--,J--;else if(U[X]===B[J-1]&&B[z]===U[W-1]){let $=Y(U[--W],-0).nextSibling;_.insertBefore(Y(B[z++],1),Y(U[X++],-0).nextSibling),_.insertBefore(Y(B[--J],1),$),U[W]=B[J]}else{if(!Z){Z=new Map;let $=z;while($<J)Z.set(B[$],$++)}if(Z.has(U[X])){let $=Z.get(U[X]);if(z<$&&$<J){let K=X,O=1;while(++K<W&&K<J&&Z.get(U[K])===$+O)O++;if(O>$-z){let D=Y(U[X],0);while(z<$)_.insertBefore(Y(B[z++],1),D)}else _.replaceChild(Y(B[z++],1),Y(U[X++],-1))}else X++}else _.removeChild(Y(U[X++],-1))}return B};var{isArray:E}=Array,{getPrototypeOf:R1,getOwnPropertyDescriptor:D1}=Object;var i="http://www.w3.org/2000/svg",q=[],N=()=>document.createRange(),M=(_,U,B)=>{return _.set(U,B),B},m=(_,U)=>{let B;do B=D1(_,U);while(!B&&(_=R1(_)));return B},o=(_,U)=>U.reduceRight(M1,_),M1=(_,U)=>_.childNodes[U];var s=1;var r=8;var l=11;var{setPrototypeOf:T1}=Object,n=(_)=>{function U(B){return T1(B,new.target.prototype)}return U.prototype=_.prototype,U};var T,V=(_,U,B)=>{if(!T)T=N();if(B)T.setStartAfter(_);else T.setStartBefore(_);return T.setEndAfter(U),T.deleteContents(),_};var x=({firstChild:_,lastChild:U},B)=>V(_,U,B),t=!1,j=(_,U)=>t&&_.nodeType===l?1/U<0?U?x(_,!0):_.lastChild:U?_.valueOf():_.firstChild:_,a=(_)=>document.createComment(_);class w extends n(DocumentFragment){#U=a("<>");#B=a("</>");#_=q;constructor(_){super(_);this.replaceChildren(...[this.#U,..._.childNodes,this.#B]),t=!0}get firstChild(){return this.#U}get lastChild(){return this.#B}get parentNode(){return this.#U.parentNode}remove(){x(this,!1)}replaceWith(_){x(this,!0).replaceWith(_)}valueOf(){let{parentNode:_}=this;if(_===this){if(this.#_===q)this.#_=[...this.childNodes]}else{if(_){let{firstChild:U,lastChild:B}=this;this.#_=[U];while(U!==B)this.#_.push(U=U.nextSibling)}this.replaceChildren(...this.#_)}return this}}var e=(_,U,B)=>_.setAttribute(U,B),F=(_,U)=>_.removeAttribute(U),P1=(_,U)=>{for(let B in U){let Y=U[B],I=B==="role"?B:`aria-${B}`;if(Y==null)F(_,I);else e(_,I,Y)}return U},C,F1=(_,U,B)=>{if(B=B.slice(1),!C)C=new WeakMap;let Y=C.get(_)||M(C,_,{}),I=Y[B];if(I&&I[0])_.removeEventListener(B,...I);if(I=E(U)?U:[U,!1],Y[B]=I,I[0])_.addEventListener(B,...I);return U};var G=(_,U)=>{let{t:B,n:Y}=_,I=!1;switch(typeof U){case"object":if(U!==null){(Y||B).replaceWith(_.n=U.valueOf());break}case"undefined":I=!0;default:if(B.data=I?"":U,Y)_.n=null,Y.replaceWith(B);break}return U},H1=(_,U)=>f(_,U,U==null?"class":"className"),A1=(_,U)=>{let{dataset:B}=_;for(let Y in U)if(U[Y]==null)delete B[Y];else B[Y]=U[Y];return U},k=(_,U,B)=>_[B]=U,E1=(_,U,B)=>k(_,U,B.slice(1)),f=(_,U,B)=>U==null?(F(_,B),U):k(_,U,B),_1=(_,U)=>(typeof U==="function"?U(_):U.current=_,U),y=(_,U,B)=>(U==null?F(_,B):e(_,B,U),U),N1=(_,U)=>U==null?f(_,U,"style"):k(_.style,U,"cssText"),V1=(_,U,B)=>(_.toggleAttribute(B.slice(1),U),U),R=(_,U,B)=>{let{length:Y}=U;if(_.data=`[${Y}]`,Y)return p(_.parentNode,B,U,j,_);switch(B.length){case 1:B[0].remove();case 0:break;default:V(j(B[0],0),j(B.at(-1),-0),!1);break}return q},U1=new Map([["aria",P1],["class",H1],["data",A1],["ref",_1],["style",N1]]),B1=(_,U,B)=>{switch(U[0]){case".":return E1;case"?":return V1;case"@":return F1;default:return B||"ownerSVGElement"in _?U==="ref"?_1:y:U1.get(U)||(U in _?U.startsWith("on")?k:m(_,U)?.set?f:y:y)}},Y1=(_,U)=>(_.textContent=U==null?"":U,U);var P=(_,U,B)=>({a:_,b:U,c:B}),I1=(_,U)=>({b:_,c:U}),J1=(_,U,B,Y)=>({v:q,u:_,t:U,n:B,c:Y}),L=()=>P(null,null,q);var b=(_)=>(U,B)=>{let{a:Y,b:I,c:Q}=_(U,B),W=document.importNode(Y,!0),J=q;if(I!==q){J=[];for(let X,z,Z=0;Z<I.length;Z++){let{a:$,b:K,c:O}=I[Z],D=$===z?X:X=o(W,z=$);J[Z]=J1(K,D,O,K===R?[]:K===G?L():null)}}return I1(Q?W.firstChild:new w(W),J)};var Q1=/^(?:plaintext|script|style|textarea|title|xmp)$/i,W1=/^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i;var j1=/<([a-zA-Z0-9]+[a-zA-Z0-9:._-]*)([^>]*?)(\/?)>/g,C1=/([^\s\\>"'=]+)\s*=\s*(['"]?)\x01/g,k1=/[\x01\x02]/g,X1=(_,U,B)=>{let Y=0;return _.join("\x01").trim().replace(j1,(I,Q,W,J)=>`<${Q}${W.replace(C1,"\x02=$2$1").trimEnd()}${J?B||W1.test(Q)?" /":`></${Q}`:""}>`).replace(k1,(I)=>I==="\x01"?`<!--${U+Y++}-->`:U+Y++)};var S=document.createElement("template"),g,h,$1=(_,U)=>{if(U){if(!g)g=document.createElementNS(i,"svg"),h=N(),h.selectNodeContents(g);return h.createContextualFragment(_)}S.innerHTML=_;let{content:B}=S;return S=S.cloneNode(!1),B};var c=(_)=>{let U=[],B;while(B=_.parentNode)U.push(U.indexOf.call(B.childNodes,_)),_=B;return U},Z1=()=>document.createTextNode(""),S1=(_,U,B)=>{let Y=$1(X1(_,H,B),B),{length:I}=_,Q=q;if(I>1){let X=[],z=document.createTreeWalker(Y,129),Z=0,$=`${H}${Z++}`;Q=[];while(Z<I){let K=z.nextNode();if(K.nodeType===r){if(K.data===$){let O=E(U[Z-1])?R:G;if(O===G)X.push(K);Q.push(P(c(K),O,null)),$=`${H}${Z++}`}}else{let O;while(K.hasAttribute($)){if(!O)O=c(K);let D=K.getAttribute($);Q.push(P(O,B1(K,D,B),D)),F(K,$),$=`${H}${Z++}`}if(!B&&Q1.test(K.localName)&&K.textContent.trim()===`<!--${$}-->`)Q.push(P(O||c(K),Y1,null)),$=`${H}${Z++}`}}for(Z=0;Z<X.length;Z++)X[Z].replaceWith(Z1())}let{childNodes:W}=Y,{length:J}=W;if(J<1)J=1,Y.appendChild(Z1());else if(J===1&&I!==1&&W[0].nodeType!==s)J=0;return M(z1,_,P(Y,Q,J===1))},z1=new WeakMap,H="isµ",v=(_)=>(U,B)=>z1.get(U)||S1(U,B,_);var x1=b(v(!1)),w1=b(v(!0)),u=(_,{s:U,t:B,v:Y})=>{if(_.a!==B){let{b:I,c:Q}=(U?w1:x1)(B,Y);_.a=B,_.b=I,_.c=Q}for(let{c:I}=_,Q=0;Q<I.length;Q++){let W=Y[Q],J=I[Q];switch(J.u){case R:J.v=R(J.t,y1(J.c,W),J.v);break;case G:let X=W instanceof A?u(J.c||(J.c=L()),W):(J.c=null,W);if(X!==J.v)J.v=G(J,X);break;default:if(W!==J.v)J.v=J.u(J.t,W,J.n,J.v);break}}return _.b},y1=(_,U)=>{let B=0,{length:Y}=U;if(Y<_.length)_.splice(Y);for(;B<Y;B++){let I=U[B];if(I instanceof A)U[B]=u(_[B]||(_[B]=L()),I);else _[B]=null}return U};class A{constructor(_,U,B){this.s=_,this.t=U,this.v=B}toDOM(_=L()){return u(_,this)}}var K1=new WeakMap,d=(_,U)=>{let B=K1.get(_)||M(K1,_,L()),{b:Y}=B;if(Y!==(typeof U==="function"?U():U).toDOM(B))_.replaceChildren(B.b.valueOf());return _};/*! (c) Andrea Giammarchi - MIT */var O1=(_)=>(U,...B)=>new A(_,U,B),q1=O1(!1),C_=O1(!0);var G1=new CSSStyleSheet;G1.replaceSync(`:host {
2
+ --kb-color-bg-canvas: #ffffff;
3
+ --kb-color-bg-surface: #f6f8fa;
4
+ --kb-color-bg-subtle: #eef1f4;
5
+ --kb-color-bg-inverse: #1f2328;
6
+ --kb-color-fg-default: #1f2328;
7
+ --kb-color-fg-muted: #656d76;
8
+ --kb-color-fg-subtle: #8c959f;
9
+ --kb-color-fg-on-accent: #ffffff;
10
+ --kb-color-fg-on-inverse: #ffffff;
11
+ --kb-color-border-default: #d0d7de;
12
+ --kb-color-border-muted: #e6ebf0;
13
+ --kb-color-border-focus: #0969da;
14
+ --kb-color-accent-default: #0969da;
15
+ --kb-color-accent-hover: #0860ca;
16
+ --kb-color-accent-subtle: #ddf4ff;
17
+ --kb-color-danger-default: #cf222e;
18
+ --kb-color-danger-subtle: #ffebe9;
19
+ --kb-color-success-default: #1a7f37;
20
+ --kb-color-success-subtle: #dafbe1;
21
+ --kb-color-warning-default: #9a6700;
22
+ --kb-color-warning-subtle: #fff8c5;
23
+ --kb-space-2xs: 2px;
24
+ --kb-space-xs: 4px;
25
+ --kb-space-sm: 8px;
26
+ --kb-space-md: 16px;
27
+ --kb-space-lg: 24px;
28
+ --kb-space-xl: 32px;
29
+ --kb-space-2xl: 48px;
30
+ --kb-radius-sm: 4px;
31
+ --kb-radius-md: 8px;
32
+ --kb-radius-lg: 12px;
33
+ --kb-radius-full: 9999px;
34
+ --kb-font-family-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
35
+ --kb-font-size-sm: 0.875rem;
36
+ --kb-font-size-md: 1rem;
37
+ --kb-font-size-lg: 1.125rem;
38
+ --kb-font-size-xl: 1.25rem;
39
+ --kb-font-weight-regular: 400;
40
+ --kb-font-weight-medium: 500;
41
+ --kb-font-weight-semibold: 600;
42
+ --kb-line-height-tight: 1.25;
43
+ --kb-line-height-normal: 1.5;
44
+ --kb-shadow-sm: 0 1px 2px rgba(31, 35, 40, 0.08);
45
+ --kb-shadow-md: 0 4px 12px rgba(31, 35, 40, 0.1);
46
+ --kb-focus-ring: 0 0 0 3px rgba(9, 105, 218, 0.35);
47
+ }
48
+
49
+
50
+ :host {
51
+ display: block;
52
+ font-family: var(--kb-font-family-sans);
53
+ }
54
+ h1, h2, h3, h4, h5, h6 {
55
+ margin: 0;
56
+ font-weight: var(--kb-font-weight-semibold);
57
+ line-height: var(--kb-line-height-tight);
58
+ color: var(--kb-color-fg-default);
59
+ }
60
+ .t-muted { color: var(--kb-color-fg-muted); }
61
+ .t-subtle { color: var(--kb-color-fg-subtle); }
62
+ .t-accent { color: var(--kb-color-accent-default); }
63
+ .t-danger { color: var(--kb-color-danger-default); }
64
+ .t-default { color: var(--kb-color-fg-default); }
65
+ .s-sm { font-size: var(--kb-font-size-sm); }
66
+ .s-md { font-size: var(--kb-font-size-md); }
67
+ .s-lg { font-size: var(--kb-font-size-lg); }
68
+ .s-xl { font-size: var(--kb-font-size-xl); }
69
+ .lvl-1 { font-size: calc(var(--kb-font-size-xl) * 1.5); }
70
+ .lvl-2 { font-size: calc(var(--kb-font-size-xl) * 1.25); }
71
+ .lvl-3 { font-size: var(--kb-font-size-xl); }
72
+ .lvl-4 { font-size: var(--kb-font-size-lg); }
73
+ .lvl-5 { font-size: var(--kb-font-size-md); }
74
+ .lvl-6 { font-size: var(--kb-font-size-sm); }
75
+ `);class L1 extends HTMLElement{static get observedAttributes(){return["level","size","tone"]}static get propTypes(){return{level:"String",size:"String",tone:"String"}}get level(){return this._props.level}set level(_){this._assignProp("level",_),this.update()}get size(){return this._props.size}set size(_){this._assignProp("size",_),this.update()}get tone(){return this._props.tone}set tone(_){this._assignProp("tone",_),this.update()}constructor(){super();this.attachShadow({mode:"open",delegatesFocus:!1}),this.shadowRoot.adoptedStyleSheets=[G1],this._defaults={level:"2",size:"",tone:"default"},this._state={},this._props={...this._defaults},this._eventHandlers={},this._eventCleanup=[],this._renderFn=function({props:_,html:U}){let B=_.level==="1"||_.level==="2"||_.level==="3"||_.level==="4"||_.level==="5"||_.level==="6"?_.level:"2",Y=_.tone==="muted"||_.tone==="subtle"||_.tone==="accent"||_.tone==="danger"?_.tone:"default",I=_.size==="sm"||_.size==="md"||_.size==="lg"||_.size==="xl"?_.size:"",Q=[`t-${Y}`,I?`s-${I}`:`lvl-${B}`].join(" ");if(B==="1")return U`<h1 part="heading-root" class=${Q}><slot></slot></h1>`;if(B==="3")return U`<h3 part="heading-root" class=${Q}><slot></slot></h3>`;if(B==="4")return U`<h4 part="heading-root" class=${Q}><slot></slot></h4>`;if(B==="5")return U`<h5 part="heading-root" class=${Q}><slot></slot></h5>`;if(B==="6")return U`<h6 part="heading-root" class=${Q}><slot></slot></h6>`;return U`<h2 part="heading-root" class=${Q}><slot></slot></h2>`},this._reflecting=!1}connectedCallback(){this.update()}disconnectedCallback(){this.cleanupEvents()}attributeChangedCallback(_,U,B){if(this._reflecting)return;if(U===B)return;let Y=this.constructor.propTypes[_],I=B;if(B===null||B===void 0)I=this._defaults[_];else if(Y==="Number"&&B!=="")I=Number(B);else if(Y==="Boolean")I=B!==null&&B!=="false";this._props[_]=I,this.update()}_assignProp(_,U){let B=this.constructor.propTypes[_],Y=U;if(B==="Boolean")Y=U===!0||U==="";else if(B==="Number"&&U!==null&&U!==void 0&&U!=="")Y=Number(U);this._props[_]=Y;let I=Y===null||Y===void 0||typeof Y==="string"||typeof Y==="number"||typeof Y==="boolean";this._reflecting=!0;try{if(B==="Boolean")if(Y)this.setAttribute(_,"");else this.removeAttribute(_);else if(!I)this.removeAttribute(_);else if(Y===null||Y===void 0)this.removeAttribute(_);else this.setAttribute(_,String(Y))}finally{this._reflecting=!1}}_emitChange(){this.dispatchEvent(new CustomEvent("kitbash-change",{detail:{state:{...this._state},props:{...this._props}},bubbles:!0,composed:!0}))}commit(_){let U=_||{};if(U.props){let B=this.constructor.propTypes||{};for(let Y of Object.keys(U.props)){if(!Object.prototype.hasOwnProperty.call(B,Y))continue;this._assignProp(Y,U.props[Y])}}if(U.state)this._state={...this._state,...U.state};this.update(),this._emitChange()}setState(_){this.commit({state:_})}setProps(_){this.commit({props:_})}_handlerCtx(){return{props:{...this._props},state:{...this._state},setState:this.setState.bind(this),setProps:this.setProps.bind(this),commit:this.commit.bind(this)}}cleanupEvents(){this._eventCleanup.forEach((_)=>_()),this._eventCleanup=[]}update(){d(this.shadowRoot,this._renderFn({...this._handlerCtx(),html:q1})),this.cleanupEvents(),this.bindEvents()}bindEvents(){Object.keys(this._eventHandlers).forEach((_)=>{let U=_.split(" "),B=U[0],Y=U.slice(1).join(" ");(Y?this.shadowRoot.querySelectorAll(Y):[this]).forEach((Q)=>{let W=(J)=>{this._eventHandlers[_](J,this._handlerCtx())};Q.addEventListener(B,W),this._eventCleanup.push(()=>Q.removeEventListener(B,W))})})}}if(!customElements.get("kitbash-heading"))customElements.define("kitbash-heading",L1);export{L1 as KitbashHeading};
@@ -0,0 +1,241 @@
1
+
2
+ import { render, html } from 'uhtml';
3
+
4
+ const sheet = new CSSStyleSheet();
5
+ sheet.replaceSync(":host {\n--kb-color-bg-canvas: #ffffff;\n--kb-color-bg-surface: #f6f8fa;\n--kb-color-bg-subtle: #eef1f4;\n--kb-color-bg-inverse: #1f2328;\n--kb-color-fg-default: #1f2328;\n--kb-color-fg-muted: #656d76;\n--kb-color-fg-subtle: #8c959f;\n--kb-color-fg-on-accent: #ffffff;\n--kb-color-fg-on-inverse: #ffffff;\n--kb-color-border-default: #d0d7de;\n--kb-color-border-muted: #e6ebf0;\n--kb-color-border-focus: #0969da;\n--kb-color-accent-default: #0969da;\n--kb-color-accent-hover: #0860ca;\n--kb-color-accent-subtle: #ddf4ff;\n--kb-color-danger-default: #cf222e;\n--kb-color-danger-subtle: #ffebe9;\n--kb-color-success-default: #1a7f37;\n--kb-color-success-subtle: #dafbe1;\n--kb-color-warning-default: #9a6700;\n--kb-color-warning-subtle: #fff8c5;\n--kb-space-2xs: 2px;\n--kb-space-xs: 4px;\n--kb-space-sm: 8px;\n--kb-space-md: 16px;\n--kb-space-lg: 24px;\n--kb-space-xl: 32px;\n--kb-space-2xl: 48px;\n--kb-radius-sm: 4px;\n--kb-radius-md: 8px;\n--kb-radius-lg: 12px;\n--kb-radius-full: 9999px;\n--kb-font-family-sans: ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;\n--kb-font-size-sm: 0.875rem;\n--kb-font-size-md: 1rem;\n--kb-font-size-lg: 1.125rem;\n--kb-font-size-xl: 1.25rem;\n--kb-font-weight-regular: 400;\n--kb-font-weight-medium: 500;\n--kb-font-weight-semibold: 600;\n--kb-line-height-tight: 1.25;\n--kb-line-height-normal: 1.5;\n--kb-shadow-sm: 0 1px 2px rgba(31, 35, 40, 0.08);\n--kb-shadow-md: 0 4px 12px rgba(31, 35, 40, 0.1);\n--kb-focus-ring: 0 0 0 3px rgba(9, 105, 218, 0.35);\n}\n\n\n :host {\n display: block;\n font-family: var(--kb-font-family-sans);\n }\n h1, h2, h3, h4, h5, h6 {\n margin: 0;\n font-weight: var(--kb-font-weight-semibold);\n line-height: var(--kb-line-height-tight);\n color: var(--kb-color-fg-default);\n }\n .t-muted { color: var(--kb-color-fg-muted); }\n .t-subtle { color: var(--kb-color-fg-subtle); }\n .t-accent { color: var(--kb-color-accent-default); }\n .t-danger { color: var(--kb-color-danger-default); }\n .t-default { color: var(--kb-color-fg-default); }\n .s-sm { font-size: var(--kb-font-size-sm); }\n .s-md { font-size: var(--kb-font-size-md); }\n .s-lg { font-size: var(--kb-font-size-lg); }\n .s-xl { font-size: var(--kb-font-size-xl); }\n .lvl-1 { font-size: calc(var(--kb-font-size-xl) * 1.5); }\n .lvl-2 { font-size: calc(var(--kb-font-size-xl) * 1.25); }\n .lvl-3 { font-size: var(--kb-font-size-xl); }\n .lvl-4 { font-size: var(--kb-font-size-lg); }\n .lvl-5 { font-size: var(--kb-font-size-md); }\n .lvl-6 { font-size: var(--kb-font-size-sm); }\n ");
6
+
7
+ export class KitbashHeading extends HTMLElement {
8
+
9
+ static get observedAttributes() {
10
+ return ["level","size","tone"];
11
+ }
12
+
13
+ static get propTypes() {
14
+ return {"level":"String","size":"String","tone":"String"};
15
+ }
16
+
17
+
18
+ get level() {
19
+ return this._props['level'];
20
+ }
21
+ set level(val) {
22
+ // External / framework property writes: re-render, do not emit kitbash-change
23
+ // (caller already knows the value they set).
24
+ this._assignProp('level', val);
25
+ this.update();
26
+ }
27
+
28
+ get size() {
29
+ return this._props['size'];
30
+ }
31
+ set size(val) {
32
+ // External / framework property writes: re-render, do not emit kitbash-change
33
+ // (caller already knows the value they set).
34
+ this._assignProp('size', val);
35
+ this.update();
36
+ }
37
+
38
+ get tone() {
39
+ return this._props['tone'];
40
+ }
41
+ set tone(val) {
42
+ // External / framework property writes: re-render, do not emit kitbash-change
43
+ // (caller already knows the value they set).
44
+ this._assignProp('tone', val);
45
+ this.update();
46
+ }
47
+
48
+
49
+ constructor() {
50
+ super();
51
+ this.attachShadow({ mode: 'open', delegatesFocus: false });
52
+
53
+ this.shadowRoot.adoptedStyleSheets = [sheet];
54
+
55
+ this._defaults = {"level":"2","size":"","tone":"default"};
56
+
57
+ this._state = {};
58
+ this._props = { ...this._defaults };
59
+ this._eventHandlers = {};
60
+ this._eventCleanup = [];
61
+ this._renderFn = function({ props, html }) {
62
+ const level = props.level === "1" || props.level === "2" || props.level === "3" || props.level === "4" || props.level === "5" || props.level === "6" ? props.level : "2", tone = props.tone === "muted" || props.tone === "subtle" || props.tone === "accent" || props.tone === "danger" ? props.tone : "default", sizeOverride = props.size === "sm" || props.size === "md" || props.size === "lg" || props.size === "xl" ? props.size : "", cls = [
63
+ `t-${tone}`,
64
+ sizeOverride ? `s-${sizeOverride}` : `lvl-${level}`
65
+ ].join(" ");
66
+ if (level === "1")
67
+ return html`<h1 part="heading-root" class=${cls}><slot></slot></h1>`;
68
+ if (level === "3")
69
+ return html`<h3 part="heading-root" class=${cls}><slot></slot></h3>`;
70
+ if (level === "4")
71
+ return html`<h4 part="heading-root" class=${cls}><slot></slot></h4>`;
72
+ if (level === "5")
73
+ return html`<h5 part="heading-root" class=${cls}><slot></slot></h5>`;
74
+ if (level === "6")
75
+ return html`<h6 part="heading-root" class=${cls}><slot></slot></h6>`;
76
+ return html`<h2 part="heading-root" class=${cls}><slot></slot></h2>`;
77
+ };
78
+ this._reflecting = false;
79
+ }
80
+
81
+ connectedCallback() {
82
+ // Initial attribute values are parsed by attributeChangedCallback before connectedCallback fires
83
+ this.update();
84
+
85
+ }
86
+
87
+ disconnectedCallback() {
88
+ this.cleanupEvents();
89
+ }
90
+
91
+ attributeChangedCallback(name, oldValue, newValue) {
92
+ // Skip when we caused the attribute write ourselves (avoids double update)
93
+ if (this._reflecting) return;
94
+ if (oldValue === newValue) return;
95
+
96
+ const type = this.constructor.propTypes[name];
97
+
98
+ let parsedValue = newValue;
99
+ if (newValue === null || newValue === undefined) {
100
+ parsedValue = this._defaults[name];
101
+ } else if (type === 'Number' && newValue !== '') {
102
+ // Match property coercion: empty string is not Number('') → 0
103
+ parsedValue = Number(newValue);
104
+ } else if (type === 'Boolean') {
105
+ parsedValue = newValue !== null && newValue !== 'false';
106
+ }
107
+
108
+ this._props[name] = parsedValue;
109
+
110
+ this.update();
111
+ }
112
+
113
+ /** Coerce + reflect a single prop; no re-render (caller decides). */
114
+ _assignProp(key, val) {
115
+ const type = this.constructor.propTypes[key];
116
+ let next = val;
117
+ if (type === 'Boolean') {
118
+ next = val === true || val === '';
119
+ } else if (type === 'Number' && val !== null && val !== undefined && val !== '') {
120
+ next = Number(val);
121
+ }
122
+ this._props[key] = next;
123
+
124
+ // Only reflect primitives — objects/arrays stay as properties (no [object Object] attrs)
125
+ const isPrimitive =
126
+ next === null ||
127
+ next === undefined ||
128
+ typeof next === 'string' ||
129
+ typeof next === 'number' ||
130
+ typeof next === 'boolean';
131
+
132
+ this._reflecting = true;
133
+ try {
134
+ if (type === 'Boolean') {
135
+ if (next) this.setAttribute(key, '');
136
+ else this.removeAttribute(key);
137
+ } else if (!isPrimitive) {
138
+ // property-only; drop stale attribute if any
139
+ this.removeAttribute(key);
140
+ } else if (next === null || next === undefined) {
141
+ this.removeAttribute(key);
142
+ } else {
143
+ this.setAttribute(key, String(next));
144
+ }
145
+ } finally {
146
+ this._reflecting = false;
147
+ }
148
+
149
+ }
150
+
151
+ _emitChange() {
152
+ // Shallow-copy both bags so listeners cannot mutate internal state/props via event.detail
153
+ this.dispatchEvent(new CustomEvent('kitbash-change', {
154
+ detail: {
155
+ state: { ...this._state },
156
+ props: { ...this._props },
157
+ },
158
+ bubbles: true,
159
+ composed: true
160
+ }));
161
+ }
162
+
163
+ /**
164
+ * Batch props and/or state: one uhtml update, one kitbash-change.
165
+ * Prefer this in event handlers (e.g. controlled inputs).
166
+ */
167
+ commit(patch) {
168
+ const p = patch || {};
169
+ if (p.props) {
170
+ const allowed = this.constructor.propTypes || {};
171
+ for (const key of Object.keys(p.props)) {
172
+ // Only declared props — ignore typos / stray keys
173
+ if (!Object.prototype.hasOwnProperty.call(allowed, key)) continue;
174
+ this._assignProp(key, p.props[key]);
175
+ }
176
+ }
177
+ if (p.state) {
178
+ this._state = { ...this._state, ...p.state };
179
+ }
180
+ this.update();
181
+ this._emitChange();
182
+ }
183
+
184
+ setState(newState) {
185
+ this.commit({ state: newState });
186
+ }
187
+
188
+ setProps(nextProps) {
189
+ this.commit({ props: nextProps });
190
+ }
191
+
192
+ _handlerCtx() {
193
+ // Snapshots so handlers/render cannot mutate internal bags by accident
194
+ return {
195
+ props: { ...this._props },
196
+ state: { ...this._state },
197
+ setState: this.setState.bind(this),
198
+ setProps: this.setProps.bind(this),
199
+ commit: this.commit.bind(this),
200
+ };
201
+ }
202
+
203
+ cleanupEvents() {
204
+ this._eventCleanup.forEach(cleanup => cleanup());
205
+ this._eventCleanup = [];
206
+ }
207
+
208
+ update() {
209
+ // Run the uhtml DOM diffing render cycle
210
+ render(this.shadowRoot, this._renderFn({
211
+ ...this._handlerCtx(),
212
+ html
213
+ }));
214
+ this.cleanupEvents();
215
+ this.bindEvents();
216
+
217
+ }
218
+
219
+
220
+
221
+ bindEvents() {
222
+ Object.keys(this._eventHandlers).forEach(eventSelector => {
223
+ const parts = eventSelector.split(' ');
224
+ const eventName = parts[0];
225
+ const selector = parts.slice(1).join(' ');
226
+
227
+ const targets = selector ? this.shadowRoot.querySelectorAll(selector) : [this];
228
+ targets.forEach(target => {
229
+ const handler = (e) => {
230
+ this._eventHandlers[eventSelector](e, this._handlerCtx());
231
+ };
232
+ target.addEventListener(eventName, handler);
233
+ this._eventCleanup.push(() => target.removeEventListener(eventName, handler));
234
+ });
235
+ });
236
+ }
237
+ }
238
+
239
+ if (!customElements.get('kitbash-heading')) {
240
+ customElements.define('kitbash-heading', KitbashHeading);
241
+ }
@@ -0,0 +1,2 @@
1
+ /** Side-effect import — registers the custom element. */
2
+ export {};