@ktbsh/ui 0.2.0 → 0.3.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 (36) hide show
  1. package/dist/custom-elements.json +155 -0
  2. package/dist/react/preset-toggle.d.ts +18 -0
  3. package/dist/react/preset-toggle.js +30 -0
  4. package/dist/react/scroll-top.d.ts +20 -0
  5. package/dist/react/scroll-top.js +30 -0
  6. package/dist/react/theme-toggle.d.ts +18 -0
  7. package/dist/react/theme-toggle.js +30 -0
  8. package/dist/react/toggle-group.d.ts +17 -0
  9. package/dist/react/toggle-group.js +30 -0
  10. package/dist/vanilla/checkbox.js +53 -9
  11. package/dist/vanilla/checkbox.src.js +1 -1
  12. package/dist/vanilla/input.js +6 -0
  13. package/dist/vanilla/input.src.js +1 -1
  14. package/dist/vanilla/preset-toggle.d.ts +2 -0
  15. package/dist/vanilla/preset-toggle.js +72 -0
  16. package/dist/vanilla/preset-toggle.src.js +271 -0
  17. package/dist/vanilla/radio.js +44 -10
  18. package/dist/vanilla/radio.src.js +1 -1
  19. package/dist/vanilla/scroll-top.d.ts +2 -0
  20. package/dist/vanilla/scroll-top.js +69 -0
  21. package/dist/vanilla/scroll-top.src.js +251 -0
  22. package/dist/vanilla/select.js +42 -3
  23. package/dist/vanilla/select.src.js +1 -1
  24. package/dist/vanilla/skeleton.js +8 -3
  25. package/dist/vanilla/skeleton.src.js +1 -1
  26. package/dist/vanilla/switch.js +57 -19
  27. package/dist/vanilla/switch.src.js +1 -1
  28. package/dist/vanilla/textarea.js +6 -0
  29. package/dist/vanilla/textarea.src.js +1 -1
  30. package/dist/vanilla/theme-toggle.d.ts +2 -0
  31. package/dist/vanilla/theme-toggle.js +72 -0
  32. package/dist/vanilla/theme-toggle.src.js +242 -0
  33. package/dist/vanilla/toggle-group.d.ts +2 -0
  34. package/dist/vanilla/toggle-group.js +19 -0
  35. package/dist/vanilla/toggle-group.src.js +215 -0
  36. package/package.json +1 -1
@@ -0,0 +1,271 @@
1
+
2
+ import { render, html } from 'uhtml';
3
+
4
+ const sheet = new CSSStyleSheet();
5
+ sheet.replaceSync("\n\n :host {\n display: inline-block;\n font-family: var(--kb-font-family-sans);\n }\n button {\n box-sizing: border-box;\n margin: 0;\n min-height: 2.25rem;\n padding: 0.45rem 0.8rem;\n border: 1px solid var(--kb-color-border-default);\n border-radius: var(--kb-radius-sm);\n background: var(--kb-color-bg-subtle);\n color: var(--kb-color-accent-default);\n font: inherit;\n font-size: var(--kb-font-size-sm);\n font-weight: var(--kb-font-weight-medium);\n line-height: 1;\n letter-spacing: 0.08em;\n text-transform: lowercase;\n cursor: pointer;\n transition:\n color 0.2s ease,\n background-color 0.2s ease,\n border-color 0.2s ease;\n }\n button:hover {\n color: var(--kb-color-fg-default);\n background: var(--kb-color-bg-surface);\n border-color: var(--kb-color-border-focus);\n }\n button:focus-visible {\n outline: 2px solid var(--kb-color-border-focus);\n outline-offset: 2px;\n color: var(--kb-color-fg-default);\n background: var(--kb-color-bg-surface);\n }\n :host([data-group-pos=\"start\"]) button {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n border-right-width: 0;\n }\n :host([data-group-pos=\"end\"]) button {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n }\n :host([data-group-pos=\"mid\"]) button {\n border-radius: 0;\n border-right-width: 0;\n }\n :host([data-group-pos]:hover),\n :host([data-group-pos]:focus-within) {\n position: relative;\n z-index: 1;\n }\n @media (prefers-reduced-motion: reduce) {\n button {\n transition: none;\n }\n }\n ");
6
+
7
+ export class KitbashPresetToggle extends HTMLElement {
8
+
9
+ static get observedAttributes() {
10
+ return ["value","storageKey"];
11
+ }
12
+
13
+ static get propTypes() {
14
+ return {"value":"String","storageKey":"String"};
15
+ }
16
+
17
+
18
+ get value() {
19
+ return this._props['value'];
20
+ }
21
+ set value(val) {
22
+ // External / framework property writes: re-render, do not emit kitbash-change
23
+ // (caller already knows the value they set).
24
+ this._assignProp('value', val);
25
+ this.update();
26
+ }
27
+
28
+ get storageKey() {
29
+ return this._props['storageKey'];
30
+ }
31
+ set storageKey(val) {
32
+ // External / framework property writes: re-render, do not emit kitbash-change
33
+ // (caller already knows the value they set).
34
+ this._assignProp('storageKey', val);
35
+ this.update();
36
+ }
37
+
38
+
39
+ constructor() {
40
+ super();
41
+ this.attachShadow({ mode: 'open', delegatesFocus: true });
42
+
43
+ this.shadowRoot.adoptedStyleSheets = [sheet];
44
+
45
+ this._defaults = {"value":"","storageKey":"ui-mode"};
46
+
47
+ this._state = {};
48
+ this._props = { ...this._defaults };
49
+ this._eventHandlers = { 'click button': function(_e, { props, commit }) {
50
+ let cur = props.value === "default" || props.value === "terminal" ? props.value : "";
51
+ if (!cur && typeof document < "u") {
52
+ const root = document.documentElement;
53
+ if (root.dataset.kbPreset === "terminal")
54
+ cur = "terminal";
55
+ else if (root.dataset.uiMode === "terminal")
56
+ cur = "terminal";
57
+ else
58
+ cur = "default";
59
+ }
60
+ if (!cur)
61
+ cur = "default";
62
+ const next = cur === "terminal" ? "default" : "terminal";
63
+ if (typeof document < "u") {
64
+ const root = document.documentElement;
65
+ if (next === "terminal") {
66
+ root.dataset.kbPreset = "terminal";
67
+ root.dataset.uiMode = "terminal";
68
+ } else {
69
+ delete root.dataset.kbPreset;
70
+ root.dataset.uiMode = "regular";
71
+ }
72
+ }
73
+ const key = typeof props.storageKey === "string" ? props.storageKey : "ui-mode";
74
+ if (key && typeof localStorage < "u")
75
+ try {
76
+ const stored = key === "ui-mode" ? next === "terminal" ? "terminal" : "regular" : next;
77
+ localStorage.setItem(key, stored);
78
+ } catch {}
79
+ commit({ props: { value: next } });
80
+ } };
81
+ this._eventCleanup = [];
82
+ this._renderFn = function({ props, html }) {
83
+ let preset = props.value === "default" || props.value === "terminal" ? props.value : "";
84
+ if (!preset && typeof document < "u") {
85
+ const root = document.documentElement;
86
+ if (root.dataset.kbPreset === "terminal")
87
+ preset = "terminal";
88
+ else if (root.dataset.uiMode === "terminal")
89
+ preset = "terminal";
90
+ else
91
+ preset = "default";
92
+ }
93
+ if (!preset)
94
+ preset = "default";
95
+ const isRegular = preset === "default";
96
+ return html`
97
+ <button
98
+ part="toggle-root"
99
+ type="button"
100
+ aria-pressed=${isRegular ? "true" : "false"}
101
+ aria-label=${isRegular ? "Switch to terminal UI preset" : "Switch to regular UI preset"}
102
+ title="Toggle UI mode"
103
+ >
104
+ ${isRegular ? "ui=regular" : "ui=terminal"}
105
+ </button>
106
+ `;
107
+ };
108
+ this._reflecting = false;
109
+ }
110
+
111
+ connectedCallback() {
112
+ // Initial attribute values are parsed by attributeChangedCallback before connectedCallback fires
113
+ this.update();
114
+
115
+ }
116
+
117
+ disconnectedCallback() {
118
+ this.cleanupEvents();
119
+ }
120
+
121
+ attributeChangedCallback(name, oldValue, newValue) {
122
+ // Skip when we caused the attribute write ourselves (avoids double update)
123
+ if (this._reflecting) return;
124
+ if (oldValue === newValue) return;
125
+
126
+ const type = this.constructor.propTypes[name];
127
+
128
+ let parsedValue = newValue;
129
+ if (newValue === null || newValue === undefined) {
130
+ parsedValue = this._defaults[name];
131
+ } else if (type === 'Number' && newValue !== '') {
132
+ // Match property coercion: empty string is not Number('') → 0
133
+ parsedValue = Number(newValue);
134
+ } else if (type === 'Boolean') {
135
+ parsedValue = newValue !== null && newValue !== 'false';
136
+ }
137
+
138
+ this._props[name] = parsedValue;
139
+
140
+ this.update();
141
+ }
142
+
143
+ /** Coerce + reflect a single prop; no re-render (caller decides). */
144
+ _assignProp(key, val) {
145
+ const type = this.constructor.propTypes[key];
146
+ let next = val;
147
+ if (type === 'Boolean') {
148
+ next = val === true || val === '';
149
+ } else if (type === 'Number' && val !== null && val !== undefined && val !== '') {
150
+ next = Number(val);
151
+ }
152
+ this._props[key] = next;
153
+
154
+ // Only reflect primitives — objects/arrays stay as properties (no [object Object] attrs)
155
+ const isPrimitive =
156
+ next === null ||
157
+ next === undefined ||
158
+ typeof next === 'string' ||
159
+ typeof next === 'number' ||
160
+ typeof next === 'boolean';
161
+
162
+ this._reflecting = true;
163
+ try {
164
+ if (type === 'Boolean') {
165
+ if (next) this.setAttribute(key, '');
166
+ else this.removeAttribute(key);
167
+ } else if (!isPrimitive) {
168
+ // property-only; drop stale attribute if any
169
+ this.removeAttribute(key);
170
+ } else if (next === null || next === undefined) {
171
+ this.removeAttribute(key);
172
+ } else {
173
+ this.setAttribute(key, String(next));
174
+ }
175
+ } finally {
176
+ this._reflecting = false;
177
+ }
178
+
179
+ }
180
+
181
+ _emitChange() {
182
+ // Shallow-copy both bags so listeners cannot mutate internal state/props via event.detail
183
+ this.dispatchEvent(new CustomEvent('kitbash-change', {
184
+ detail: {
185
+ state: { ...this._state },
186
+ props: { ...this._props },
187
+ },
188
+ bubbles: true,
189
+ composed: true
190
+ }));
191
+ }
192
+
193
+ /**
194
+ * Batch props and/or state: one uhtml update, one kitbash-change.
195
+ * Prefer this in event handlers (e.g. controlled inputs).
196
+ */
197
+ commit(patch) {
198
+ const p = patch || {};
199
+ if (p.props) {
200
+ const allowed = this.constructor.propTypes || {};
201
+ for (const key of Object.keys(p.props)) {
202
+ // Only declared props — ignore typos / stray keys
203
+ if (!Object.prototype.hasOwnProperty.call(allowed, key)) continue;
204
+ this._assignProp(key, p.props[key]);
205
+ }
206
+ }
207
+ if (p.state) {
208
+ this._state = { ...this._state, ...p.state };
209
+ }
210
+ this.update();
211
+ this._emitChange();
212
+ }
213
+
214
+ setState(newState) {
215
+ this.commit({ state: newState });
216
+ }
217
+
218
+ setProps(nextProps) {
219
+ this.commit({ props: nextProps });
220
+ }
221
+
222
+ _handlerCtx() {
223
+ // Snapshots so handlers/render cannot mutate internal bags by accident
224
+ return {
225
+ props: { ...this._props },
226
+ state: { ...this._state },
227
+ setState: this.setState.bind(this),
228
+ setProps: this.setProps.bind(this),
229
+ commit: this.commit.bind(this),
230
+ };
231
+ }
232
+
233
+ cleanupEvents() {
234
+ this._eventCleanup.forEach(cleanup => cleanup());
235
+ this._eventCleanup = [];
236
+ }
237
+
238
+ update() {
239
+ // Run the uhtml DOM diffing render cycle
240
+ render(this.shadowRoot, this._renderFn({
241
+ ...this._handlerCtx(),
242
+ html
243
+ }));
244
+ this.cleanupEvents();
245
+ this.bindEvents();
246
+
247
+ }
248
+
249
+
250
+
251
+ bindEvents() {
252
+ Object.keys(this._eventHandlers).forEach(eventSelector => {
253
+ const parts = eventSelector.split(' ');
254
+ const eventName = parts[0];
255
+ const selector = parts.slice(1).join(' ');
256
+
257
+ const targets = selector ? this.shadowRoot.querySelectorAll(selector) : [this];
258
+ targets.forEach(target => {
259
+ const handler = (e) => {
260
+ this._eventHandlers[eventSelector](e, this._handlerCtx());
261
+ };
262
+ target.addEventListener(eventName, handler);
263
+ this._eventCleanup.push(() => target.removeEventListener(eventName, handler));
264
+ });
265
+ });
266
+ }
267
+ }
268
+
269
+ if (!customElements.get('kitbash-preset-toggle')) {
270
+ customElements.define('kitbash-preset-toggle', KitbashPresetToggle);
271
+ }
@@ -15,31 +15,65 @@ var d=(_,U,B,Y,I)=>{let O=B.length,Q=U.length,z=O,W=0,X=0,J=null;while(W<Q||X<z)
15
15
  user-select: none;
16
16
  }
17
17
  input {
18
- appearance: auto;
19
- width: 1.5rem;
20
- height: 1.5rem;
21
- min-width: 1.5rem;
22
- min-height: 1.5rem;
18
+ appearance: none;
19
+ -webkit-appearance: none;
20
+ box-sizing: border-box;
21
+ width: 1.15em;
22
+ height: 1.15em;
23
+ min-width: 1.15em;
24
+ min-height: 1.15em;
23
25
  margin: 0;
24
26
  flex-shrink: 0;
25
- accent-color: var(--kb-color-accent-default);
27
+ display: grid;
28
+ place-content: center;
29
+ border: 2px solid var(--kb-color-border-default);
30
+ border-radius: var(--kb-radius-full);
31
+ background: var(--kb-color-bg-canvas);
26
32
  cursor: pointer;
33
+ transition:
34
+ border-color 0.12s ease,
35
+ box-shadow 0.12s ease;
36
+ }
37
+ input::before {
38
+ content: '';
39
+ width: 0.55em;
40
+ height: 0.55em;
41
+ border-radius: var(--kb-radius-full);
42
+ transform: scale(0);
43
+ transition: transform 0.12s ease;
44
+ background: var(--kb-color-accent-default);
45
+ box-shadow: 0 0 0 1px var(--kb-color-accent-default);
46
+ }
47
+ input:hover:not(:disabled) {
48
+ border-color: var(--kb-color-border-focus);
49
+ }
50
+ input:checked {
51
+ border-color: var(--kb-color-accent-default);
52
+ background: var(--kb-color-accent-subtle);
53
+ }
54
+ input:checked::before {
55
+ transform: scale(1);
27
56
  }
28
57
  input:focus-visible {
29
58
  outline: none;
30
59
  box-shadow: var(--kb-focus-ring);
31
- border-radius: var(--kb-radius-full);
32
60
  }
33
61
  input:disabled {
34
62
  cursor: not-allowed;
35
- opacity: 0.55;
63
+ opacity: 0.5;
36
64
  }
37
- input[aria-invalid="true"] {
38
- outline: 1px solid var(--kb-color-danger-default);
65
+ input[aria-invalid='true'] {
66
+ border-color: var(--kb-color-danger-default);
39
67
  }
40
68
  .label {
41
69
  line-height: var(--kb-line-height-normal);
42
70
  }
71
+ @media (prefers-reduced-motion: reduce) {
72
+ input,
73
+ input::before {
74
+ transition: none;
75
+ }
76
+ }
43
77
  `);class L1 extends HTMLElement{static formAssociated=!0;static get observedAttributes(){return["name","value","checked","disabled","required","invalid"]}static get propTypes(){return{name:"String",value:"String",checked:"Boolean",disabled:"Boolean",required:"Boolean",invalid:"Boolean"}}get name(){return this._props.name}set name(_){this._assignProp("name",_),this.update()}get value(){return this._props.value}set value(_){this._assignProp("value",_),this.update()}get checked(){return this._props.checked}set checked(_){this._assignProp("checked",_),this.update()}get disabled(){return this._props.disabled}set disabled(_){this._assignProp("disabled",_),this.update()}get required(){return this._props.required}set required(_){this._assignProp("required",_),this.update()}get invalid(){return this._props.invalid}set invalid(_){this._assignProp("invalid",_),this.update()}constructor(){super();this.attachShadow({mode:"open",delegatesFocus:!0}),this._internals=this.attachInternals(),this.shadowRoot.adoptedStyleSheets=[G1],this._defaults={name:"",value:"",checked:!1,disabled:!1,required:!1,invalid:!1},this._state={},this._props={...this._defaults},this._eventHandlers={click:function(_){let U=_.currentTarget;if(_.composedPath()[0]!==U)return;let B=U.shadowRoot?.querySelector("input");if(!B||B.disabled)return;B.click()},"change input":function(_,{commit:U,props:B}){let Y=_.target,I=Y.getRootNode().host,O=typeof B.name==="string"?B.name:"";if(Y.checked&&O&&typeof document<"u"){let Q=I.closest("form"),z=[],W=(X)=>{X.querySelectorAll("kitbash-radio").forEach((J)=>{z.push(J)}),X.querySelectorAll("*").forEach((J)=>{let $=J.shadowRoot;if($)W($)})};W(document),z.forEach((X)=>{if(X===I)return;if(X.closest("form")!==Q)return;let J=X;if(J.name===O&&J.checked)J.checked=!1})}U({props:{checked:Y.checked}})}},this._eventCleanup=[],this._renderFn=function({props:_,html:U}){return U`
44
78
  <label part="radio-container" class="label-wrap">
45
79
  <input
@@ -2,7 +2,7 @@
2
2
  import { render, html } from 'uhtml';
3
3
 
4
4
  const sheet = new CSSStyleSheet();
5
- sheet.replaceSync("\n\n :host {\n display: inline-flex;\n vertical-align: middle;\n font-family: var(--kb-font-family-sans);\n font-size: var(--kb-font-size-md);\n color: var(--kb-color-fg-default);\n }\n .label-wrap {\n display: inline-flex;\n align-items: center;\n gap: var(--kb-space-sm);\n cursor: pointer;\n user-select: none;\n }\n input {\n appearance: auto;\n width: 1.5rem;\n height: 1.5rem;\n min-width: 1.5rem;\n min-height: 1.5rem;\n margin: 0;\n flex-shrink: 0;\n accent-color: var(--kb-color-accent-default);\n cursor: pointer;\n }\n input:focus-visible {\n outline: none;\n box-shadow: var(--kb-focus-ring);\n border-radius: var(--kb-radius-full);\n }\n input:disabled {\n cursor: not-allowed;\n opacity: 0.55;\n }\n input[aria-invalid=\"true\"] {\n outline: 1px solid var(--kb-color-danger-default);\n }\n .label {\n line-height: var(--kb-line-height-normal);\n }\n ");
5
+ sheet.replaceSync("\n\n :host {\n display: inline-flex;\n vertical-align: middle;\n font-family: var(--kb-font-family-sans);\n font-size: var(--kb-font-size-md);\n color: var(--kb-color-fg-default);\n }\n .label-wrap {\n display: inline-flex;\n align-items: center;\n gap: var(--kb-space-sm);\n cursor: pointer;\n user-select: none;\n }\n input {\n appearance: none;\n -webkit-appearance: none;\n box-sizing: border-box;\n width: 1.15em;\n height: 1.15em;\n min-width: 1.15em;\n min-height: 1.15em;\n margin: 0;\n flex-shrink: 0;\n display: grid;\n place-content: center;\n border: 2px solid var(--kb-color-border-default);\n border-radius: var(--kb-radius-full);\n background: var(--kb-color-bg-canvas);\n cursor: pointer;\n transition:\n border-color 0.12s ease,\n box-shadow 0.12s ease;\n }\n input::before {\n content: '';\n width: 0.55em;\n height: 0.55em;\n border-radius: var(--kb-radius-full);\n transform: scale(0);\n transition: transform 0.12s ease;\n background: var(--kb-color-accent-default);\n box-shadow: 0 0 0 1px var(--kb-color-accent-default);\n }\n input:hover:not(:disabled) {\n border-color: var(--kb-color-border-focus);\n }\n input:checked {\n border-color: var(--kb-color-accent-default);\n background: var(--kb-color-accent-subtle);\n }\n input:checked::before {\n transform: scale(1);\n }\n input:focus-visible {\n outline: none;\n box-shadow: var(--kb-focus-ring);\n }\n input:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n }\n input[aria-invalid='true'] {\n border-color: var(--kb-color-danger-default);\n }\n .label {\n line-height: var(--kb-line-height-normal);\n }\n @media (prefers-reduced-motion: reduce) {\n input,\n input::before {\n transition: none;\n }\n }\n ");
6
6
 
7
7
  export class KitbashRadio extends HTMLElement {
8
8
  static formAssociated = true;
@@ -0,0 +1,2 @@
1
+ /** Side-effect import — registers the custom element. */
2
+ export {};
@@ -0,0 +1,69 @@
1
+ var d=(_,U,B,Y,I)=>{let W=B.length,J=U.length,z=W,O=0,$=0,X=null;while(O<J||$<z)if(J===O){let Q=z<W?$?Y(B[$-1],-0).nextSibling:Y(B[z],0):I;while($<z)_.insertBefore(Y(B[$++],1),Q)}else if(z===$)while(O<J){if(!X||!X.has(U[O]))_.removeChild(Y(U[O],-1));O++}else if(U[O]===B[$])O++,$++;else if(U[J-1]===B[z-1])J--,z--;else if(U[O]===B[z-1]&&B[$]===U[J-1]){let Q=Y(U[--J],-0).nextSibling;_.insertBefore(Y(B[$++],1),Y(U[O++],-0).nextSibling),_.insertBefore(Y(B[--z],1),Q),U[J]=B[z]}else{if(!X){X=new Map;let Q=$;while(Q<z)X.set(B[Q],Q++)}if(X.has(U[O])){let Q=X.get(U[O]);if($<Q&&Q<z){let Z=O,K=1;while(++Z<J&&Z<z&&X.get(U[Z])===Q+K)K++;if(K>Q-$){let D=Y(U[O],0);while($<Q)_.insertBefore(Y(B[$++],1),D)}else _.replaceChild(Y(B[$++],1),Y(U[O++],-1))}else O++}else _.removeChild(Y(U[O++],-1))}return B};var{isArray:E}=Array,{getPrototypeOf:R1,getOwnPropertyDescriptor:D1}=Object;var m="http://www.w3.org/2000/svg",q=[],N=()=>document.createRange(),M=(_,U,B)=>{return _.set(U,B),B},i=(_,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),P=(_,U)=>_.removeAttribute(U),H1=(_,U)=>{for(let B in U){let Y=U[B],I=B==="role"?B:`aria-${B}`;if(Y==null)P(_,I);else e(_,I,Y)}return U},C,P1=(_,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},F1=(_,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?(P(_,B),U):k(_,U,B),_1=(_,U)=>(typeof U==="function"?U(_):U.current=_,U),y=(_,U,B)=>(U==null?P(_,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 d(_.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",H1],["class",F1],["data",A1],["ref",_1],["style",N1]]),B1=(_,U,B)=>{switch(U[0]){case".":return E1;case"?":return V1;case"@":return P1;default:return B||"ownerSVGElement"in _?U==="ref"?_1:y:U1.get(U)||(U in _?U.startsWith("on")?k:i(_,U)?.set?f:y:y)}},Y1=(_,U)=>(_.textContent=U==null?"":U,U);var H=(_,U,B)=>({a:_,b:U,c:B}),z1=(_,U)=>({b:_,c:U}),I1=(_,U,B,Y)=>({v:q,u:_,t:U,n:B,c:Y}),L=()=>H(null,null,q);var b=(_)=>(U,B)=>{let{a:Y,b:I,c:W}=_(U,B),J=document.importNode(Y,!0),z=q;if(I!==q){z=[];for(let O,$,X=0;X<I.length;X++){let{a:Q,b:Z,c:K}=I[X],D=Q===$?O:O=o(J,$=Q);z[X]=I1(Z,D,K,Z===R?[]:Z===G?L():null)}}return z1(W?J.firstChild:new w(J),z)};var J1=/^(?:plaintext|script|style|textarea|title|xmp)$/i,O1=/^(?: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,Q1=(_,U,B)=>{let Y=0;return _.join("\x01").trim().replace(j1,(I,W,J,z)=>`<${W}${J.replace(C1,"\x02=$2$1").trimEnd()}${z?B||O1.test(W)?" /":`></${W}`:""}>`).replace(k1,(I)=>I==="\x01"?`<!--${U+Y++}-->`:U+Y++)};var S=document.createElement("template"),g,h,W1=(_,U)=>{if(U){if(!g)g=document.createElementNS(m,"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},X1=()=>document.createTextNode(""),S1=(_,U,B)=>{let Y=W1(Q1(_,F,B),B),{length:I}=_,W=q;if(I>1){let O=[],$=document.createTreeWalker(Y,129),X=0,Q=`${F}${X++}`;W=[];while(X<I){let Z=$.nextNode();if(Z.nodeType===r){if(Z.data===Q){let K=E(U[X-1])?R:G;if(K===G)O.push(Z);W.push(H(c(Z),K,null)),Q=`${F}${X++}`}}else{let K;while(Z.hasAttribute(Q)){if(!K)K=c(Z);let D=Z.getAttribute(Q);W.push(H(K,B1(Z,D,B),D)),P(Z,Q),Q=`${F}${X++}`}if(!B&&J1.test(Z.localName)&&Z.textContent.trim()===`<!--${Q}-->`)W.push(H(K||c(Z),Y1,null)),Q=`${F}${X++}`}}for(X=0;X<O.length;X++)O[X].replaceWith(X1())}let{childNodes:J}=Y,{length:z}=J;if(z<1)z=1,Y.appendChild(X1());else if(z===1&&I!==1&&J[0].nodeType!==s)z=0;return M($1,_,H(Y,W,z===1))},$1=new WeakMap,F="isµ",v=(_)=>(U,B)=>$1.get(U)||S1(U,B,_);var x1=b(v(!1)),w1=b(v(!0)),p=(_,{s:U,t:B,v:Y})=>{if(_.a!==B){let{b:I,c:W}=(U?w1:x1)(B,Y);_.a=B,_.b=I,_.c=W}for(let{c:I}=_,W=0;W<I.length;W++){let J=Y[W],z=I[W];switch(z.u){case R:z.v=R(z.t,y1(z.c,J),z.v);break;case G:let O=J instanceof A?p(z.c||(z.c=L()),J):(z.c=null,J);if(O!==z.v)z.v=G(z,O);break;default:if(J!==z.v)z.v=z.u(z.t,J,z.n,z.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]=p(_[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 p(_,this)}}var Z1=new WeakMap,u=(_,U)=>{let B=Z1.get(_)||M(Z1,_,L()),{b:Y}=B;if(Y!==(typeof U==="function"?U():U).toDOM(B))_.replaceChildren(B.b.valueOf());return _};/*! (c) Andrea Giammarchi - MIT */var K1=(_)=>(U,...B)=>new A(_,U,B),q1=K1(!1),C_=K1(!0);var G1=new CSSStyleSheet;G1.replaceSync(`
2
+
3
+ :host {
4
+ position: fixed;
5
+ bottom: var(--kb-space-lg);
6
+ right: var(--kb-space-md);
7
+ z-index: 40;
8
+ font-family: var(--kb-font-family-sans);
9
+ /* Hidden unless visible — keeps focusable only when shown */
10
+ display: none;
11
+ }
12
+ :host([visible]) {
13
+ display: block;
14
+ }
15
+ button {
16
+ box-sizing: border-box;
17
+ min-height: 2.25rem;
18
+ min-width: 2.25rem;
19
+ padding: var(--kb-space-sm) var(--kb-space-md);
20
+ border: 1px solid var(--kb-color-border-default);
21
+ border-radius: var(--kb-radius-sm);
22
+ background: var(--kb-color-bg-canvas);
23
+ color: var(--kb-color-accent-default);
24
+ font: inherit;
25
+ font-size: var(--kb-font-size-sm);
26
+ font-weight: var(--kb-font-weight-medium);
27
+ letter-spacing: 0.25em;
28
+ text-transform: uppercase;
29
+ cursor: pointer;
30
+ box-shadow: var(--kb-shadow-sm);
31
+ transition:
32
+ color 0.2s ease,
33
+ background-color 0.2s ease,
34
+ border-color 0.2s ease;
35
+ }
36
+ button:hover {
37
+ color: var(--kb-color-fg-default);
38
+ background: var(--kb-color-bg-surface);
39
+ border-color: var(--kb-color-border-focus);
40
+ }
41
+ button:focus-visible {
42
+ outline: none;
43
+ box-shadow: var(--kb-focus-ring);
44
+ }
45
+ .caption {
46
+ display: inline-block;
47
+ }
48
+ .sr-only {
49
+ position: absolute;
50
+ width: 1px;
51
+ height: 1px;
52
+ padding: 0;
53
+ margin: -1px;
54
+ overflow: hidden;
55
+ clip: rect(0, 0, 0, 0);
56
+ white-space: nowrap;
57
+ border: 0;
58
+ }
59
+ @media (prefers-reduced-motion: reduce) {
60
+ button {
61
+ transition: none;
62
+ }
63
+ }
64
+ `);class L1 extends HTMLElement{static get observedAttributes(){return["visible","label","caption","behavior"]}static get propTypes(){return{visible:"Boolean",label:"String",caption:"String",behavior:"String"}}get visible(){return this._props.visible}set visible(_){this._assignProp("visible",_),this.update()}get label(){return this._props.label}set label(_){this._assignProp("label",_),this.update()}get caption(){return this._props.caption}set caption(_){this._assignProp("caption",_),this.update()}get behavior(){return this._props.behavior}set behavior(_){this._assignProp("behavior",_),this.update()}constructor(){super();this.attachShadow({mode:"open",delegatesFocus:!0}),this.shadowRoot.adoptedStyleSheets=[G1],this._defaults={visible:!1,label:"Scroll to top",caption:"$ top",behavior:"smooth"},this._state={},this._props={...this._defaults},this._eventHandlers={"click button":function(_,{props:U}){if(typeof window>"u")return;let B=U.behavior==="auto"||U.behavior==="instant"?"auto":"smooth",Y=typeof matchMedia==="function"&&matchMedia("(prefers-reduced-motion: reduce)").matches;window.scrollTo({top:0,behavior:Y?"auto":B})}},this._eventCleanup=[],this._renderFn=function({props:_,html:U}){let B=typeof _.label==="string"&&_.label.length>0?_.label:"Scroll to top",Y=typeof _.caption==="string"&&_.caption.length>0?_.caption:"$ top";return U`
65
+ <button part="scroll-top-root" type="button" aria-label=${B}>
66
+ <span class="sr-only">${B}</span>
67
+ <span class="caption" aria-hidden="true">${Y}</span>
68
+ </button>
69
+ `},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(){u(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((W)=>{let J=(z)=>{this._eventHandlers[_](z,this._handlerCtx())};W.addEventListener(B,J),this._eventCleanup.push(()=>W.removeEventListener(B,J))})})}}if(!customElements.get("kitbash-scroll-top"))customElements.define("kitbash-scroll-top",L1);export{L1 as KitbashScrollTop};