@incursa/ui-kit 1.5.0 → 1.7.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 (80) hide show
  1. package/LLMS.txt +4 -4
  2. package/README.md +46 -6
  3. package/dist/inc-design-language.css +273 -56
  4. package/dist/inc-design-language.css.map +1 -1
  5. package/dist/inc-design-language.js +326 -1
  6. package/dist/inc-design-language.min.css +1 -1
  7. package/dist/inc-design-language.min.css.map +1 -1
  8. package/dist/mcp/ai/agent-instructions.json +21 -0
  9. package/dist/mcp/ai/llms-txt.json +21 -0
  10. package/dist/mcp/components/buttons.json +29 -0
  11. package/dist/mcp/components/cards.json +29 -0
  12. package/dist/mcp/components/filter-bars.json +28 -0
  13. package/dist/mcp/components/form-choices.json +29 -0
  14. package/dist/mcp/components/forms.json +29 -0
  15. package/dist/mcp/components/interaction.json +28 -0
  16. package/dist/mcp/components/layout.json +28 -0
  17. package/dist/mcp/components/metrics.json +28 -0
  18. package/dist/mcp/components/states.json +28 -0
  19. package/dist/mcp/components/status.json +28 -0
  20. package/dist/mcp/components/tables.json +32 -0
  21. package/dist/mcp/components/utilities.json +28 -0
  22. package/dist/mcp/examples/data-grid-advanced.json +22 -0
  23. package/dist/mcp/examples/demo.json +24 -0
  24. package/dist/mcp/examples/forms-and-validation.json +21 -0
  25. package/dist/mcp/examples/native-patterns.json +21 -0
  26. package/dist/mcp/examples/overlay-workflows.json +22 -0
  27. package/dist/mcp/examples/record-detail.json +21 -0
  28. package/dist/mcp/examples/reference.json +23 -0
  29. package/dist/mcp/examples/states.json +21 -0
  30. package/dist/mcp/examples/web-components.json +24 -0
  31. package/dist/mcp/examples/work-queue.json +21 -0
  32. package/dist/mcp/guides/allowed-web-component-families.json +19 -0
  33. package/dist/mcp/guides/choose-css-vs-scss-vs-js-vs-web-components.json +20 -0
  34. package/dist/mcp/guides/customization-order.json +20 -0
  35. package/dist/mcp/guides/decision-tree.json +28 -0
  36. package/dist/mcp/guides/guardrails.json +20 -0
  37. package/dist/mcp/guides/install.json +31 -0
  38. package/dist/mcp/guides/latest.json +25 -0
  39. package/dist/mcp/guides/overview.json +26 -0
  40. package/dist/mcp/guides/package-metadata.json +25 -0
  41. package/dist/mcp/guides/update.json +26 -0
  42. package/dist/mcp/guides/when-to-use-css-first.json +20 -0
  43. package/dist/mcp/install.json +36 -0
  44. package/dist/mcp/patterns/data-grid-advanced.json +22 -0
  45. package/dist/mcp/patterns/demo.json +24 -0
  46. package/dist/mcp/patterns/forms-and-validation.json +21 -0
  47. package/dist/mcp/patterns/native-patterns.json +21 -0
  48. package/dist/mcp/patterns/overlay-workflows.json +22 -0
  49. package/dist/mcp/patterns/record-detail.json +21 -0
  50. package/dist/mcp/patterns/reference.json +24 -0
  51. package/dist/mcp/patterns/states.json +21 -0
  52. package/dist/mcp/patterns/web-components.json +24 -0
  53. package/dist/mcp/patterns/work-queue.json +21 -0
  54. package/dist/mcp/resources.json +2100 -0
  55. package/dist/mcp/search-index.json +827 -0
  56. package/dist/mcp/specs/control-conventions.json +21 -0
  57. package/dist/mcp/specs/public-surface.json +21 -0
  58. package/dist/mcp/specs/requirements-index.json +21 -0
  59. package/dist/mcp/specs/verification-index.json +21 -0
  60. package/dist/mcp/update.json +24 -0
  61. package/dist/mcp/worker.mjs +60079 -0
  62. package/dist/mcp/worker.mjs.map +7 -0
  63. package/dist/web-components/README.md +10 -4
  64. package/dist/web-components/RUNTIME-NOTES.md +7 -2
  65. package/dist/web-components/components/actions.js +633 -0
  66. package/dist/web-components/components/collections.js +272 -0
  67. package/dist/web-components/components/dom-helpers.js +46 -0
  68. package/dist/web-components/components/feedback.js +181 -3
  69. package/dist/web-components/index.js +4425 -813
  70. package/package.json +19 -8
  71. package/src/inc-design-language.js +326 -1
  72. package/src/inc-design-language.scss +300 -56
  73. package/src/mcp/worker.ts +858 -0
  74. package/src/web-components/README.md +10 -4
  75. package/src/web-components/RUNTIME-NOTES.md +7 -2
  76. package/src/web-components/components/actions.js +633 -0
  77. package/src/web-components/components/collections.js +272 -0
  78. package/src/web-components/components/dom-helpers.js +46 -0
  79. package/src/web-components/components/feedback.js +181 -3
  80. package/src/web-components/index.js +53 -847
@@ -1,883 +1,89 @@
1
- const FALSE_TOKENS = new Set(["false", "0", "off", "no"]);
2
- const THEME_MODES = ["light", "dark", "system"];
1
+ import { defineCustomElement } from "./shared.js";
2
+ import { installRegistryNamespace } from "./registry.js";
3
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
- }
4
+ import "./components/layout.js";
5
+ import "./components/navigation.js";
6
+ import "./components/forms.js";
7
+ import "./components/feedback.js";
8
+ import "./components/actions.js";
9
+ import "./components/collections.js";
10
+ import "./components/overlays.js";
36
11
 
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
- }
12
+ const namespace = globalThis.IncWebComponents || (globalThis.IncWebComponents = {});
44
13
 
45
- class IncElement extends HTMLElement {
46
- emit(type, detail = {}, options = {}) {
47
- return emit(this, type, detail, options);
48
- }
49
- }
14
+ installRegistryNamespace();
50
15
 
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
- });
16
+ function addEntry(entryMap, tagName, constructor) {
17
+ if (typeof tagName !== "string" || !tagName || typeof constructor !== "function") {
18
+ return;
62
19
  }
63
- }
64
20
 
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
- });
21
+ if (!entryMap.has(tagName)) {
22
+ entryMap.set(tagName, constructor);
75
23
  }
76
24
  }
77
25
 
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" });
26
+ function addEntries(entryMap, entries) {
27
+ if (!Array.isArray(entries)) {
28
+ return;
82
29
  }
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
30
 
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";
31
+ entries.forEach(([tagName, constructor]) => {
32
+ addEntry(entryMap, tagName, constructor);
33
+ });
578
34
  }
579
35
 
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
- }
36
+ function getComponentEntries() {
37
+ const entryMap = new Map();
588
38
 
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
- }
39
+ addEntries(entryMap, namespace.layout?.layoutComponents);
602
40
 
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
- }
41
+ addEntry(entryMap, "inc-navbar", namespace.navigation?.IncNavbarElement);
42
+ addEntry(entryMap, "inc-tabs", namespace.navigation?.IncTabsElement);
43
+ addEntry(entryMap, "inc-user-menu", namespace.navigation?.IncUserMenuElement);
614
44
 
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
- }
45
+ addEntry(entryMap, "inc-field", namespace.forms?.components?.IncFieldElement);
46
+ addEntry(entryMap, "inc-input-group", namespace.forms?.components?.IncInputGroupElement);
47
+ addEntry(entryMap, "inc-choice-group", namespace.forms?.components?.IncChoiceGroupElement);
48
+ addEntry(entryMap, "inc-readonly-field", namespace.forms?.components?.IncReadonlyFieldElement);
49
+ addEntry(entryMap, "inc-validation-summary", namespace.forms?.components?.IncValidationSummaryElement);
700
50
 
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
- }
51
+ addEntries(entryMap, namespace.feedback?.feedbackDefinitions);
52
+ addEntries(entryMap, namespace.actions?.actionDefinitions);
53
+ addEntries(entryMap, namespace.collections?.collectionDefinitions);
737
54
 
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
- }
55
+ addEntry(entryMap, "inc-disclosure", namespace.overlays?.IncDisclosureElement);
56
+ addEntry(entryMap, "inc-dialog", namespace.overlays?.IncDialogElement);
57
+ addEntry(entryMap, "inc-drawer", namespace.overlays?.IncDrawerElement);
816
58
 
817
- class IncDialogElement extends IncDialogBaseElement {
818
- get drawerMode() { return false; }
59
+ return [...entryMap.entries()];
819
60
  }
820
61
 
821
- class IncDrawerElement extends IncDialogBaseElement {
822
- get drawerMode() { return true; }
62
+ function syncNamespace() {
63
+ namespace.components = new Map(getComponentEntries());
64
+ return namespace.components;
823
65
  }
824
66
 
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
67
  function defineAll(options = {}) {
852
68
  const registry = options.registry || globalThis.customElements;
853
- if (!registry || typeof registry.define !== "function" || typeof registry.get !== "function") return [];
69
+ if (!registry || typeof registry.define !== "function" || typeof registry.get !== "function") {
70
+ return [];
71
+ }
854
72
 
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
- }
73
+ const entries = getComponentEntries();
74
+ const results = entries.map(([name, ctor]) => defineCustomElement(name, ctor, registry));
75
+ syncNamespace();
865
76
 
866
- registry.define(name, ctor);
867
- return { name, defined: true, reason: "defined", constructor: ctor };
868
- });
77
+ return results;
869
78
  }
870
79
 
871
80
  function registerIncWebComponents(options = {}) {
872
81
  return defineAll(options);
873
82
  }
874
83
 
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
- }
84
+ namespace.defineAll = defineAll;
85
+ namespace.registerIncWebComponents = registerIncWebComponents;
86
+ syncNamespace();
881
87
 
882
88
  defineAll();
883
89