@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,979 @@
1
+ const NATIVE_CONTROL_SELECTOR = [
2
+ "input:not([type='hidden'])",
3
+ "select",
4
+ "textarea",
5
+ "button",
6
+ ].join(", ");
7
+
8
+ const HostElement = typeof HTMLElement === "undefined" ? class {} : HTMLElement;
9
+
10
+ let generatedIdCounter = 0;
11
+
12
+ function nextGeneratedId(prefix) {
13
+ generatedIdCounter += 1;
14
+ return `inc-wc-${prefix}-${generatedIdCounter}`;
15
+ }
16
+
17
+ function parseBooleanAttribute(host, name) {
18
+ return host.hasAttribute(name) && host.getAttribute(name) !== "false";
19
+ }
20
+
21
+ function reflectBooleanAttribute(host, name, value) {
22
+ if (value) {
23
+ host.setAttribute(name, "");
24
+ } else {
25
+ host.removeAttribute(name);
26
+ }
27
+ }
28
+
29
+ function toggleClass(element, className, enabled) {
30
+ if (!element) {
31
+ return;
32
+ }
33
+
34
+ element.classList.toggle(className, Boolean(enabled));
35
+ }
36
+
37
+ function withPart(element, partName) {
38
+ if (!element || !partName) {
39
+ return element;
40
+ }
41
+
42
+ const existing = (element.getAttribute("part") || "")
43
+ .split(/\s+/)
44
+ .filter(Boolean);
45
+
46
+ if (!existing.includes(partName)) {
47
+ existing.push(partName);
48
+ element.setAttribute("part", existing.join(" "));
49
+ }
50
+
51
+ return element;
52
+ }
53
+
54
+ function resolveAssignedElement(host, slotName, selector) {
55
+ // Only treat direct light-DOM children as assigned content. Generated wrappers
56
+ // live inside the host too, and reselecting them would make sync non-idempotent.
57
+ const explicit = Array.from(host.children).find((child) => (
58
+ child instanceof HTMLElement
59
+ && child.getAttribute("slot") === slotName
60
+ ));
61
+
62
+ if (explicit instanceof HTMLElement) {
63
+ return explicit;
64
+ }
65
+
66
+ if (!selector) {
67
+ return null;
68
+ }
69
+
70
+ return Array.from(host.children).find((child) => (
71
+ child instanceof HTMLElement
72
+ && child.matches(selector)
73
+ )) || null;
74
+ }
75
+
76
+ function ensureGeneratedElement(host, key, selector, factory) {
77
+ let element = host.querySelector(selector);
78
+
79
+ if (element instanceof HTMLElement) {
80
+ return element;
81
+ }
82
+
83
+ if (!host.__incGeneratedElements) {
84
+ host.__incGeneratedElements = new Map();
85
+ }
86
+
87
+ if (host.__incGeneratedElements.has(key)) {
88
+ return host.__incGeneratedElements.get(key);
89
+ }
90
+
91
+ element = factory();
92
+ element.setAttribute("data-inc-generated", key);
93
+ host.append(element);
94
+ host.__incGeneratedElements.set(key, element);
95
+
96
+ return element;
97
+ }
98
+
99
+ function ensureControlId(control) {
100
+ if (!control.id) {
101
+ control.id = nextGeneratedId("control");
102
+ }
103
+
104
+ return control.id;
105
+ }
106
+
107
+ function setDescribedBy(control, ids) {
108
+ const validIds = ids.filter(Boolean);
109
+
110
+ if (!validIds.length) {
111
+ control.removeAttribute("aria-describedby");
112
+ return;
113
+ }
114
+
115
+ control.setAttribute("aria-describedby", Array.from(new Set(validIds)).join(" "));
116
+ }
117
+
118
+ class IncFormsElement extends HostElement {
119
+ constructor() {
120
+ super();
121
+ this.__observer = null;
122
+ this.__syncScheduled = false;
123
+ }
124
+
125
+ connectedCallback() {
126
+ this.__installObserver();
127
+ this.sync();
128
+ }
129
+
130
+ disconnectedCallback() {
131
+ this.__observer?.disconnect();
132
+ this.__observer = null;
133
+ }
134
+
135
+ attributeChangedCallback() {
136
+ this.requestSync();
137
+ }
138
+
139
+ requestSync() {
140
+ if (this.__syncScheduled) {
141
+ return;
142
+ }
143
+
144
+ this.__syncScheduled = true;
145
+ queueMicrotask(() => {
146
+ this.__syncScheduled = false;
147
+ this.sync();
148
+ });
149
+ }
150
+
151
+ sync() {
152
+ // Overridden by concrete components.
153
+ }
154
+
155
+ notifySlotChange() {
156
+ this.dispatchEvent(new CustomEvent("slotchange", {
157
+ bubbles: true,
158
+ composed: true,
159
+ }));
160
+ }
161
+
162
+ __installObserver() {
163
+ if (this.__observer) {
164
+ return;
165
+ }
166
+
167
+ this.__observer = new MutationObserver(() => {
168
+ this.sync();
169
+ this.notifySlotChange();
170
+ });
171
+
172
+ // Only watch direct child assignment changes. Managed subtrees are updated
173
+ // by sync() itself and should not retrigger the observer loop.
174
+ this.__observer.observe(this, {
175
+ childList: true,
176
+ subtree: false,
177
+ attributes: true,
178
+ attributeFilter: ["slot"],
179
+ });
180
+ }
181
+ }
182
+
183
+ class IncFieldElement extends IncFormsElement {
184
+ static get observedAttributes() {
185
+ return ["label", "hint", "error", "required", "invalid", "dense"];
186
+ }
187
+
188
+ get label() {
189
+ return this.getAttribute("label") || "";
190
+ }
191
+
192
+ set label(value) {
193
+ if (value == null || value === "") {
194
+ this.removeAttribute("label");
195
+ } else {
196
+ this.setAttribute("label", String(value));
197
+ }
198
+ }
199
+
200
+ get hint() {
201
+ return this.getAttribute("hint") || "";
202
+ }
203
+
204
+ set hint(value) {
205
+ if (value == null || value === "") {
206
+ this.removeAttribute("hint");
207
+ } else {
208
+ this.setAttribute("hint", String(value));
209
+ }
210
+ }
211
+
212
+ get error() {
213
+ return this.getAttribute("error") || "";
214
+ }
215
+
216
+ set error(value) {
217
+ if (value == null || value === "") {
218
+ this.removeAttribute("error");
219
+ } else {
220
+ this.setAttribute("error", String(value));
221
+ }
222
+ }
223
+
224
+ get required() {
225
+ return parseBooleanAttribute(this, "required");
226
+ }
227
+
228
+ set required(value) {
229
+ reflectBooleanAttribute(this, "required", value);
230
+ }
231
+
232
+ get invalid() {
233
+ return parseBooleanAttribute(this, "invalid");
234
+ }
235
+
236
+ set invalid(value) {
237
+ reflectBooleanAttribute(this, "invalid", value);
238
+ }
239
+
240
+ get dense() {
241
+ return parseBooleanAttribute(this, "dense");
242
+ }
243
+
244
+ set dense(value) {
245
+ reflectBooleanAttribute(this, "dense", value);
246
+ }
247
+
248
+ focus() {
249
+ const control = this.__resolveControl();
250
+ control?.focus();
251
+ }
252
+
253
+ sync() {
254
+ this.classList.add("inc-form__field");
255
+ toggleClass(this, "inc-form__field--compact", this.dense);
256
+ withPart(this, "field");
257
+
258
+ const control = this.__resolveControl();
259
+ const label = this.__resolveLabel(control);
260
+ const hint = this.__resolveHint();
261
+ const error = this.__resolveError();
262
+
263
+ if (label) {
264
+ withPart(label, "label");
265
+ label.classList.add("inc-form__label");
266
+ toggleClass(label, "inc-form__label--required", this.required);
267
+
268
+ if (control && label instanceof HTMLLabelElement) {
269
+ label.htmlFor = ensureControlId(control);
270
+ }
271
+ }
272
+
273
+ if (control) {
274
+ withPart(control, "control");
275
+
276
+ if (!control.classList.contains("inc-form__control")
277
+ && !control.classList.contains("inc-input-group")
278
+ && control.slot !== "control") {
279
+ control.classList.add("inc-form__control");
280
+ }
281
+
282
+ control.required = this.required;
283
+
284
+ const invalid = this.invalid || this.error.length > 0;
285
+ if (invalid) {
286
+ control.setAttribute("aria-invalid", "true");
287
+ control.classList.add("is-invalid");
288
+ } else if (control.getAttribute("aria-invalid") === "true") {
289
+ control.removeAttribute("aria-invalid");
290
+ }
291
+
292
+ const describedBy = [];
293
+ if (hint?.id) {
294
+ describedBy.push(hint.id);
295
+ }
296
+ if (error?.id) {
297
+ describedBy.push(error.id);
298
+ }
299
+ setDescribedBy(control, describedBy);
300
+ }
301
+
302
+ if (hint) {
303
+ withPart(hint, "hint");
304
+ hint.classList.add("inc-form__hint");
305
+ }
306
+
307
+ if (error) {
308
+ withPart(error, "error");
309
+ error.classList.add("inc-form__invalid-feedback");
310
+ error.setAttribute("aria-live", "polite");
311
+ }
312
+ }
313
+
314
+ __resolveControl() {
315
+ const control = resolveAssignedElement(this, "control", NATIVE_CONTROL_SELECTOR);
316
+
317
+ if (!(control instanceof HTMLElement)) {
318
+ return null;
319
+ }
320
+
321
+ return control;
322
+ }
323
+
324
+ __resolveLabel(control) {
325
+ const explicit = resolveAssignedElement(this, "label", "label, [data-inc-field-label]");
326
+ if (explicit instanceof HTMLElement) {
327
+ return explicit;
328
+ }
329
+
330
+ if (!this.label) {
331
+ return null;
332
+ }
333
+
334
+ const generated = ensureGeneratedElement(
335
+ this,
336
+ "label",
337
+ '[data-inc-generated="label"]',
338
+ () => document.createElement("label"),
339
+ );
340
+
341
+ generated.textContent = this.label;
342
+
343
+ if (control instanceof HTMLElement) {
344
+ generated.setAttribute("for", ensureControlId(control));
345
+ }
346
+
347
+ return generated;
348
+ }
349
+
350
+ __resolveHint() {
351
+ const explicit = resolveAssignedElement(this, "hint", ".inc-form__hint, [data-inc-field-hint]");
352
+ if (explicit instanceof HTMLElement) {
353
+ if (!explicit.id) {
354
+ explicit.id = nextGeneratedId("hint");
355
+ }
356
+ return explicit;
357
+ }
358
+
359
+ if (!this.hint) {
360
+ return null;
361
+ }
362
+
363
+ const generated = ensureGeneratedElement(
364
+ this,
365
+ "hint",
366
+ '[data-inc-generated="hint"]',
367
+ () => document.createElement("p"),
368
+ );
369
+
370
+ if (!generated.id) {
371
+ generated.id = nextGeneratedId("hint");
372
+ }
373
+
374
+ generated.textContent = this.hint;
375
+ return generated;
376
+ }
377
+
378
+ __resolveError() {
379
+ const explicit = resolveAssignedElement(this, "error", ".inc-form__invalid-feedback, [data-inc-field-error]");
380
+ if (explicit instanceof HTMLElement) {
381
+ if (!explicit.id) {
382
+ explicit.id = nextGeneratedId("error");
383
+ }
384
+ return explicit;
385
+ }
386
+
387
+ if (!this.error) {
388
+ return null;
389
+ }
390
+
391
+ const generated = ensureGeneratedElement(
392
+ this,
393
+ "error",
394
+ '[data-inc-generated="error"]',
395
+ () => document.createElement("p"),
396
+ );
397
+
398
+ if (!generated.id) {
399
+ generated.id = nextGeneratedId("error");
400
+ }
401
+
402
+ generated.textContent = this.error;
403
+ return generated;
404
+ }
405
+ }
406
+
407
+ class IncInputGroupElement extends IncFormsElement {
408
+ static get observedAttributes() {
409
+ return ["prefix", "suffix", "dense", "expand"];
410
+ }
411
+
412
+ get prefix() {
413
+ return this.getAttribute("prefix") || "";
414
+ }
415
+
416
+ set prefix(value) {
417
+ if (value == null || value === "") {
418
+ this.removeAttribute("prefix");
419
+ } else {
420
+ this.setAttribute("prefix", String(value));
421
+ }
422
+ }
423
+
424
+ get suffix() {
425
+ return this.getAttribute("suffix") || "";
426
+ }
427
+
428
+ set suffix(value) {
429
+ if (value == null || value === "") {
430
+ this.removeAttribute("suffix");
431
+ } else {
432
+ this.setAttribute("suffix", String(value));
433
+ }
434
+ }
435
+
436
+ get dense() {
437
+ return parseBooleanAttribute(this, "dense");
438
+ }
439
+
440
+ set dense(value) {
441
+ reflectBooleanAttribute(this, "dense", value);
442
+ }
443
+
444
+ get expand() {
445
+ return parseBooleanAttribute(this, "expand");
446
+ }
447
+
448
+ set expand(value) {
449
+ reflectBooleanAttribute(this, "expand", value);
450
+ }
451
+
452
+ focus() {
453
+ const control = this.__resolveControl();
454
+ control?.focus();
455
+ }
456
+
457
+ sync() {
458
+ this.classList.add("inc-input-group");
459
+ toggleClass(this, "inc-input-group--sm", this.dense);
460
+ toggleClass(this, "inc-input-group--expand", this.expand);
461
+ withPart(this, "group");
462
+
463
+ const prefix = this.__resolvePrefix();
464
+ const suffix = this.__resolveSuffix();
465
+ const control = this.__resolveControl();
466
+
467
+ if (prefix) {
468
+ withPart(prefix, "prefix");
469
+ prefix.classList.add("inc-input-group__text");
470
+ }
471
+
472
+ if (suffix) {
473
+ withPart(suffix, "suffix");
474
+ suffix.classList.add("inc-input-group__text");
475
+ }
476
+
477
+ if (control) {
478
+ withPart(control, "control");
479
+ if (!control.classList.contains("inc-form__control")) {
480
+ control.classList.add("inc-form__control");
481
+ }
482
+ }
483
+ }
484
+
485
+ __resolvePrefix() {
486
+ const explicit = resolveAssignedElement(this, "prefix", ".inc-input-group__text[data-inc-prefix]");
487
+ if (explicit instanceof HTMLElement) {
488
+ return explicit;
489
+ }
490
+
491
+ if (!this.prefix) {
492
+ return null;
493
+ }
494
+
495
+ const generated = ensureGeneratedElement(
496
+ this,
497
+ "prefix",
498
+ '[data-inc-generated="prefix"]',
499
+ () => document.createElement("span"),
500
+ );
501
+
502
+ generated.setAttribute("data-inc-prefix", "true");
503
+ generated.textContent = this.prefix;
504
+ return generated;
505
+ }
506
+
507
+ __resolveSuffix() {
508
+ const explicit = resolveAssignedElement(this, "suffix", ".inc-input-group__text[data-inc-suffix]");
509
+ if (explicit instanceof HTMLElement) {
510
+ return explicit;
511
+ }
512
+
513
+ if (!this.suffix) {
514
+ return null;
515
+ }
516
+
517
+ const generated = ensureGeneratedElement(
518
+ this,
519
+ "suffix",
520
+ '[data-inc-generated="suffix"]',
521
+ () => document.createElement("span"),
522
+ );
523
+
524
+ generated.setAttribute("data-inc-suffix", "true");
525
+ generated.textContent = this.suffix;
526
+ return generated;
527
+ }
528
+
529
+ __resolveControl() {
530
+ const control = resolveAssignedElement(this, "control", NATIVE_CONTROL_SELECTOR);
531
+ return control instanceof HTMLElement ? control : null;
532
+ }
533
+ }
534
+
535
+ class IncChoiceGroupElement extends IncFormsElement {
536
+ static get observedAttributes() {
537
+ return ["type", "legend", "orientation", "inline", "dense", "hint", "error"];
538
+ }
539
+
540
+ get legend() {
541
+ return this.getAttribute("legend") || "";
542
+ }
543
+
544
+ set legend(value) {
545
+ if (value == null || value === "") {
546
+ this.removeAttribute("legend");
547
+ } else {
548
+ this.setAttribute("legend", String(value));
549
+ }
550
+ }
551
+
552
+ get inline() {
553
+ return parseBooleanAttribute(this, "inline");
554
+ }
555
+
556
+ set inline(value) {
557
+ reflectBooleanAttribute(this, "inline", value);
558
+ }
559
+
560
+ focusFirst() {
561
+ const firstFocusable = this.querySelector(NATIVE_CONTROL_SELECTOR);
562
+ firstFocusable?.focus();
563
+ }
564
+
565
+ sync() {
566
+ this.classList.add("inc-form__fieldset");
567
+ withPart(this, "group");
568
+ this.setAttribute("role", "group");
569
+
570
+ const legend = this.__resolveLegend();
571
+ const choices = this.__resolveChoices();
572
+ const hint = this.__resolveHint();
573
+ const error = this.__resolveError();
574
+
575
+ if (legend) {
576
+ withPart(legend, "legend");
577
+ legend.classList.add("inc-form__legend");
578
+
579
+ if (!legend.id) {
580
+ legend.id = nextGeneratedId("legend");
581
+ }
582
+
583
+ this.setAttribute("aria-labelledby", legend.id);
584
+ }
585
+
586
+ if (choices) {
587
+ withPart(choices, "control");
588
+ choices.classList.add("inc-form__choices");
589
+ toggleClass(choices, "inc-form__choices--inline", this.inline);
590
+ }
591
+
592
+ if (hint) {
593
+ withPart(hint, "hint");
594
+ hint.classList.add("inc-form__hint");
595
+ }
596
+
597
+ if (error) {
598
+ withPart(error, "error");
599
+ error.classList.add("inc-form__invalid-feedback");
600
+ error.setAttribute("aria-live", "polite");
601
+ }
602
+ }
603
+
604
+ __resolveLegend() {
605
+ const explicit = resolveAssignedElement(this, "legend", "legend, [data-inc-choice-legend]");
606
+ if (explicit instanceof HTMLElement) {
607
+ return explicit;
608
+ }
609
+
610
+ if (!this.legend) {
611
+ return null;
612
+ }
613
+
614
+ const generated = ensureGeneratedElement(
615
+ this,
616
+ "legend",
617
+ '[data-inc-generated="legend"]',
618
+ () => document.createElement("legend"),
619
+ );
620
+
621
+ generated.setAttribute("data-inc-choice-legend", "true");
622
+ generated.textContent = this.legend;
623
+ return generated;
624
+ }
625
+
626
+ __resolveChoices() {
627
+ const existing = this.querySelector(".inc-form__choices, [data-inc-choice-items]");
628
+ if (existing instanceof HTMLElement) {
629
+ return existing;
630
+ }
631
+
632
+ const slotItems = Array.from(this.children).filter((child) => (
633
+ child instanceof HTMLElement
634
+ && child.getAttribute("slot") === "item"
635
+ ));
636
+ if (!slotItems.length) {
637
+ return null;
638
+ }
639
+
640
+ const generated = ensureGeneratedElement(
641
+ this,
642
+ "items",
643
+ '[data-inc-generated="items"]',
644
+ () => document.createElement("div"),
645
+ );
646
+
647
+ generated.setAttribute("data-inc-choice-items", "true");
648
+
649
+ for (const node of slotItems) {
650
+ // Skip nodes that are already inside the generated wrapper to avoid
651
+ // reordering the same items on every observer tick.
652
+ if (!generated.contains(node)) {
653
+ generated.append(node);
654
+ }
655
+ }
656
+
657
+ return generated;
658
+ }
659
+
660
+ __resolveHint() {
661
+ const explicit = resolveAssignedElement(this, "hint", ".inc-form__hint, [data-inc-choice-hint]");
662
+ if (explicit instanceof HTMLElement) {
663
+ return explicit;
664
+ }
665
+
666
+ const hintText = this.getAttribute("hint");
667
+ if (!hintText) {
668
+ return null;
669
+ }
670
+
671
+ const generated = ensureGeneratedElement(
672
+ this,
673
+ "hint",
674
+ '[data-inc-generated="hint"]',
675
+ () => document.createElement("p"),
676
+ );
677
+
678
+ generated.setAttribute("data-inc-choice-hint", "true");
679
+ generated.textContent = hintText;
680
+ return generated;
681
+ }
682
+
683
+ __resolveError() {
684
+ const explicit = resolveAssignedElement(this, "error", ".inc-form__invalid-feedback, [data-inc-choice-error]");
685
+ if (explicit instanceof HTMLElement) {
686
+ return explicit;
687
+ }
688
+
689
+ const errorText = this.getAttribute("error");
690
+ if (!errorText) {
691
+ return null;
692
+ }
693
+
694
+ const generated = ensureGeneratedElement(
695
+ this,
696
+ "error",
697
+ '[data-inc-generated="error"]',
698
+ () => document.createElement("p"),
699
+ );
700
+
701
+ generated.setAttribute("data-inc-choice-error", "true");
702
+ generated.textContent = errorText;
703
+ return generated;
704
+ }
705
+ }
706
+
707
+ class IncReadonlyFieldElement extends IncFormsElement {
708
+ static get observedAttributes() {
709
+ return ["label", "value", "dense"];
710
+ }
711
+
712
+ sync() {
713
+ this.classList.add("inc-readonly-field");
714
+ withPart(this, "field");
715
+
716
+ const label = this.__resolveLabel();
717
+ const value = this.__resolveValue();
718
+ const meta = resolveAssignedElement(this, "meta", '[slot="meta"], [data-inc-readonly-meta]');
719
+
720
+ if (label) {
721
+ withPart(label, "label");
722
+ }
723
+
724
+ if (value) {
725
+ withPart(value, "value");
726
+ }
727
+
728
+ if (meta) {
729
+ withPart(meta, "meta");
730
+ }
731
+ }
732
+
733
+ __resolveLabel() {
734
+ const explicit = resolveAssignedElement(this, "label", '[slot="label"], [data-inc-readonly-label]');
735
+ if (explicit instanceof HTMLElement) {
736
+ return explicit;
737
+ }
738
+
739
+ const labelText = this.getAttribute("label");
740
+ if (!labelText) {
741
+ return null;
742
+ }
743
+
744
+ const generated = ensureGeneratedElement(
745
+ this,
746
+ "label",
747
+ '[data-inc-generated="label"]',
748
+ () => document.createElement("span"),
749
+ );
750
+
751
+ generated.setAttribute("data-inc-readonly-label", "true");
752
+ generated.textContent = labelText;
753
+ return generated;
754
+ }
755
+
756
+ __resolveValue() {
757
+ const explicit = resolveAssignedElement(this, "value", '[slot="value"], [data-inc-readonly-value]');
758
+ if (explicit instanceof HTMLElement) {
759
+ return explicit;
760
+ }
761
+
762
+ const valueText = this.getAttribute("value");
763
+ if (!valueText) {
764
+ return null;
765
+ }
766
+
767
+ const generated = ensureGeneratedElement(
768
+ this,
769
+ "value",
770
+ '[data-inc-generated="value"]',
771
+ () => document.createElement("span"),
772
+ );
773
+
774
+ generated.setAttribute("data-inc-readonly-value", "true");
775
+ generated.textContent = valueText;
776
+ return generated;
777
+ }
778
+ }
779
+
780
+ class IncValidationSummaryElement extends IncFormsElement {
781
+ static get observedAttributes() {
782
+ return ["title", "count", "live"];
783
+ }
784
+
785
+ get title() {
786
+ return this.getAttribute("title") || "";
787
+ }
788
+
789
+ set title(value) {
790
+ if (value == null || value === "") {
791
+ this.removeAttribute("title");
792
+ } else {
793
+ this.setAttribute("title", String(value));
794
+ }
795
+ }
796
+
797
+ announce(message) {
798
+ const announcement = String(message || "").trim();
799
+ if (!announcement) {
800
+ return;
801
+ }
802
+
803
+ const node = ensureGeneratedElement(
804
+ this,
805
+ "announcement",
806
+ '[data-inc-generated="announcement"]',
807
+ () => document.createElement("span"),
808
+ );
809
+
810
+ node.style.position = "absolute";
811
+ node.style.width = "1px";
812
+ node.style.height = "1px";
813
+ node.style.overflow = "hidden";
814
+ node.style.clip = "rect(0 0 0 0)";
815
+ node.style.clipPath = "inset(50%)";
816
+ node.style.whiteSpace = "nowrap";
817
+ node.setAttribute("aria-live", this.getAttribute("live") || "polite");
818
+ node.textContent = announcement;
819
+ }
820
+
821
+ sync() {
822
+ this.classList.add("inc-form__error-summary");
823
+ withPart(this, "summary");
824
+
825
+ const title = this.__resolveTitle();
826
+ const list = this.__resolveList();
827
+
828
+ const liveMode = this.getAttribute("live");
829
+ if (liveMode) {
830
+ this.setAttribute("aria-live", liveMode);
831
+ } else {
832
+ this.removeAttribute("aria-live");
833
+ }
834
+
835
+ if (title) {
836
+ withPart(title, "title");
837
+ title.classList.add("inc-form__error-summary-title");
838
+
839
+ const count = this.getAttribute("count");
840
+ if (count && title.getAttribute("data-inc-generated") === "title") {
841
+ const numeric = Number.parseInt(count, 10);
842
+ if (Number.isFinite(numeric) && numeric >= 0) {
843
+ title.textContent = numeric === 1
844
+ ? "There is 1 issue to fix"
845
+ : `There are ${numeric} issues to fix`;
846
+ }
847
+ }
848
+ }
849
+
850
+ if (list) {
851
+ withPart(list, "list");
852
+ list.classList.add("inc-form__error-summary-list");
853
+ for (const item of list.children) {
854
+ withPart(item, "item");
855
+ }
856
+ }
857
+ }
858
+
859
+ __resolveTitle() {
860
+ const explicit = resolveAssignedElement(
861
+ this,
862
+ "title",
863
+ ".inc-form__error-summary-title, [data-inc-validation-title]",
864
+ );
865
+ if (explicit instanceof HTMLElement) {
866
+ return explicit;
867
+ }
868
+
869
+ const titleText = this.title;
870
+ if (!titleText && !this.hasAttribute("count")) {
871
+ return null;
872
+ }
873
+
874
+ const generated = ensureGeneratedElement(
875
+ this,
876
+ "title",
877
+ '[data-inc-generated="title"]',
878
+ () => document.createElement("h3"),
879
+ );
880
+
881
+ generated.setAttribute("data-inc-validation-title", "true");
882
+ if (titleText) {
883
+ generated.textContent = titleText;
884
+ }
885
+
886
+ return generated;
887
+ }
888
+
889
+ __resolveList() {
890
+ const existing = this.querySelector(".inc-form__error-summary-list, [data-inc-validation-list]");
891
+ if (existing instanceof HTMLElement) {
892
+ return existing;
893
+ }
894
+
895
+ const slotItems = Array.from(this.children).filter((child) => (
896
+ child instanceof HTMLElement
897
+ && child.getAttribute("slot") === "item"
898
+ ));
899
+ if (!slotItems.length) {
900
+ return null;
901
+ }
902
+
903
+ const generated = ensureGeneratedElement(
904
+ this,
905
+ "list",
906
+ '[data-inc-generated="list"]',
907
+ () => document.createElement("ul"),
908
+ );
909
+
910
+ generated.setAttribute("data-inc-validation-list", "true");
911
+
912
+ for (const item of slotItems) {
913
+ if (generated.contains(item)) {
914
+ continue;
915
+ }
916
+
917
+ if (item.tagName !== "LI") {
918
+ // Wrap non-list items once, then leave them in place on later syncs.
919
+ const wrapped = document.createElement("li");
920
+ wrapped.append(item);
921
+ generated.append(wrapped);
922
+ continue;
923
+ }
924
+
925
+ generated.append(item);
926
+ }
927
+
928
+ return generated;
929
+ }
930
+ }
931
+
932
+ const FORM_COMPONENTS = [
933
+ ["inc-field", IncFieldElement],
934
+ ["inc-input-group", IncInputGroupElement],
935
+ ["inc-choice-group", IncChoiceGroupElement],
936
+ ["inc-readonly-field", IncReadonlyFieldElement],
937
+ ["inc-validation-summary", IncValidationSummaryElement],
938
+ ];
939
+
940
+ function registerFormsComponents(registry = globalThis.customElements) {
941
+ if (!registry || typeof registry.define !== "function" || typeof registry.get !== "function") {
942
+ return [];
943
+ }
944
+
945
+ const registered = [];
946
+ for (const [name, ctor] of FORM_COMPONENTS) {
947
+ if (!registry.get(name)) {
948
+ registry.define(name, ctor);
949
+ registered.push(name);
950
+ }
951
+ }
952
+
953
+ return registered;
954
+ }
955
+
956
+ if (typeof module !== "undefined" && module.exports) {
957
+ module.exports = {
958
+ registerFormsComponents,
959
+ IncFieldElement,
960
+ IncInputGroupElement,
961
+ IncChoiceGroupElement,
962
+ IncReadonlyFieldElement,
963
+ IncValidationSummaryElement,
964
+ };
965
+ }
966
+
967
+ if (typeof globalThis !== "undefined") {
968
+ const namespace = globalThis.IncWebComponents || (globalThis.IncWebComponents = {});
969
+ namespace.forms = Object.assign({}, namespace.forms, {
970
+ register: registerFormsComponents,
971
+ components: {
972
+ IncFieldElement,
973
+ IncInputGroupElement,
974
+ IncChoiceGroupElement,
975
+ IncReadonlyFieldElement,
976
+ IncValidationSummaryElement,
977
+ },
978
+ });
979
+ }