@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.
- package/README.md +165 -0
- package/dist/custom-elements.json +1427 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +12 -0
- package/dist/react/alert.d.ts +20 -0
- package/dist/react/alert.js +30 -0
- package/dist/react/badge.d.ts +17 -0
- package/dist/react/badge.js +30 -0
- package/dist/react/box.d.ts +22 -0
- package/dist/react/box.js +30 -0
- package/dist/react/breadcrumb.d.ts +17 -0
- package/dist/react/breadcrumb.js +30 -0
- package/dist/react/button.d.ts +20 -0
- package/dist/react/button.js +30 -0
- package/dist/react/checkbox.d.ts +22 -0
- package/dist/react/checkbox.js +30 -0
- package/dist/react/container.d.ts +18 -0
- package/dist/react/container.js +30 -0
- package/dist/react/field.d.ts +20 -0
- package/dist/react/field.js +30 -0
- package/dist/react/heading.d.ts +19 -0
- package/dist/react/heading.js +30 -0
- package/dist/react/input.d.ts +25 -0
- package/dist/react/input.js +30 -0
- package/dist/react/label.d.ts +18 -0
- package/dist/react/label.js +30 -0
- package/dist/react/link.d.ts +21 -0
- package/dist/react/link.js +30 -0
- package/dist/react/modal.d.ts +18 -0
- package/dist/react/modal.js +30 -0
- package/dist/react/nav.d.ts +17 -0
- package/dist/react/nav.js +30 -0
- package/dist/react/pagination.d.ts +19 -0
- package/dist/react/pagination.js +30 -0
- package/dist/react/progress.d.ts +19 -0
- package/dist/react/progress.js +30 -0
- package/dist/react/radio.d.ts +22 -0
- package/dist/react/radio.js +30 -0
- package/dist/react/select.d.ts +22 -0
- package/dist/react/select.js +30 -0
- package/dist/react/skeleton.d.ts +19 -0
- package/dist/react/skeleton.js +30 -0
- package/dist/react/spinner.d.ts +18 -0
- package/dist/react/spinner.js +30 -0
- package/dist/react/stack.d.ts +21 -0
- package/dist/react/stack.js +30 -0
- package/dist/react/switch.d.ts +21 -0
- package/dist/react/switch.js +30 -0
- package/dist/react/tab-panel.d.ts +18 -0
- package/dist/react/tab-panel.js +30 -0
- package/dist/react/tab.d.ts +19 -0
- package/dist/react/tab.js +30 -0
- package/dist/react/tabs.d.ts +17 -0
- package/dist/react/tabs.js +30 -0
- package/dist/react/text.d.ts +19 -0
- package/dist/react/text.js +30 -0
- package/dist/react/textarea.d.ts +24 -0
- package/dist/react/textarea.js +30 -0
- package/dist/react/toast.d.ts +19 -0
- package/dist/react/toast.js +30 -0
- package/dist/themes/dark.css +52 -0
- package/dist/themes/light.css +53 -0
- package/dist/tokens/index.d.ts +7 -0
- package/dist/tokens/index.js +7 -0
- package/dist/tokens/semantic.d.ts +209 -0
- package/dist/tokens/semantic.js +102 -0
- package/dist/types/index.d.ts +15 -0
- package/dist/types/index.js +2 -0
- package/dist/vanilla/alert.d.ts +2 -0
- package/dist/vanilla/alert.js +134 -0
- package/dist/vanilla/alert.src.js +257 -0
- package/dist/vanilla/badge.d.ts +2 -0
- package/dist/vanilla/badge.js +89 -0
- package/dist/vanilla/badge.src.js +212 -0
- package/dist/vanilla/box.d.ts +2 -0
- package/dist/vanilla/box.js +100 -0
- package/dist/vanilla/box.src.js +270 -0
- package/dist/vanilla/breadcrumb.d.ts +2 -0
- package/dist/vanilla/breadcrumb.js +93 -0
- package/dist/vanilla/breadcrumb.src.js +255 -0
- package/dist/vanilla/button.d.ts +2 -0
- package/dist/vanilla/button.js +135 -0
- package/dist/vanilla/button.src.js +260 -0
- package/dist/vanilla/checkbox.d.ts +2 -0
- package/dist/vanilla/checkbox.js +96 -0
- package/dist/vanilla/checkbox.src.js +296 -0
- package/dist/vanilla/container.d.ts +2 -0
- package/dist/vanilla/container.js +79 -0
- package/dist/vanilla/container.src.js +222 -0
- package/dist/vanilla/field.d.ts +2 -0
- package/dist/vanilla/field.js +113 -0
- package/dist/vanilla/field.src.js +278 -0
- package/dist/vanilla/heading.d.ts +2 -0
- package/dist/vanilla/heading.js +75 -0
- package/dist/vanilla/heading.src.js +241 -0
- package/dist/vanilla/input.d.ts +2 -0
- package/dist/vanilla/input.js +121 -0
- package/dist/vanilla/input.src.js +326 -0
- package/dist/vanilla/label.d.ts +2 -0
- package/dist/vanilla/label.js +75 -0
- package/dist/vanilla/label.src.js +232 -0
- package/dist/vanilla/link.d.ts +2 -0
- package/dist/vanilla/link.js +96 -0
- package/dist/vanilla/link.src.js +272 -0
- package/dist/vanilla/modal.d.ts +2 -0
- package/dist/vanilla/modal.js +125 -0
- package/dist/vanilla/modal.src.js +251 -0
- package/dist/vanilla/nav.d.ts +2 -0
- package/dist/vanilla/nav.js +94 -0
- package/dist/vanilla/nav.src.js +214 -0
- package/dist/vanilla/pagination.d.ts +2 -0
- package/dist/vanilla/pagination.js +114 -0
- package/dist/vanilla/pagination.src.js +262 -0
- package/dist/vanilla/progress.d.ts +2 -0
- package/dist/vanilla/progress.js +108 -0
- package/dist/vanilla/progress.src.js +244 -0
- package/dist/vanilla/radio.d.ts +2 -0
- package/dist/vanilla/radio.js +104 -0
- package/dist/vanilla/radio.src.js +326 -0
- package/dist/vanilla/select.d.ts +2 -0
- package/dist/vanilla/select.js +104 -0
- package/dist/vanilla/select.src.js +332 -0
- package/dist/vanilla/skeleton.d.ts +2 -0
- package/dist/vanilla/skeleton.js +99 -0
- package/dist/vanilla/skeleton.src.js +240 -0
- package/dist/vanilla/spinner.d.ts +2 -0
- package/dist/vanilla/spinner.js +108 -0
- package/dist/vanilla/spinner.src.js +222 -0
- package/dist/vanilla/stack.d.ts +2 -0
- package/dist/vanilla/stack.js +83 -0
- package/dist/vanilla/stack.src.js +259 -0
- package/dist/vanilla/switch.d.ts +2 -0
- package/dist/vanilla/switch.js +166 -0
- package/dist/vanilla/switch.src.js +299 -0
- package/dist/vanilla/tab-panel.d.ts +2 -0
- package/dist/vanilla/tab-panel.js +80 -0
- package/dist/vanilla/tab-panel.src.js +227 -0
- package/dist/vanilla/tab.d.ts +2 -0
- package/dist/vanilla/tab.js +99 -0
- package/dist/vanilla/tab.src.js +253 -0
- package/dist/vanilla/tabs.d.ts +2 -0
- package/dist/vanilla/tabs.js +66 -0
- package/dist/vanilla/tabs.src.js +237 -0
- package/dist/vanilla/text.d.ts +2 -0
- package/dist/vanilla/text.js +74 -0
- package/dist/vanilla/text.src.js +232 -0
- package/dist/vanilla/textarea.d.ts +2 -0
- package/dist/vanilla/textarea.js +97 -0
- package/dist/vanilla/textarea.src.js +316 -0
- package/dist/vanilla/toast.d.ts +2 -0
- package/dist/vanilla/toast.js +120 -0
- package/dist/vanilla/toast.src.js +245 -0
- package/package.json +111 -0
|
@@ -0,0 +1,232 @@
|
|
|
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: inline-flex;\n align-items: baseline;\n gap: var(--kb-space-2xs);\n font-family: var(--kb-font-family-sans);\n font-size: var(--kb-font-size-sm);\n font-weight: var(--kb-font-weight-medium);\n color: var(--kb-color-fg-default);\n line-height: var(--kb-line-height-normal);\n }\n .root {\n display: inline-flex;\n align-items: baseline;\n gap: var(--kb-space-2xs);\n cursor: pointer;\n }\n .req {\n color: var(--kb-color-danger-default);\n font-weight: var(--kb-font-weight-semibold);\n }\n ");
|
|
6
|
+
|
|
7
|
+
export class KitbashLabel extends HTMLElement {
|
|
8
|
+
|
|
9
|
+
static get observedAttributes() {
|
|
10
|
+
return ["for","required"];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
static get propTypes() {
|
|
14
|
+
return {"for":"String","required":"Boolean"};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
get for() {
|
|
19
|
+
return this._props['for'];
|
|
20
|
+
}
|
|
21
|
+
set for(val) {
|
|
22
|
+
// External / framework property writes: re-render, do not emit kitbash-change
|
|
23
|
+
// (caller already knows the value they set).
|
|
24
|
+
this._assignProp('for', val);
|
|
25
|
+
this.update();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get required() {
|
|
29
|
+
return this._props['required'];
|
|
30
|
+
}
|
|
31
|
+
set required(val) {
|
|
32
|
+
// External / framework property writes: re-render, do not emit kitbash-change
|
|
33
|
+
// (caller already knows the value they set).
|
|
34
|
+
this._assignProp('required', val);
|
|
35
|
+
this.update();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
constructor() {
|
|
40
|
+
super();
|
|
41
|
+
this.attachShadow({ mode: 'open', delegatesFocus: false });
|
|
42
|
+
|
|
43
|
+
this.shadowRoot.adoptedStyleSheets = [sheet];
|
|
44
|
+
|
|
45
|
+
this._defaults = {"for":"","required":false};
|
|
46
|
+
|
|
47
|
+
this._state = {};
|
|
48
|
+
this._props = { ...this._defaults };
|
|
49
|
+
this._eventHandlers = { 'click .root': function(_e, { props }) {
|
|
50
|
+
const id = typeof props.for === "string" ? props.for : "";
|
|
51
|
+
if (!id || typeof document > "u")
|
|
52
|
+
return;
|
|
53
|
+
const el = document.getElementById(id);
|
|
54
|
+
if (!el)
|
|
55
|
+
return;
|
|
56
|
+
if (typeof el.focus === "function")
|
|
57
|
+
el.focus();
|
|
58
|
+
} };
|
|
59
|
+
this._eventCleanup = [];
|
|
60
|
+
this._renderFn = function({ props, html }) {
|
|
61
|
+
const forId = typeof props.for === "string" && props.for.length > 0 ? props.for : null;
|
|
62
|
+
return html`
|
|
63
|
+
<span part="label-root" class="root" data-for=${forId}>
|
|
64
|
+
<slot></slot>
|
|
65
|
+
${props.required ? html`<span class="req" aria-hidden="true">*</span>` : null}
|
|
66
|
+
</span>
|
|
67
|
+
`;
|
|
68
|
+
};
|
|
69
|
+
this._reflecting = false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
connectedCallback() {
|
|
73
|
+
// Initial attribute values are parsed by attributeChangedCallback before connectedCallback fires
|
|
74
|
+
this.update();
|
|
75
|
+
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
disconnectedCallback() {
|
|
79
|
+
this.cleanupEvents();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
83
|
+
// Skip when we caused the attribute write ourselves (avoids double update)
|
|
84
|
+
if (this._reflecting) return;
|
|
85
|
+
if (oldValue === newValue) return;
|
|
86
|
+
|
|
87
|
+
const type = this.constructor.propTypes[name];
|
|
88
|
+
|
|
89
|
+
let parsedValue = newValue;
|
|
90
|
+
if (newValue === null || newValue === undefined) {
|
|
91
|
+
parsedValue = this._defaults[name];
|
|
92
|
+
} else if (type === 'Number' && newValue !== '') {
|
|
93
|
+
// Match property coercion: empty string is not Number('') → 0
|
|
94
|
+
parsedValue = Number(newValue);
|
|
95
|
+
} else if (type === 'Boolean') {
|
|
96
|
+
parsedValue = newValue !== null && newValue !== 'false';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
this._props[name] = parsedValue;
|
|
100
|
+
|
|
101
|
+
this.update();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Coerce + reflect a single prop; no re-render (caller decides). */
|
|
105
|
+
_assignProp(key, val) {
|
|
106
|
+
const type = this.constructor.propTypes[key];
|
|
107
|
+
let next = val;
|
|
108
|
+
if (type === 'Boolean') {
|
|
109
|
+
next = val === true || val === '';
|
|
110
|
+
} else if (type === 'Number' && val !== null && val !== undefined && val !== '') {
|
|
111
|
+
next = Number(val);
|
|
112
|
+
}
|
|
113
|
+
this._props[key] = next;
|
|
114
|
+
|
|
115
|
+
// Only reflect primitives — objects/arrays stay as properties (no [object Object] attrs)
|
|
116
|
+
const isPrimitive =
|
|
117
|
+
next === null ||
|
|
118
|
+
next === undefined ||
|
|
119
|
+
typeof next === 'string' ||
|
|
120
|
+
typeof next === 'number' ||
|
|
121
|
+
typeof next === 'boolean';
|
|
122
|
+
|
|
123
|
+
this._reflecting = true;
|
|
124
|
+
try {
|
|
125
|
+
if (type === 'Boolean') {
|
|
126
|
+
if (next) this.setAttribute(key, '');
|
|
127
|
+
else this.removeAttribute(key);
|
|
128
|
+
} else if (!isPrimitive) {
|
|
129
|
+
// property-only; drop stale attribute if any
|
|
130
|
+
this.removeAttribute(key);
|
|
131
|
+
} else if (next === null || next === undefined) {
|
|
132
|
+
this.removeAttribute(key);
|
|
133
|
+
} else {
|
|
134
|
+
this.setAttribute(key, String(next));
|
|
135
|
+
}
|
|
136
|
+
} finally {
|
|
137
|
+
this._reflecting = false;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
_emitChange() {
|
|
143
|
+
// Shallow-copy both bags so listeners cannot mutate internal state/props via event.detail
|
|
144
|
+
this.dispatchEvent(new CustomEvent('kitbash-change', {
|
|
145
|
+
detail: {
|
|
146
|
+
state: { ...this._state },
|
|
147
|
+
props: { ...this._props },
|
|
148
|
+
},
|
|
149
|
+
bubbles: true,
|
|
150
|
+
composed: true
|
|
151
|
+
}));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Batch props and/or state: one uhtml update, one kitbash-change.
|
|
156
|
+
* Prefer this in event handlers (e.g. controlled inputs).
|
|
157
|
+
*/
|
|
158
|
+
commit(patch) {
|
|
159
|
+
const p = patch || {};
|
|
160
|
+
if (p.props) {
|
|
161
|
+
const allowed = this.constructor.propTypes || {};
|
|
162
|
+
for (const key of Object.keys(p.props)) {
|
|
163
|
+
// Only declared props — ignore typos / stray keys
|
|
164
|
+
if (!Object.prototype.hasOwnProperty.call(allowed, key)) continue;
|
|
165
|
+
this._assignProp(key, p.props[key]);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (p.state) {
|
|
169
|
+
this._state = { ...this._state, ...p.state };
|
|
170
|
+
}
|
|
171
|
+
this.update();
|
|
172
|
+
this._emitChange();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
setState(newState) {
|
|
176
|
+
this.commit({ state: newState });
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
setProps(nextProps) {
|
|
180
|
+
this.commit({ props: nextProps });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
_handlerCtx() {
|
|
184
|
+
// Snapshots so handlers/render cannot mutate internal bags by accident
|
|
185
|
+
return {
|
|
186
|
+
props: { ...this._props },
|
|
187
|
+
state: { ...this._state },
|
|
188
|
+
setState: this.setState.bind(this),
|
|
189
|
+
setProps: this.setProps.bind(this),
|
|
190
|
+
commit: this.commit.bind(this),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
cleanupEvents() {
|
|
195
|
+
this._eventCleanup.forEach(cleanup => cleanup());
|
|
196
|
+
this._eventCleanup = [];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
update() {
|
|
200
|
+
// Run the uhtml DOM diffing render cycle
|
|
201
|
+
render(this.shadowRoot, this._renderFn({
|
|
202
|
+
...this._handlerCtx(),
|
|
203
|
+
html
|
|
204
|
+
}));
|
|
205
|
+
this.cleanupEvents();
|
|
206
|
+
this.bindEvents();
|
|
207
|
+
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
bindEvents() {
|
|
213
|
+
Object.keys(this._eventHandlers).forEach(eventSelector => {
|
|
214
|
+
const parts = eventSelector.split(' ');
|
|
215
|
+
const eventName = parts[0];
|
|
216
|
+
const selector = parts.slice(1).join(' ');
|
|
217
|
+
|
|
218
|
+
const targets = selector ? this.shadowRoot.querySelectorAll(selector) : [this];
|
|
219
|
+
targets.forEach(target => {
|
|
220
|
+
const handler = (e) => {
|
|
221
|
+
this._eventHandlers[eventSelector](e, this._handlerCtx());
|
|
222
|
+
};
|
|
223
|
+
target.addEventListener(eventName, handler);
|
|
224
|
+
this._eventCleanup.push(() => target.removeEventListener(eventName, handler));
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (!customElements.get('kitbash-label')) {
|
|
231
|
+
customElements.define('kitbash-label', KitbashLabel);
|
|
232
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
var p=(_,U,Y,z,I)=>{let X=Y.length,O=U.length,B=X,J=0,$=0,W=null;while(J<O||$<B)if(O===J){let Q=B<X?$?z(Y[$-1],-0).nextSibling:z(Y[B],0):I;while($<B)_.insertBefore(z(Y[$++],1),Q)}else if(B===$)while(J<O){if(!W||!W.has(U[J]))_.removeChild(z(U[J],-1));J++}else if(U[J]===Y[$])J++,$++;else if(U[O-1]===Y[B-1])O--,B--;else if(U[J]===Y[B-1]&&Y[$]===U[O-1]){let Q=z(U[--O],-0).nextSibling;_.insertBefore(z(Y[$++],1),z(U[J++],-0).nextSibling),_.insertBefore(z(Y[--B],1),Q),U[O]=Y[B]}else{if(!W){W=new Map;let Q=$;while(Q<B)W.set(Y[Q],Q++)}if(W.has(U[J])){let Q=W.get(U[J]);if($<Q&&Q<B){let Z=J,K=1;while(++Z<O&&Z<B&&W.get(U[Z])===Q+K)K++;if(K>Q-$){let L=z(U[J],0);while($<Q)_.insertBefore(z(Y[$++],1),L)}else _.replaceChild(z(Y[$++],1),z(U[J++],-1))}else J++}else _.removeChild(z(U[J++],-1))}return Y};var{isArray:E}=Array,{getPrototypeOf:M1,getOwnPropertyDescriptor:L1}=Object;var i="http://www.w3.org/2000/svg",q=[],N=()=>document.createRange(),R=(_,U,Y)=>{return _.set(U,Y),Y},m=(_,U)=>{let Y;do Y=L1(_,U);while(!Y&&(_=M1(_)));return Y},o=(_,U)=>U.reduceRight(R1,_),R1=(_,U)=>_.childNodes[U];var s=1;var r=8;var l=11;var{setPrototypeOf:T1}=Object,n=(_)=>{function U(Y){return T1(Y,new.target.prototype)}return U.prototype=_.prototype,U};var T,V=(_,U,Y)=>{if(!T)T=N();if(Y)T.setStartAfter(_);else T.setStartBefore(_);return T.setEndAfter(U),T.deleteContents(),_};var x=({firstChild:_,lastChild:U},Y)=>V(_,U,Y),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("<>");#Y=a("</>");#_=q;constructor(_){super(_);this.replaceChildren(...[this.#U,..._.childNodes,this.#Y]),t=!0}get firstChild(){return this.#U}get lastChild(){return this.#Y}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:Y}=this;this.#_=[U];while(U!==Y)this.#_.push(U=U.nextSibling)}this.replaceChildren(...this.#_)}return this}}var e=(_,U,Y)=>_.setAttribute(U,Y),F=(_,U)=>_.removeAttribute(U),H1=(_,U)=>{for(let Y in U){let z=U[Y],I=Y==="role"?Y:`aria-${Y}`;if(z==null)F(_,I);else e(_,I,z)}return U},C,F1=(_,U,Y)=>{if(Y=Y.slice(1),!C)C=new WeakMap;let z=C.get(_)||R(C,_,{}),I=z[Y];if(I&&I[0])_.removeEventListener(Y,...I);if(I=E(U)?U:[U,!1],z[Y]=I,I[0])_.addEventListener(Y,...I);return U};var G=(_,U)=>{let{t:Y,n:z}=_,I=!1;switch(typeof U){case"object":if(U!==null){(z||Y).replaceWith(_.n=U.valueOf());break}case"undefined":I=!0;default:if(Y.data=I?"":U,z)_.n=null,z.replaceWith(Y);break}return U},P1=(_,U)=>f(_,U,U==null?"class":"className"),A1=(_,U)=>{let{dataset:Y}=_;for(let z in U)if(U[z]==null)delete Y[z];else Y[z]=U[z];return U},k=(_,U,Y)=>_[Y]=U,E1=(_,U,Y)=>k(_,U,Y.slice(1)),f=(_,U,Y)=>U==null?(F(_,Y),U):k(_,U,Y),_1=(_,U)=>(typeof U==="function"?U(_):U.current=_,U),y=(_,U,Y)=>(U==null?F(_,Y):e(_,Y,U),U),N1=(_,U)=>U==null?f(_,U,"style"):k(_.style,U,"cssText"),V1=(_,U,Y)=>(_.toggleAttribute(Y.slice(1),U),U),M=(_,U,Y)=>{let{length:z}=U;if(_.data=`[${z}]`,z)return p(_.parentNode,Y,U,j,_);switch(Y.length){case 1:Y[0].remove();case 0:break;default:V(j(Y[0],0),j(Y.at(-1),-0),!1);break}return q},U1=new Map([["aria",H1],["class",P1],["data",A1],["ref",_1],["style",N1]]),Y1=(_,U,Y)=>{switch(U[0]){case".":return E1;case"?":return V1;case"@":return F1;default:return Y||"ownerSVGElement"in _?U==="ref"?_1:y:U1.get(U)||(U in _?U.startsWith("on")?k:m(_,U)?.set?f:y:y)}},z1=(_,U)=>(_.textContent=U==null?"":U,U);var H=(_,U,Y)=>({a:_,b:U,c:Y}),B1=(_,U)=>({b:_,c:U}),I1=(_,U,Y,z)=>({v:q,u:_,t:U,n:Y,c:z}),D=()=>H(null,null,q);var b=(_)=>(U,Y)=>{let{a:z,b:I,c:X}=_(U,Y),O=document.importNode(z,!0),B=q;if(I!==q){B=[];for(let J,$,W=0;W<I.length;W++){let{a:Q,b:Z,c:K}=I[W],L=Q===$?J:J=o(O,$=Q);B[W]=I1(Z,L,K,Z===M?[]:Z===G?D():null)}}return B1(X?O.firstChild:new w(O),B)};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,Y)=>{let z=0;return _.join("\x01").trim().replace(j1,(I,X,O,B)=>`<${X}${O.replace(C1,"\x02=$2$1").trimEnd()}${B?Y||O1.test(X)?" /":`></${X}`:""}>`).replace(k1,(I)=>I==="\x01"?`<!--${U+z++}-->`:U+z++)};var S=document.createElement("template"),g,h,W1=(_,U)=>{if(U){if(!g)g=document.createElementNS(i,"svg"),h=N(),h.selectNodeContents(g);return h.createContextualFragment(_)}S.innerHTML=_;let{content:Y}=S;return S=S.cloneNode(!1),Y};var c=(_)=>{let U=[],Y;while(Y=_.parentNode)U.push(U.indexOf.call(Y.childNodes,_)),_=Y;return U},X1=()=>document.createTextNode(""),S1=(_,U,Y)=>{let z=W1(Q1(_,P,Y),Y),{length:I}=_,X=q;if(I>1){let J=[],$=document.createTreeWalker(z,129),W=0,Q=`${P}${W++}`;X=[];while(W<I){let Z=$.nextNode();if(Z.nodeType===r){if(Z.data===Q){let K=E(U[W-1])?M:G;if(K===G)J.push(Z);X.push(H(c(Z),K,null)),Q=`${P}${W++}`}}else{let K;while(Z.hasAttribute(Q)){if(!K)K=c(Z);let L=Z.getAttribute(Q);X.push(H(K,Y1(Z,L,Y),L)),F(Z,Q),Q=`${P}${W++}`}if(!Y&&J1.test(Z.localName)&&Z.textContent.trim()===`<!--${Q}-->`)X.push(H(K||c(Z),z1,null)),Q=`${P}${W++}`}}for(W=0;W<J.length;W++)J[W].replaceWith(X1())}let{childNodes:O}=z,{length:B}=O;if(B<1)B=1,z.appendChild(X1());else if(B===1&&I!==1&&O[0].nodeType!==s)B=0;return R($1,_,H(z,X,B===1))},$1=new WeakMap,P="isµ",v=(_)=>(U,Y)=>$1.get(U)||S1(U,Y,_);var x1=b(v(!1)),w1=b(v(!0)),u=(_,{s:U,t:Y,v:z})=>{if(_.a!==Y){let{b:I,c:X}=(U?w1:x1)(Y,z);_.a=Y,_.b=I,_.c=X}for(let{c:I}=_,X=0;X<I.length;X++){let O=z[X],B=I[X];switch(B.u){case M:B.v=M(B.t,y1(B.c,O),B.v);break;case G:let J=O instanceof A?u(B.c||(B.c=D()),O):(B.c=null,O);if(J!==B.v)B.v=G(B,J);break;default:if(O!==B.v)B.v=B.u(B.t,O,B.n,B.v);break}}return _.b},y1=(_,U)=>{let Y=0,{length:z}=U;if(z<_.length)_.splice(z);for(;Y<z;Y++){let I=U[Y];if(I instanceof A)U[Y]=u(_[Y]||(_[Y]=D()),I);else _[Y]=null}return U};class A{constructor(_,U,Y){this.s=_,this.t=U,this.v=Y}toDOM(_=D()){return u(_,this)}}var Z1=new WeakMap,d=(_,U)=>{let Y=Z1.get(_)||R(Z1,_,D()),{b:z}=Y;if(z!==(typeof U==="function"?U():U).toDOM(Y))_.replaceChildren(Y.b.valueOf());return _};/*! (c) Andrea Giammarchi - MIT */var K1=(_)=>(U,...Y)=>new A(_,U,Y),q1=K1(!1),C_=K1(!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: inline;
|
|
52
|
+
font-family: var(--kb-font-family-sans);
|
|
53
|
+
font-size: inherit;
|
|
54
|
+
}
|
|
55
|
+
a {
|
|
56
|
+
color: var(--kb-color-accent-default);
|
|
57
|
+
text-decoration: underline;
|
|
58
|
+
text-underline-offset: 0.15em;
|
|
59
|
+
border-radius: var(--kb-radius-sm);
|
|
60
|
+
cursor: pointer;
|
|
61
|
+
}
|
|
62
|
+
a.muted {
|
|
63
|
+
color: var(--kb-color-fg-muted);
|
|
64
|
+
}
|
|
65
|
+
a.accent {
|
|
66
|
+
color: var(--kb-color-accent-default);
|
|
67
|
+
font-weight: var(--kb-font-weight-medium);
|
|
68
|
+
}
|
|
69
|
+
a.default {
|
|
70
|
+
color: var(--kb-color-accent-default);
|
|
71
|
+
}
|
|
72
|
+
a:hover {
|
|
73
|
+
color: var(--kb-color-accent-hover);
|
|
74
|
+
}
|
|
75
|
+
a:focus-visible {
|
|
76
|
+
outline: none;
|
|
77
|
+
box-shadow: var(--kb-focus-ring);
|
|
78
|
+
}
|
|
79
|
+
a[aria-disabled="true"] {
|
|
80
|
+
opacity: 0.55;
|
|
81
|
+
cursor: not-allowed;
|
|
82
|
+
pointer-events: none;
|
|
83
|
+
}
|
|
84
|
+
`);class D1 extends HTMLElement{static get observedAttributes(){return["href","target","rel","tone","disabled"]}static get propTypes(){return{href:"String",target:"String",rel:"String",tone:"String",disabled:"Boolean"}}get href(){return this._props.href}set href(_){this._assignProp("href",_),this.update()}get target(){return this._props.target}set target(_){this._assignProp("target",_),this.update()}get rel(){return this._props.rel}set rel(_){this._assignProp("rel",_),this.update()}get tone(){return this._props.tone}set tone(_){this._assignProp("tone",_),this.update()}get disabled(){return this._props.disabled}set disabled(_){this._assignProp("disabled",_),this.update()}constructor(){super();this.attachShadow({mode:"open",delegatesFocus:!0}),this.shadowRoot.adoptedStyleSheets=[G1],this._defaults={href:"#",target:"",rel:"",tone:"default",disabled:!1},this._state={},this._props={...this._defaults},this._eventHandlers={},this._eventCleanup=[],this._renderFn=function({props:_,html:U}){let Y=_.tone==="muted"||_.tone==="accent"?_.tone:"default",z=typeof _.target==="string"&&_.target.length>0?_.target:"",I=z||null,X=z.toLowerCase()==="_blank",O=(typeof _.rel==="string"&&_.rel.length>0?_.rel:null)??(X?"noopener noreferrer":null),B="#";if(_.disabled)B=null;else if(typeof _.href==="string"){let J=_.href.trim(),$="";for(let Q=0;Q<J.length;Q++)if(J.charCodeAt(Q)>32)$+=J[Q];let W=$.toLowerCase();B=W.startsWith("javascript:")||W.startsWith("data:")||W.startsWith("vbscript:")?"#":J}return U`
|
|
85
|
+
<a
|
|
86
|
+
part="link-root"
|
|
87
|
+
class=${Y}
|
|
88
|
+
href=${B}
|
|
89
|
+
target=${I}
|
|
90
|
+
rel=${O}
|
|
91
|
+
aria-disabled=${_.disabled?"true":"false"}
|
|
92
|
+
tabindex=${_.disabled?-1:null}
|
|
93
|
+
>
|
|
94
|
+
<slot></slot>
|
|
95
|
+
</a>
|
|
96
|
+
`},this._reflecting=!1}connectedCallback(){this.update()}disconnectedCallback(){this.cleanupEvents()}attributeChangedCallback(_,U,Y){if(this._reflecting)return;if(U===Y)return;let z=this.constructor.propTypes[_],I=Y;if(Y===null||Y===void 0)I=this._defaults[_];else if(z==="Number"&&Y!=="")I=Number(Y);else if(z==="Boolean")I=Y!==null&&Y!=="false";this._props[_]=I,this.update()}_assignProp(_,U){let Y=this.constructor.propTypes[_],z=U;if(Y==="Boolean")z=U===!0||U==="";else if(Y==="Number"&&U!==null&&U!==void 0&&U!=="")z=Number(U);this._props[_]=z;let I=z===null||z===void 0||typeof z==="string"||typeof z==="number"||typeof z==="boolean";this._reflecting=!0;try{if(Y==="Boolean")if(z)this.setAttribute(_,"");else this.removeAttribute(_);else if(!I)this.removeAttribute(_);else if(z===null||z===void 0)this.removeAttribute(_);else this.setAttribute(_,String(z))}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 Y=this.constructor.propTypes||{};for(let z of Object.keys(U.props)){if(!Object.prototype.hasOwnProperty.call(Y,z))continue;this._assignProp(z,U.props[z])}}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(" "),Y=U[0],z=U.slice(1).join(" ");(z?this.shadowRoot.querySelectorAll(z):[this]).forEach((X)=>{let O=(B)=>{this._eventHandlers[_](B,this._handlerCtx())};X.addEventListener(Y,O),this._eventCleanup.push(()=>X.removeEventListener(Y,O))})})}}if(!customElements.get("kitbash-link"))customElements.define("kitbash-link",D1);export{D1 as KitbashLink};
|
|
@@ -0,0 +1,272 @@
|
|
|
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: inline;\n font-family: var(--kb-font-family-sans);\n font-size: inherit;\n }\n a {\n color: var(--kb-color-accent-default);\n text-decoration: underline;\n text-underline-offset: 0.15em;\n border-radius: var(--kb-radius-sm);\n cursor: pointer;\n }\n a.muted {\n color: var(--kb-color-fg-muted);\n }\n a.accent {\n color: var(--kb-color-accent-default);\n font-weight: var(--kb-font-weight-medium);\n }\n a.default {\n color: var(--kb-color-accent-default);\n }\n a:hover {\n color: var(--kb-color-accent-hover);\n }\n a:focus-visible {\n outline: none;\n box-shadow: var(--kb-focus-ring);\n }\n a[aria-disabled=\"true\"] {\n opacity: 0.55;\n cursor: not-allowed;\n pointer-events: none;\n }\n ");
|
|
6
|
+
|
|
7
|
+
export class KitbashLink extends HTMLElement {
|
|
8
|
+
|
|
9
|
+
static get observedAttributes() {
|
|
10
|
+
return ["href","target","rel","tone","disabled"];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
static get propTypes() {
|
|
14
|
+
return {"href":"String","target":"String","rel":"String","tone":"String","disabled":"Boolean"};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
get href() {
|
|
19
|
+
return this._props['href'];
|
|
20
|
+
}
|
|
21
|
+
set href(val) {
|
|
22
|
+
// External / framework property writes: re-render, do not emit kitbash-change
|
|
23
|
+
// (caller already knows the value they set).
|
|
24
|
+
this._assignProp('href', val);
|
|
25
|
+
this.update();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get target() {
|
|
29
|
+
return this._props['target'];
|
|
30
|
+
}
|
|
31
|
+
set target(val) {
|
|
32
|
+
// External / framework property writes: re-render, do not emit kitbash-change
|
|
33
|
+
// (caller already knows the value they set).
|
|
34
|
+
this._assignProp('target', val);
|
|
35
|
+
this.update();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
get rel() {
|
|
39
|
+
return this._props['rel'];
|
|
40
|
+
}
|
|
41
|
+
set rel(val) {
|
|
42
|
+
// External / framework property writes: re-render, do not emit kitbash-change
|
|
43
|
+
// (caller already knows the value they set).
|
|
44
|
+
this._assignProp('rel', val);
|
|
45
|
+
this.update();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
get tone() {
|
|
49
|
+
return this._props['tone'];
|
|
50
|
+
}
|
|
51
|
+
set tone(val) {
|
|
52
|
+
// External / framework property writes: re-render, do not emit kitbash-change
|
|
53
|
+
// (caller already knows the value they set).
|
|
54
|
+
this._assignProp('tone', val);
|
|
55
|
+
this.update();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
get disabled() {
|
|
59
|
+
return this._props['disabled'];
|
|
60
|
+
}
|
|
61
|
+
set disabled(val) {
|
|
62
|
+
// External / framework property writes: re-render, do not emit kitbash-change
|
|
63
|
+
// (caller already knows the value they set).
|
|
64
|
+
this._assignProp('disabled', val);
|
|
65
|
+
this.update();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
constructor() {
|
|
70
|
+
super();
|
|
71
|
+
this.attachShadow({ mode: 'open', delegatesFocus: true });
|
|
72
|
+
|
|
73
|
+
this.shadowRoot.adoptedStyleSheets = [sheet];
|
|
74
|
+
|
|
75
|
+
this._defaults = {"href":"#","target":"","rel":"","tone":"default","disabled":false};
|
|
76
|
+
|
|
77
|
+
this._state = {};
|
|
78
|
+
this._props = { ...this._defaults };
|
|
79
|
+
this._eventHandlers = {};
|
|
80
|
+
this._eventCleanup = [];
|
|
81
|
+
this._renderFn = function({ props, html }) {
|
|
82
|
+
const tone = props.tone === "muted" || props.tone === "accent" ? props.tone : "default", targetRaw = typeof props.target === "string" && props.target.length > 0 ? props.target : "", target = targetRaw || null, isBlank = targetRaw.toLowerCase() === "_blank", rel = (typeof props.rel === "string" && props.rel.length > 0 ? props.rel : null) ?? (isBlank ? "noopener noreferrer" : null);
|
|
83
|
+
let href = "#";
|
|
84
|
+
if (props.disabled)
|
|
85
|
+
href = null;
|
|
86
|
+
else if (typeof props.href === "string") {
|
|
87
|
+
const trimmed = props.href.trim();
|
|
88
|
+
let schemeProbe = "";
|
|
89
|
+
for (let i = 0;i < trimmed.length; i++)
|
|
90
|
+
if (trimmed.charCodeAt(i) > 32)
|
|
91
|
+
schemeProbe += trimmed[i];
|
|
92
|
+
const lower = schemeProbe.toLowerCase();
|
|
93
|
+
href = lower.startsWith("javascript:") || lower.startsWith("data:") || lower.startsWith("vbscript:") ? "#" : trimmed;
|
|
94
|
+
}
|
|
95
|
+
return html`
|
|
96
|
+
<a
|
|
97
|
+
part="link-root"
|
|
98
|
+
class=${tone}
|
|
99
|
+
href=${href}
|
|
100
|
+
target=${target}
|
|
101
|
+
rel=${rel}
|
|
102
|
+
aria-disabled=${props.disabled ? "true" : "false"}
|
|
103
|
+
tabindex=${props.disabled ? -1 : null}
|
|
104
|
+
>
|
|
105
|
+
<slot></slot>
|
|
106
|
+
</a>
|
|
107
|
+
`;
|
|
108
|
+
};
|
|
109
|
+
this._reflecting = false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
connectedCallback() {
|
|
113
|
+
// Initial attribute values are parsed by attributeChangedCallback before connectedCallback fires
|
|
114
|
+
this.update();
|
|
115
|
+
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
disconnectedCallback() {
|
|
119
|
+
this.cleanupEvents();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
123
|
+
// Skip when we caused the attribute write ourselves (avoids double update)
|
|
124
|
+
if (this._reflecting) return;
|
|
125
|
+
if (oldValue === newValue) return;
|
|
126
|
+
|
|
127
|
+
const type = this.constructor.propTypes[name];
|
|
128
|
+
|
|
129
|
+
let parsedValue = newValue;
|
|
130
|
+
if (newValue === null || newValue === undefined) {
|
|
131
|
+
parsedValue = this._defaults[name];
|
|
132
|
+
} else if (type === 'Number' && newValue !== '') {
|
|
133
|
+
// Match property coercion: empty string is not Number('') → 0
|
|
134
|
+
parsedValue = Number(newValue);
|
|
135
|
+
} else if (type === 'Boolean') {
|
|
136
|
+
parsedValue = newValue !== null && newValue !== 'false';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
this._props[name] = parsedValue;
|
|
140
|
+
|
|
141
|
+
this.update();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Coerce + reflect a single prop; no re-render (caller decides). */
|
|
145
|
+
_assignProp(key, val) {
|
|
146
|
+
const type = this.constructor.propTypes[key];
|
|
147
|
+
let next = val;
|
|
148
|
+
if (type === 'Boolean') {
|
|
149
|
+
next = val === true || val === '';
|
|
150
|
+
} else if (type === 'Number' && val !== null && val !== undefined && val !== '') {
|
|
151
|
+
next = Number(val);
|
|
152
|
+
}
|
|
153
|
+
this._props[key] = next;
|
|
154
|
+
|
|
155
|
+
// Only reflect primitives — objects/arrays stay as properties (no [object Object] attrs)
|
|
156
|
+
const isPrimitive =
|
|
157
|
+
next === null ||
|
|
158
|
+
next === undefined ||
|
|
159
|
+
typeof next === 'string' ||
|
|
160
|
+
typeof next === 'number' ||
|
|
161
|
+
typeof next === 'boolean';
|
|
162
|
+
|
|
163
|
+
this._reflecting = true;
|
|
164
|
+
try {
|
|
165
|
+
if (type === 'Boolean') {
|
|
166
|
+
if (next) this.setAttribute(key, '');
|
|
167
|
+
else this.removeAttribute(key);
|
|
168
|
+
} else if (!isPrimitive) {
|
|
169
|
+
// property-only; drop stale attribute if any
|
|
170
|
+
this.removeAttribute(key);
|
|
171
|
+
} else if (next === null || next === undefined) {
|
|
172
|
+
this.removeAttribute(key);
|
|
173
|
+
} else {
|
|
174
|
+
this.setAttribute(key, String(next));
|
|
175
|
+
}
|
|
176
|
+
} finally {
|
|
177
|
+
this._reflecting = false;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
_emitChange() {
|
|
183
|
+
// Shallow-copy both bags so listeners cannot mutate internal state/props via event.detail
|
|
184
|
+
this.dispatchEvent(new CustomEvent('kitbash-change', {
|
|
185
|
+
detail: {
|
|
186
|
+
state: { ...this._state },
|
|
187
|
+
props: { ...this._props },
|
|
188
|
+
},
|
|
189
|
+
bubbles: true,
|
|
190
|
+
composed: true
|
|
191
|
+
}));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Batch props and/or state: one uhtml update, one kitbash-change.
|
|
196
|
+
* Prefer this in event handlers (e.g. controlled inputs).
|
|
197
|
+
*/
|
|
198
|
+
commit(patch) {
|
|
199
|
+
const p = patch || {};
|
|
200
|
+
if (p.props) {
|
|
201
|
+
const allowed = this.constructor.propTypes || {};
|
|
202
|
+
for (const key of Object.keys(p.props)) {
|
|
203
|
+
// Only declared props — ignore typos / stray keys
|
|
204
|
+
if (!Object.prototype.hasOwnProperty.call(allowed, key)) continue;
|
|
205
|
+
this._assignProp(key, p.props[key]);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (p.state) {
|
|
209
|
+
this._state = { ...this._state, ...p.state };
|
|
210
|
+
}
|
|
211
|
+
this.update();
|
|
212
|
+
this._emitChange();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
setState(newState) {
|
|
216
|
+
this.commit({ state: newState });
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
setProps(nextProps) {
|
|
220
|
+
this.commit({ props: nextProps });
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
_handlerCtx() {
|
|
224
|
+
// Snapshots so handlers/render cannot mutate internal bags by accident
|
|
225
|
+
return {
|
|
226
|
+
props: { ...this._props },
|
|
227
|
+
state: { ...this._state },
|
|
228
|
+
setState: this.setState.bind(this),
|
|
229
|
+
setProps: this.setProps.bind(this),
|
|
230
|
+
commit: this.commit.bind(this),
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
cleanupEvents() {
|
|
235
|
+
this._eventCleanup.forEach(cleanup => cleanup());
|
|
236
|
+
this._eventCleanup = [];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
update() {
|
|
240
|
+
// Run the uhtml DOM diffing render cycle
|
|
241
|
+
render(this.shadowRoot, this._renderFn({
|
|
242
|
+
...this._handlerCtx(),
|
|
243
|
+
html
|
|
244
|
+
}));
|
|
245
|
+
this.cleanupEvents();
|
|
246
|
+
this.bindEvents();
|
|
247
|
+
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
bindEvents() {
|
|
253
|
+
Object.keys(this._eventHandlers).forEach(eventSelector => {
|
|
254
|
+
const parts = eventSelector.split(' ');
|
|
255
|
+
const eventName = parts[0];
|
|
256
|
+
const selector = parts.slice(1).join(' ');
|
|
257
|
+
|
|
258
|
+
const targets = selector ? this.shadowRoot.querySelectorAll(selector) : [this];
|
|
259
|
+
targets.forEach(target => {
|
|
260
|
+
const handler = (e) => {
|
|
261
|
+
this._eventHandlers[eventSelector](e, this._handlerCtx());
|
|
262
|
+
};
|
|
263
|
+
target.addEventListener(eventName, handler);
|
|
264
|
+
this._eventCleanup.push(() => target.removeEventListener(eventName, handler));
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (!customElements.get('kitbash-link')) {
|
|
271
|
+
customElements.define('kitbash-link', KitbashLink);
|
|
272
|
+
}
|