@incursa/ui-kit 1.0.1 → 1.4.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 (45) hide show
  1. package/AI-AGENT-INSTRUCTIONS.md +49 -28
  2. package/LLMS.txt +58 -42
  3. package/README.md +181 -68
  4. package/dist/inc-design-language.css +655 -251
  5. package/dist/inc-design-language.css.map +1 -1
  6. package/dist/inc-design-language.js +520 -0
  7. package/dist/inc-design-language.min.css +1 -1
  8. package/dist/inc-design-language.min.css.map +1 -1
  9. package/dist/web-components/README.md +92 -0
  10. package/dist/web-components/RUNTIME-NOTES.md +40 -0
  11. package/dist/web-components/base-element.js +193 -0
  12. package/dist/web-components/components/feedback.js +1074 -0
  13. package/dist/web-components/components/forms.js +979 -0
  14. package/dist/web-components/components/layout.js +408 -0
  15. package/dist/web-components/components/navigation.js +854 -0
  16. package/dist/web-components/components/overlays.js +634 -0
  17. package/dist/web-components/controllers/focus.js +101 -0
  18. package/dist/web-components/controllers/overlay.js +128 -0
  19. package/dist/web-components/controllers/selection.js +145 -0
  20. package/dist/web-components/controllers/theme.js +173 -0
  21. package/dist/web-components/index.js +887 -0
  22. package/dist/web-components/package.json +3 -0
  23. package/dist/web-components/registry.js +74 -0
  24. package/dist/web-components/shared.js +186 -0
  25. package/dist/web-components/style.css +6 -0
  26. package/package.json +11 -2
  27. package/src/inc-design-language.js +520 -0
  28. package/src/inc-design-language.scss +692 -258
  29. package/src/web-components/README.md +92 -0
  30. package/src/web-components/RUNTIME-NOTES.md +40 -0
  31. package/src/web-components/base-element.js +193 -0
  32. package/src/web-components/components/feedback.js +1074 -0
  33. package/src/web-components/components/forms.js +979 -0
  34. package/src/web-components/components/layout.js +408 -0
  35. package/src/web-components/components/navigation.js +854 -0
  36. package/src/web-components/components/overlays.js +634 -0
  37. package/src/web-components/controllers/focus.js +101 -0
  38. package/src/web-components/controllers/overlay.js +128 -0
  39. package/src/web-components/controllers/selection.js +145 -0
  40. package/src/web-components/controllers/theme.js +173 -0
  41. package/src/web-components/index.js +887 -0
  42. package/src/web-components/package.json +3 -0
  43. package/src/web-components/registry.js +74 -0
  44. package/src/web-components/shared.js +186 -0
  45. package/src/web-components/style.css +6 -0
@@ -0,0 +1,887 @@
1
+ const FALSE_TOKENS = new Set(["false", "0", "off", "no"]);
2
+ const THEME_MODES = ["light", "dark", "system"];
3
+
4
+ function toBoolean(value, fallback = false) {
5
+ if (value == null) return fallback;
6
+ return !FALSE_TOKENS.has(String(value).toLowerCase());
7
+ }
8
+
9
+ function byClass(host, className) {
10
+ return Array.from(host.children).find((node) => node.classList?.contains(className)) || null;
11
+ }
12
+
13
+ function addClass(node, className) {
14
+ if (node instanceof Element && className) node.classList.add(className);
15
+ }
16
+
17
+ function emit(host, type, detail = {}, options = {}) {
18
+ return host.dispatchEvent(new CustomEvent(type, {
19
+ detail,
20
+ bubbles: options.bubbles !== false,
21
+ composed: options.composed !== false,
22
+ cancelable: options.cancelable === true,
23
+ }));
24
+ }
25
+
26
+ function moveSlots(host, mapping) {
27
+ Object.entries(mapping).forEach(([slotName, className]) => {
28
+ Array.from(host.children)
29
+ .filter((node) => node.getAttribute("slot") === slotName)
30
+ .forEach((node) => {
31
+ node.removeAttribute("slot");
32
+ addClass(node, className);
33
+ });
34
+ });
35
+ }
36
+
37
+ function ensureNode(parent, selector, build) {
38
+ const existing = parent.querySelector(`:scope > ${selector}`);
39
+ if (existing) return existing;
40
+ const node = build();
41
+ parent.append(node);
42
+ return node;
43
+ }
44
+
45
+ class IncElement extends HTMLElement {
46
+ emit(type, detail = {}, options = {}) {
47
+ return emit(this, type, detail, options);
48
+ }
49
+ }
50
+
51
+ class IncAppShellElement extends IncElement {
52
+ connectedCallback() {
53
+ addClass(this, "inc-app-shell");
54
+ moveSlots(this, {
55
+ header: "inc-app-shell__header",
56
+ body: "inc-app-shell__body",
57
+ sidebar: "inc-app-shell__sidebar",
58
+ main: "inc-app-shell__main",
59
+ content: "inc-app-shell__content",
60
+ footer: "inc-app-shell__footer",
61
+ });
62
+ }
63
+ }
64
+
65
+ class IncPageElement extends IncElement {
66
+ connectedCallback() {
67
+ addClass(this, "inc-page");
68
+ moveSlots(this, {
69
+ breadcrumbs: "inc-page__breadcrumbs",
70
+ header: "inc-page__header",
71
+ body: "inc-page__body",
72
+ aside: "inc-page__aside",
73
+ footer: "inc-page__footer",
74
+ });
75
+ }
76
+ }
77
+
78
+ class IncPageHeaderElement extends IncElement {
79
+ connectedCallback() {
80
+ addClass(this, "inc-page-header");
81
+ moveSlots(this, { title: "inc-page-header__title", body: "inc-page-header__body", actions: "inc-page-header__actions" });
82
+ }
83
+ }
84
+
85
+ class IncSectionElement extends IncElement {
86
+ connectedCallback() {
87
+ addClass(this, "inc-section-container");
88
+ addClass(this, "inc-section");
89
+ moveSlots(this, {
90
+ header: "inc-section__header",
91
+ body: "inc-section__body",
92
+ footer: "inc-section__footer",
93
+ actions: "inc-section__actions",
94
+ });
95
+ }
96
+ }
97
+
98
+ class IncCardElement extends IncElement {
99
+ connectedCallback() {
100
+ addClass(this, "inc-card");
101
+ moveSlots(this, { header: "inc-card__header", body: "inc-card__body", footer: "inc-card__footer" });
102
+ }
103
+ }
104
+
105
+ class IncSummaryOverviewElement extends IncElement {
106
+ static get observedAttributes() { return ["columns"]; }
107
+ connectedCallback() {
108
+ addClass(this, "inc-summary-overview");
109
+ this.syncColumns();
110
+ }
111
+ attributeChangedCallback() { this.syncColumns(); }
112
+ syncColumns() {
113
+ this.classList.remove("inc-summary-overview--2-col", "inc-summary-overview--3-col", "inc-summary-overview--4-col");
114
+ const columns = Number.parseInt(this.getAttribute("columns") || "", 10);
115
+ if ([2, 3, 4].includes(columns)) this.classList.add(`inc-summary-overview--${columns}-col`);
116
+ }
117
+ }
118
+
119
+ class IncSummaryBlockElement extends IncElement {
120
+ connectedCallback() {
121
+ addClass(this, "inc-summary-block");
122
+ moveSlots(this, {
123
+ header: "inc-summary-block__header",
124
+ body: "inc-summary-block__body",
125
+ value: "inc-summary-block__value",
126
+ status: "inc-summary-block__status",
127
+ actions: "inc-summary-block__actions",
128
+ footer: "inc-summary-block__footer",
129
+ });
130
+ }
131
+ }
132
+
133
+ class IncFooterBarElement extends IncElement {
134
+ connectedCallback() {
135
+ addClass(this, "inc-footer-bar");
136
+ moveSlots(this, { menu: "inc-footer-bar__menu", meta: "inc-footer-bar__meta" });
137
+ }
138
+ }
139
+
140
+ class IncNavbarElement extends IncElement {
141
+ static get observedAttributes() { return ["open", "app", "breakpoint", "variant"]; }
142
+ connectedCallback() {
143
+ addClass(this, "inc-navbar");
144
+ this.syncSlots();
145
+ this.syncState();
146
+ this.addEventListener("click", this.onClick);
147
+ }
148
+ disconnectedCallback() { this.removeEventListener("click", this.onClick); }
149
+ attributeChangedCallback() { this.syncState(); }
150
+ onClick = (event) => {
151
+ const trigger = event.target.closest("[data-inc-navbar-toggle],[data-inc-action='navbar-toggle']");
152
+ if (!trigger || !this.contains(trigger)) return;
153
+ event.preventDefault();
154
+ this.toggleAttribute("open");
155
+ this.syncState();
156
+ };
157
+ syncSlots() {
158
+ moveSlots(this, {
159
+ brand: "inc-navbar__brand",
160
+ nav: "inc-navbar__nav",
161
+ utilities: "inc-navbar__utilities",
162
+ collapse: "inc-navbar__collapse",
163
+ toggle: "inc-navbar__toggler",
164
+ });
165
+ }
166
+ syncState() {
167
+ this.classList.toggle("inc-navbar--app", this.hasAttribute("app"));
168
+ this.setAttribute("aria-expanded", this.hasAttribute("open") ? "true" : "false");
169
+ Array.from(this.classList).filter((token) => token.startsWith("inc-navbar--expand-")).forEach((token) => this.classList.remove(token));
170
+ const bp = (this.getAttribute("breakpoint") || "").toLowerCase();
171
+ if (bp) this.classList.add(`inc-navbar--expand-${bp}`);
172
+ }
173
+ }
174
+
175
+ function findPanel(host, tab, index) {
176
+ const idTarget = tab.getAttribute("aria-controls") || tab.getAttribute("data-inc-target");
177
+ if (idTarget) {
178
+ const id = idTarget.startsWith("#") ? idTarget.slice(1) : idTarget;
179
+ const direct = host.querySelector(`#${id}`);
180
+ if (direct instanceof HTMLElement) return direct;
181
+ }
182
+
183
+ return host._panels[index] || null;
184
+ }
185
+
186
+ class IncTabsElement extends IncElement {
187
+ static get observedAttributes() { return ["selected", "variant"]; }
188
+ connectedCallback() {
189
+ this.ensureLayout();
190
+ this.bindEvents();
191
+ this.syncVariant();
192
+ this.initTabs();
193
+ this.syncSelected();
194
+ }
195
+ attributeChangedCallback(name) {
196
+ if (!this.isConnected) return;
197
+ if (name === "variant") {
198
+ this.syncVariant();
199
+ return;
200
+ }
201
+ if (this._reflectingSelected) return;
202
+ this.syncSelected();
203
+ }
204
+ ensureLayout() {
205
+ const nav = ensureNode(this, ".inc-tabs-nav", () => {
206
+ const node = document.createElement("ul");
207
+ node.className = "inc-tabs-nav";
208
+ this.prepend(node);
209
+ return node;
210
+ });
211
+
212
+ const content = ensureNode(this, ".inc-tab-content", () => {
213
+ const node = document.createElement("div");
214
+ node.className = "inc-tab-content";
215
+ this.append(node);
216
+ return node;
217
+ });
218
+
219
+ Array.from(this.children).filter((node) => node.getAttribute("slot") === "tab").forEach((tab) => {
220
+ tab.removeAttribute("slot");
221
+ addClass(tab, "inc-tab");
222
+ const li = document.createElement("li");
223
+ li.append(tab);
224
+ nav.append(li);
225
+ });
226
+
227
+ Array.from(this.children).filter((node) => node.getAttribute("slot") === "panel").forEach((panel) => {
228
+ panel.removeAttribute("slot");
229
+ addClass(panel, "inc-tab-pane");
230
+ content.append(panel);
231
+ });
232
+
233
+ this._tabs = Array.from(nav.querySelectorAll(".inc-tab, [role='tab']"));
234
+ this._panels = Array.from(content.querySelectorAll(".inc-tab-pane, [role='tabpanel']"));
235
+ }
236
+ initTabs() {
237
+ this._tabs.forEach((tab, index) => {
238
+ tab.setAttribute("role", "tab");
239
+ if (!tab.id) tab.id = `${this.id || this.localName}-tab-${index + 1}`;
240
+ tab.tabIndex = index === 0 ? 0 : -1;
241
+ const panel = findPanel(this, tab, index);
242
+ if (!panel) return;
243
+ panel.setAttribute("role", "tabpanel");
244
+ panel.setAttribute("aria-labelledby", tab.id);
245
+ if (!panel.id) panel.id = `${tab.id}-panel`;
246
+ tab.setAttribute("aria-controls", panel.id);
247
+ });
248
+
249
+ const active = this._tabs.find((tab) => tab.classList.contains("active")) || this._tabs[0];
250
+ if (active) this.activate(active, { emitEvents: false, focus: false });
251
+ }
252
+ bindEvents() {
253
+ if (this._bound) return;
254
+ this._bound = true;
255
+ this.addEventListener("click", (event) => {
256
+ const tab = event.target.closest(".inc-tab,[role='tab']");
257
+ if (!tab || !this.contains(tab)) return;
258
+ event.preventDefault();
259
+ this.activate(tab, { emitEvents: true, focus: true });
260
+ });
261
+ this.addEventListener("keydown", (event) => {
262
+ const tab = event.target.closest(".inc-tab,[role='tab']");
263
+ if (!tab || !this.contains(tab)) return;
264
+ const current = this._tabs.indexOf(tab);
265
+ if (current < 0) return;
266
+ if (event.key === "ArrowRight" || event.key === "ArrowDown") {
267
+ event.preventDefault();
268
+ this.activate(this._tabs[(current + 1) % this._tabs.length], { emitEvents: true, focus: true });
269
+ } else if (event.key === "ArrowLeft" || event.key === "ArrowUp") {
270
+ event.preventDefault();
271
+ this.activate(this._tabs[(current - 1 + this._tabs.length) % this._tabs.length], { emitEvents: true, focus: true });
272
+ }
273
+ });
274
+ }
275
+ activate(tab, options = {}) {
276
+ if (!(tab instanceof HTMLElement)) return;
277
+ const previous = this._tabs.find((item) => item.getAttribute("aria-selected") === "true") || null;
278
+ const nextSelected = tab.id || "";
279
+ this._tabs.forEach((item, index) => {
280
+ const active = item === tab;
281
+ item.classList.toggle("active", active);
282
+ item.setAttribute("aria-selected", active ? "true" : "false");
283
+ item.tabIndex = active ? 0 : -1;
284
+ const panel = findPanel(this, item, index);
285
+ if (panel) {
286
+ panel.hidden = !active;
287
+ panel.classList.toggle("active", active);
288
+ panel.classList.toggle("show", active);
289
+ }
290
+ });
291
+ if (this.getAttribute("selected") !== nextSelected) {
292
+ this._reflectingSelected = true;
293
+ try {
294
+ this.setAttribute("selected", nextSelected);
295
+ } finally {
296
+ this._reflectingSelected = false;
297
+ }
298
+ }
299
+ if (options.focus) tab.focus();
300
+ if (options.emitEvents !== false) {
301
+ this.emit("select", { previous: previous?.id || null, selected: tab.id, tab });
302
+ this.emit("change", { previous: previous?.id || null, selected: tab.id, tab });
303
+ }
304
+ }
305
+ syncSelected() {
306
+ const selected = this.getAttribute("selected");
307
+ if (!selected || !this._tabs?.length) return;
308
+ const next = this._tabs.find((tab) => tab.id === selected) || null;
309
+ const active = this._tabs.find((tab) => tab.getAttribute("aria-selected") === "true") || null;
310
+ if (!next || next === active) return;
311
+ this.activate(next, { emitEvents: false, focus: false });
312
+ }
313
+ syncVariant() {
314
+ const variant = (this.getAttribute("variant") || "line").toLowerCase();
315
+ this.classList.remove("inc-tabs-line", "inc-tabs-folder");
316
+ this.classList.add(variant === "folder" ? "inc-tabs-folder" : "inc-tabs-line");
317
+ }
318
+ }
319
+
320
+ function firstMenuItem(menuRoot) {
321
+ return menuRoot?.querySelector("[role='menuitem'],button,a,[tabindex]:not([tabindex='-1'])") || null;
322
+ }
323
+
324
+ class IncUserMenuElement extends IncElement {
325
+ static get observedAttributes() { return ["open", "variant", "label"]; }
326
+ connectedCallback() {
327
+ this.ensureLayout();
328
+ this.bindEvents();
329
+ this.syncState();
330
+ }
331
+ attributeChangedCallback() { this.syncState(); }
332
+ open() { this.setAttribute("open", ""); }
333
+ close() { this.removeAttribute("open"); }
334
+ ensureLayout() {
335
+ const details = ensureNode(this, "details.inc-native-menu", () => {
336
+ const node = document.createElement("details");
337
+ node.className = "inc-native-menu";
338
+ node.innerHTML = `<summary class="inc-native-menu__summary"></summary><div class="inc-native-menu__panel" role="menu"></div>`;
339
+ this.append(node);
340
+ return node;
341
+ });
342
+ this._details = details;
343
+ this._summary = details.querySelector(":scope > .inc-native-menu__summary");
344
+ this._panel = details.querySelector(":scope > .inc-native-menu__panel");
345
+ Array.from(this.children).filter((node) => node.getAttribute("slot") === "trigger").forEach((node) => {
346
+ node.removeAttribute("slot");
347
+ this._summary.replaceChildren(node);
348
+ });
349
+ Array.from(this.children).filter((node) => node.getAttribute("slot") === "menu").forEach((node) => {
350
+ node.removeAttribute("slot");
351
+ this._panel.replaceChildren(node);
352
+ });
353
+ }
354
+ bindEvents() {
355
+ if (this._bound) return;
356
+ this._bound = true;
357
+ this._details.addEventListener("toggle", () => {
358
+ this.toggleAttribute("open", this._details.open);
359
+ this.emit(this._details.open ? "open" : "close");
360
+ });
361
+ this.addEventListener("click", (event) => {
362
+ const action = event.target.closest("[role='menuitem'],button,a");
363
+ if (!action || !this._panel.contains(action)) return;
364
+ this.emit("select", {
365
+ value: action.getAttribute("value") || action.getAttribute("data-value") || action.textContent?.trim() || "",
366
+ text: action.textContent?.trim() || "",
367
+ item: action,
368
+ });
369
+ this.close();
370
+ this._summary?.focus();
371
+ });
372
+ this.addEventListener("keydown", (event) => {
373
+ if (event.key === "Escape" && this._details.open) {
374
+ event.preventDefault();
375
+ this.close();
376
+ this._summary?.focus();
377
+ return;
378
+ }
379
+ const trigger = event.target.closest("summary,.inc-native-menu__summary,[slot='trigger'],button");
380
+ if (!trigger || !this.contains(trigger)) return;
381
+ if (event.key === "Enter" || event.key === " " || event.key === "ArrowDown") {
382
+ event.preventDefault();
383
+ this.open();
384
+ requestAnimationFrame(() => firstMenuItem(this._panel)?.focus());
385
+ }
386
+ });
387
+ }
388
+ syncState() {
389
+ this._details.classList.toggle("inc-native-menu--navbar", (this.getAttribute("variant") || "").toLowerCase() === "navbar");
390
+ this._details.open = this.hasAttribute("open");
391
+ }
392
+ }
393
+
394
+ class IncFieldElement extends IncElement {
395
+ connectedCallback() {
396
+ addClass(this, "inc-form__group");
397
+ moveSlots(this, { label: "inc-form__label", hint: "inc-form__hint", error: "inc-form__feedback", control: "inc-form__control" });
398
+ }
399
+ }
400
+
401
+ class IncInputGroupElement extends IncElement {
402
+ connectedCallback() {
403
+ addClass(this, "inc-input-group");
404
+ moveSlots(this, { prefix: "inc-input-group__text", suffix: "inc-input-group__text", control: "inc-form__control" });
405
+ }
406
+ }
407
+
408
+ class IncChoiceGroupElement extends IncElement {
409
+ connectedCallback() {
410
+ addClass(this, "inc-form__fieldset");
411
+ let legend = byClass(this, "inc-form__legend");
412
+ let choices = byClass(this, "inc-form__choices");
413
+ if (!legend) {
414
+ legend = document.createElement("legend");
415
+ legend.className = "inc-form__legend";
416
+ this.prepend(legend);
417
+ }
418
+ if (!choices) {
419
+ choices = document.createElement("div");
420
+ choices.className = "inc-form__choices";
421
+ this.append(choices);
422
+ }
423
+ if (this.hasAttribute("legend")) legend.textContent = this.getAttribute("legend");
424
+ if (this.hasAttribute("inline")) choices.classList.add("inc-form__choices--inline");
425
+ Array.from(this.children).filter((node) => node.getAttribute("slot") === "item").forEach((node) => {
426
+ node.removeAttribute("slot");
427
+ addClass(node, "inc-form__check");
428
+ choices.append(node);
429
+ });
430
+ }
431
+ }
432
+
433
+ class IncReadonlyFieldElement extends IncElement {
434
+ connectedCallback() { addClass(this, "inc-readonly-field"); }
435
+ }
436
+
437
+ class IncValidationSummaryElement extends IncElement {
438
+ connectedCallback() {
439
+ addClass(this, "inc-form__error-summary");
440
+ let title = byClass(this, "inc-form__error-summary-title");
441
+ let list = byClass(this, "inc-form__error-summary-list");
442
+ if (!title) {
443
+ title = document.createElement("h3");
444
+ title.className = "inc-form__error-summary-title";
445
+ this.prepend(title);
446
+ }
447
+ if (!list) {
448
+ list = document.createElement("ul");
449
+ list.className = "inc-form__error-summary-list";
450
+ this.append(list);
451
+ }
452
+ Array.from(this.children).filter((node) => node.getAttribute("slot") === "item").forEach((node) => {
453
+ node.removeAttribute("slot");
454
+ if (node.tagName === "LI") list.append(node);
455
+ else {
456
+ const li = document.createElement("li");
457
+ li.append(node);
458
+ list.append(li);
459
+ }
460
+ });
461
+ if (this.hasAttribute("title")) title.textContent = this.getAttribute("title");
462
+ else if (this.hasAttribute("count")) title.textContent = `There are ${this.getAttribute("count")} issues to fix`;
463
+ }
464
+ }
465
+
466
+ class IncStatePanelElement extends IncElement {
467
+ static get observedAttributes() { return ["variant", "tone", "open"]; }
468
+ connectedCallback() {
469
+ addClass(this, "inc-state-panel");
470
+ moveSlots(this, { icon: "inc-state-panel__icon", title: "inc-state-panel__title", body: "inc-state-panel__body", actions: "inc-state-panel__actions" });
471
+ this.syncState();
472
+ }
473
+ attributeChangedCallback() { this.syncState(); }
474
+ syncState() {
475
+ Array.from(this.classList).filter((token) => token.startsWith("inc-state-panel--")).forEach((token) => this.classList.remove(token));
476
+ const variant = (this.getAttribute("variant") || this.getAttribute("tone") || "").toLowerCase();
477
+ if (variant) this.classList.add(`inc-state-panel--${variant}`);
478
+ const open = this.hasAttribute("open") ? toBoolean(this.getAttribute("open"), true) : true;
479
+ this.hidden = !open;
480
+ this.setAttribute("aria-hidden", open ? "false" : "true");
481
+ }
482
+ }
483
+
484
+ class IncLiveRegionElement extends IncElement {
485
+ static get observedAttributes() { return ["politeness", "atomic", "busy"]; }
486
+ connectedCallback() {
487
+ addClass(this, "inc-live-region");
488
+ this.syncAria();
489
+ }
490
+ attributeChangedCallback() { this.syncAria(); }
491
+ announce(message) {
492
+ let node = this.querySelector(":scope > [data-inc-live-region-message]");
493
+ if (!node) {
494
+ node = document.createElement("span");
495
+ node.dataset.incLiveRegionMessage = "true";
496
+ node.className = "inc-u-visually-hidden";
497
+ this.append(node);
498
+ }
499
+ node.textContent = "";
500
+ requestAnimationFrame(() => { node.textContent = message == null ? "" : String(message); });
501
+ }
502
+ syncAria() {
503
+ const mode = (this.getAttribute("politeness") || "polite").toLowerCase();
504
+ this.setAttribute("role", mode === "assertive" ? "alert" : "status");
505
+ this.setAttribute("aria-live", mode === "assertive" ? "assertive" : "polite");
506
+ this.setAttribute("aria-atomic", toBoolean(this.getAttribute("atomic"), true) ? "true" : "false");
507
+ this.setAttribute("aria-busy", this.hasAttribute("busy") && toBoolean(this.getAttribute("busy")) ? "true" : "false");
508
+ }
509
+ }
510
+
511
+ class IncAutoRefreshElement extends IncElement {
512
+ static get observedAttributes() { return ["paused"]; }
513
+ connectedCallback() {
514
+ addClass(this, "inc-auto-refresh");
515
+ this.ensureStructure();
516
+ this.bindEvents();
517
+ this.syncPaused();
518
+ }
519
+ attributeChangedCallback() { this.syncPaused(); }
520
+ ensureStructure() {
521
+ this._countdown = ensureNode(this, ".inc-auto-refresh__countdown", () => {
522
+ const node = document.createElement("span");
523
+ node.className = "inc-auto-refresh__countdown";
524
+ node.innerHTML = `<span class="inc-auto-refresh__label">${this.getAttribute("label") || "Refresh in"}</span><span class="inc-auto-refresh__value">0s</span>`;
525
+ return node;
526
+ });
527
+ this._status = ensureNode(this, ".inc-auto-refresh__status", () => {
528
+ const node = document.createElement("span");
529
+ node.className = "inc-auto-refresh__status";
530
+ node.innerHTML = `<span class="inc-auto-refresh__status-text">${this.getAttribute("loading-label") || "Refreshing"}</span>`;
531
+ return node;
532
+ });
533
+ this._toggle = ensureNode(this, ".inc-auto-refresh__toggle", () => {
534
+ const node = document.createElement("button");
535
+ node.type = "button";
536
+ node.className = "inc-btn inc-btn--secondary inc-btn--micro inc-auto-refresh__toggle";
537
+ node.innerHTML = `<span class="inc-auto-refresh__toggle-text">Pause</span>`;
538
+ return node;
539
+ });
540
+ this._toggleText = this._toggle.querySelector(".inc-auto-refresh__toggle-text");
541
+ }
542
+ bindEvents() {
543
+ if (this._bound) return;
544
+ this._bound = true;
545
+ this.addEventListener("click", (event) => {
546
+ const trigger = event.target.closest(".inc-auto-refresh__toggle,[data-inc-action='auto-refresh-toggle']");
547
+ if (!trigger || !this.contains(trigger)) return;
548
+ event.preventDefault();
549
+ this.toggleAttribute("paused");
550
+ this.syncPaused();
551
+ this.emit(this.hasAttribute("paused") ? "pause" : "resume");
552
+ });
553
+ }
554
+ syncPaused() {
555
+ const paused = this.hasAttribute("paused");
556
+ this.classList.toggle("is-paused", paused);
557
+ this.classList.remove("is-loading");
558
+ this.setAttribute("aria-busy", "false");
559
+ this._status.hidden = true;
560
+ this._countdown.hidden = false;
561
+ this._toggle.setAttribute("aria-pressed", paused ? "true" : "false");
562
+ this._toggle.setAttribute("aria-label", paused ? "Resume" : "Pause");
563
+ this._toggleText.textContent = paused ? "Resume" : "Pause";
564
+ }
565
+ }
566
+
567
+ const themeRuntime = {
568
+ initialized: false,
569
+ mode: "system",
570
+ resolved: "light",
571
+ key: "inc-theme-mode",
572
+ switchers: new Set(),
573
+ };
574
+
575
+ function resolveTheme(mode) {
576
+ if (mode !== "system") return mode;
577
+ return window.matchMedia?.("(prefers-color-scheme: dark)").matches ? "dark" : "light";
578
+ }
579
+
580
+ function persistTheme(mode) {
581
+ try {
582
+ if (mode === "system") window.localStorage.removeItem(themeRuntime.key);
583
+ else window.localStorage.setItem(themeRuntime.key, mode);
584
+ } catch {
585
+ // ignore storage restrictions
586
+ }
587
+ }
588
+
589
+ function applyTheme(mode, options = {}) {
590
+ const next = THEME_MODES.includes(mode) ? mode : "system";
591
+ const resolved = resolveTheme(next);
592
+ themeRuntime.mode = next;
593
+ themeRuntime.resolved = resolved;
594
+ const root = document.documentElement;
595
+ root.setAttribute("data-inc-theme-mode", next);
596
+ root.setAttribute("data-bs-theme", resolved);
597
+ root.style.colorScheme = resolved;
598
+ if (options.persist !== false) persistTheme(next);
599
+ themeRuntime.switchers.forEach((switcher) => switcher.syncFromTheme?.());
600
+ emit(root, "inc-theme-change", { mode: next, resolved });
601
+ }
602
+
603
+ function initTheme() {
604
+ if (themeRuntime.initialized) return;
605
+ themeRuntime.initialized = true;
606
+ let stored = null;
607
+ try {
608
+ stored = window.localStorage.getItem(themeRuntime.key);
609
+ } catch {
610
+ stored = null;
611
+ }
612
+ applyTheme(THEME_MODES.includes(stored) ? stored : (document.documentElement.getAttribute("data-inc-theme-mode") || "system"), { persist: false });
613
+ }
614
+
615
+ class IncThemeSwitcherElement extends IncElement {
616
+ static get observedAttributes() { return ["mode", "variant", "label", "storage-key"]; }
617
+ connectedCallback() {
618
+ if (this.hasAttribute("storage-key")) themeRuntime.key = this.getAttribute("storage-key");
619
+ initTheme();
620
+ this.ensureLayout();
621
+ this.bindEvents();
622
+ themeRuntime.switchers.add(this);
623
+ this.syncFromTheme();
624
+ }
625
+ disconnectedCallback() { themeRuntime.switchers.delete(this); }
626
+ attributeChangedCallback(name) {
627
+ if (name === "mode") {
628
+ applyTheme(this.getAttribute("mode"));
629
+ return;
630
+ }
631
+ if (name === "storage-key") themeRuntime.key = this.getAttribute("storage-key") || "inc-theme-mode";
632
+ if (!this.isConnected) return;
633
+ this.syncFromTheme();
634
+ }
635
+ getMode() { return themeRuntime.mode; }
636
+ getResolvedTheme() { return themeRuntime.resolved; }
637
+ setMode(mode) { applyTheme(mode); }
638
+ cycleMode() {
639
+ const index = THEME_MODES.indexOf(themeRuntime.mode);
640
+ applyTheme(THEME_MODES[(index + 1) % THEME_MODES.length]);
641
+ }
642
+ ensureLayout() {
643
+ const details = ensureNode(this, "details.inc-theme-switcher", () => {
644
+ const node = document.createElement("details");
645
+ node.className = "inc-native-menu inc-theme-switcher";
646
+ node.innerHTML = `<summary class="inc-native-menu__summary inc-theme-switcher__summary"><span class="inc-theme-switcher__meta"><span class="inc-theme-switcher__label">Theme</span><span class="inc-theme-switcher__status"></span></span></summary><div class="inc-native-menu__panel inc-theme-switcher__panel" role="menu"></div>`;
647
+ this.append(node);
648
+ return node;
649
+ });
650
+ this._details = details;
651
+ this._summary = details.querySelector(":scope > .inc-theme-switcher__summary");
652
+ this._status = details.querySelector(":scope .inc-theme-switcher__status");
653
+ this._label = details.querySelector(":scope .inc-theme-switcher__label");
654
+ this._panel = details.querySelector(":scope > .inc-theme-switcher__panel");
655
+ if (!this._panel.querySelector(".inc-theme-switcher__option")) {
656
+ THEME_MODES.forEach((mode) => {
657
+ const option = document.createElement("button");
658
+ option.type = "button";
659
+ option.className = "inc-theme-switcher__option";
660
+ option.setAttribute("role", "menuitemradio");
661
+ option.setAttribute("data-inc-theme-mode", mode);
662
+ option.innerHTML = `<span class="inc-theme-switcher__option-body"><span class="inc-theme-switcher__option-label">${mode[0].toUpperCase()}${mode.slice(1)}</span></span>`;
663
+ this._panel.append(option);
664
+ });
665
+ }
666
+ }
667
+ bindEvents() {
668
+ if (this._bound) return;
669
+ this._bound = true;
670
+ this.addEventListener("click", (event) => {
671
+ const option = event.target.closest("[data-inc-theme-mode]");
672
+ if (!option || !this.contains(option)) return;
673
+ event.preventDefault();
674
+ applyTheme(option.getAttribute("data-inc-theme-mode"));
675
+ this._details.open = false;
676
+ this._summary.focus();
677
+ });
678
+ this.addEventListener("keydown", (event) => {
679
+ if (event.key === "Escape" && this._details.open) {
680
+ event.preventDefault();
681
+ this._details.open = false;
682
+ this._summary.focus();
683
+ }
684
+ });
685
+ }
686
+ syncFromTheme() {
687
+ if (!this._details) return;
688
+ this._details.classList.toggle("inc-native-menu--navbar", (this.getAttribute("variant") || "").toLowerCase() === "navbar");
689
+ this._label.textContent = this.getAttribute("label") || "Theme";
690
+ this._status.textContent = themeRuntime.mode === "system"
691
+ ? `System (${themeRuntime.resolved[0].toUpperCase()}${themeRuntime.resolved.slice(1)})`
692
+ : `${themeRuntime.mode[0].toUpperCase()}${themeRuntime.mode.slice(1)}`;
693
+ this.querySelectorAll("[data-inc-theme-mode]").forEach((option) => {
694
+ const selected = option.getAttribute("data-inc-theme-mode") === themeRuntime.mode;
695
+ option.classList.toggle("is-selected", selected);
696
+ option.setAttribute("aria-checked", selected ? "true" : "false");
697
+ });
698
+ }
699
+ }
700
+
701
+ class IncDisclosureElement extends IncElement {
702
+ static get observedAttributes() { return ["open", "summary"]; }
703
+ connectedCallback() {
704
+ const details = ensureNode(this, "details.inc-disclosure", () => {
705
+ const node = document.createElement("details");
706
+ node.className = "inc-disclosure";
707
+ node.innerHTML = `<summary class="inc-disclosure__summary"></summary><div class="inc-disclosure__content"></div>`;
708
+ this.append(node);
709
+ return node;
710
+ });
711
+ this._details = details;
712
+ this._summary = details.querySelector(":scope > .inc-disclosure__summary");
713
+ this._content = details.querySelector(":scope > .inc-disclosure__content");
714
+ Array.from(this.children).filter((node) => node.getAttribute("slot") === "summary").forEach((node) => {
715
+ node.removeAttribute("slot");
716
+ this._summary.replaceChildren(node);
717
+ });
718
+ Array.from(this.children).filter((node) => node.getAttribute("slot") === "content").forEach((node) => {
719
+ node.removeAttribute("slot");
720
+ this._content.append(node);
721
+ });
722
+ this._details.addEventListener("toggle", () => {
723
+ this.toggleAttribute("open", this._details.open);
724
+ this.emit(this._details.open ? "open" : "close", { open: this._details.open });
725
+ this.emit("toggle", { open: this._details.open });
726
+ });
727
+ this.syncState();
728
+ }
729
+ attributeChangedCallback() { this.syncState(); }
730
+ syncState() {
731
+ if (this._summary && this.hasAttribute("summary") && !this._summary.textContent?.trim()) {
732
+ this._summary.textContent = this.getAttribute("summary");
733
+ }
734
+ if (this._details) this._details.open = this.hasAttribute("open");
735
+ }
736
+ }
737
+
738
+ class IncDialogBaseElement extends IncElement {
739
+ static get observedAttributes() { return ["open", "modal", "dismissible", "label"]; }
740
+ connectedCallback() {
741
+ this.ensureLayout(this.drawerMode === true);
742
+ this.bindEvents();
743
+ this.syncState();
744
+ }
745
+ attributeChangedCallback() { this.syncState(); }
746
+ show() { this.openDialog(false); }
747
+ showModal() { this.openDialog(true); }
748
+ close(returnValue = "") {
749
+ if (this._dialog?.open) this._dialog.close(returnValue);
750
+ this.removeAttribute("open");
751
+ }
752
+ ensureLayout(drawerMode) {
753
+ const dialog = ensureNode(this, "dialog.inc-native-dialog", () => {
754
+ const node = document.createElement("dialog");
755
+ node.className = "inc-native-dialog";
756
+ node.innerHTML = `<div class="inc-native-dialog__surface"><div class="inc-native-dialog__header"><div class="inc-native-dialog__titles"><h2 class="inc-native-dialog__title">${this.getAttribute("label") || "Dialog"}</h2></div><button type="button" class="inc-native-dialog__close" data-inc-dialog-close aria-label="Close dialog">×</button></div><div class="inc-native-dialog__body"></div><div class="inc-native-dialog__footer"></div></div>`;
757
+ this.append(node);
758
+ return node;
759
+ });
760
+ dialog.classList.toggle("inc-native-dialog--drawer", drawerMode);
761
+ this._dialog = dialog;
762
+ this._header = dialog.querySelector(".inc-native-dialog__header");
763
+ this._titles = dialog.querySelector(".inc-native-dialog__titles");
764
+ this._body = dialog.querySelector(".inc-native-dialog__body");
765
+ this._footer = dialog.querySelector(".inc-native-dialog__footer");
766
+ Array.from(this.children).filter((node) => node.getAttribute("slot") === "header" || node.getAttribute("slot") === "title").forEach((node) => {
767
+ node.removeAttribute("slot");
768
+ (this._titles || this._header).append(node);
769
+ });
770
+ Array.from(this.children).filter((node) => node.getAttribute("slot") === "body").forEach((node) => {
771
+ node.removeAttribute("slot");
772
+ this._body.append(node);
773
+ });
774
+ Array.from(this.children).filter((node) => node.getAttribute("slot") === "footer").forEach((node) => {
775
+ node.removeAttribute("slot");
776
+ this._footer.append(node);
777
+ });
778
+ }
779
+ bindEvents() {
780
+ if (this._bound) return;
781
+ this._bound = true;
782
+ this.addEventListener("click", (event) => {
783
+ const closeButton = event.target.closest("[data-inc-dialog-close]");
784
+ if (!closeButton || !this.contains(closeButton)) return;
785
+ event.preventDefault();
786
+ this.close();
787
+ });
788
+ this._dialog.addEventListener("close", () => {
789
+ this.removeAttribute("open");
790
+ this.emit("close", { returnValue: this._dialog.returnValue || "" });
791
+ if (this._returnFocus instanceof HTMLElement) this._returnFocus.focus();
792
+ });
793
+ this._dialog.addEventListener("cancel", (event) => {
794
+ this.emit("cancel");
795
+ if (!toBoolean(this.getAttribute("dismissible"), true)) event.preventDefault();
796
+ });
797
+ }
798
+ openDialog(forceModal) {
799
+ if (!(this._dialog instanceof HTMLDialogElement) || this._dialog.open) return;
800
+ this._returnFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;
801
+ const modal = forceModal || toBoolean(this.getAttribute("modal"), true);
802
+ if (modal && typeof this._dialog.showModal === "function") this._dialog.showModal();
803
+ else if (typeof this._dialog.show === "function") this._dialog.show();
804
+ const initialFocus = this._dialog.querySelector("[data-inc-initial-focus]");
805
+ if (initialFocus instanceof HTMLElement) initialFocus.focus();
806
+ this.setAttribute("open", "");
807
+ this.emit("open");
808
+ }
809
+ syncState() {
810
+ if (!(this._dialog instanceof HTMLDialogElement)) return;
811
+ const wantsOpen = this.hasAttribute("open");
812
+ if (wantsOpen && !this._dialog.open) this.openDialog(toBoolean(this.getAttribute("modal"), true));
813
+ else if (!wantsOpen && this._dialog.open) this._dialog.close();
814
+ }
815
+ }
816
+
817
+ class IncDialogElement extends IncDialogBaseElement {
818
+ get drawerMode() { return false; }
819
+ }
820
+
821
+ class IncDrawerElement extends IncDialogBaseElement {
822
+ get drawerMode() { return true; }
823
+ }
824
+
825
+ const ENTRIES = [
826
+ ["inc-app-shell", IncAppShellElement],
827
+ ["inc-page", IncPageElement],
828
+ ["inc-page-header", IncPageHeaderElement],
829
+ ["inc-section", IncSectionElement],
830
+ ["inc-card", IncCardElement],
831
+ ["inc-summary-overview", IncSummaryOverviewElement],
832
+ ["inc-summary-block", IncSummaryBlockElement],
833
+ ["inc-footer-bar", IncFooterBarElement],
834
+ ["inc-navbar", IncNavbarElement],
835
+ ["inc-tabs", IncTabsElement],
836
+ ["inc-user-menu", IncUserMenuElement],
837
+ ["inc-field", IncFieldElement],
838
+ ["inc-input-group", IncInputGroupElement],
839
+ ["inc-choice-group", IncChoiceGroupElement],
840
+ ["inc-readonly-field", IncReadonlyFieldElement],
841
+ ["inc-validation-summary", IncValidationSummaryElement],
842
+ ["inc-state-panel", IncStatePanelElement],
843
+ ["inc-live-region", IncLiveRegionElement],
844
+ ["inc-auto-refresh", IncAutoRefreshElement],
845
+ ["inc-theme-switcher", IncThemeSwitcherElement],
846
+ ["inc-disclosure", IncDisclosureElement],
847
+ ["inc-dialog", IncDialogElement],
848
+ ["inc-drawer", IncDrawerElement],
849
+ ];
850
+
851
+ function defineAll(options = {}) {
852
+ const registry = options.registry || globalThis.customElements;
853
+ if (!registry || typeof registry.define !== "function" || typeof registry.get !== "function") return [];
854
+
855
+ return ENTRIES.map(([name, ctor]) => {
856
+ const existing = registry.get(name);
857
+ if (existing) {
858
+ return {
859
+ name,
860
+ defined: existing === ctor,
861
+ reason: existing === ctor ? "already-defined" : "name-conflict",
862
+ constructor: existing,
863
+ };
864
+ }
865
+
866
+ registry.define(name, ctor);
867
+ return { name, defined: true, reason: "defined", constructor: ctor };
868
+ });
869
+ }
870
+
871
+ function registerIncWebComponents(options = {}) {
872
+ return defineAll(options);
873
+ }
874
+
875
+ if (typeof globalThis !== "undefined") {
876
+ globalThis.IncWebComponents = globalThis.IncWebComponents || {};
877
+ globalThis.IncWebComponents.defineAll = defineAll;
878
+ globalThis.IncWebComponents.registerIncWebComponents = registerIncWebComponents;
879
+ globalThis.IncWebComponents.components = new Map(ENTRIES);
880
+ }
881
+
882
+ defineAll();
883
+
884
+ export {
885
+ defineAll,
886
+ registerIncWebComponents,
887
+ };