@incursa/ui-kit 1.5.0 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LLMS.txt +4 -4
- package/README.md +46 -6
- package/dist/inc-design-language.css +192 -35
- package/dist/inc-design-language.css.map +1 -1
- package/dist/inc-design-language.min.css +1 -1
- package/dist/inc-design-language.min.css.map +1 -1
- package/dist/mcp/ai/agent-instructions.json +21 -0
- package/dist/mcp/ai/llms-txt.json +21 -0
- package/dist/mcp/components/buttons.json +29 -0
- package/dist/mcp/components/cards.json +29 -0
- package/dist/mcp/components/filter-bars.json +28 -0
- package/dist/mcp/components/form-choices.json +29 -0
- package/dist/mcp/components/forms.json +29 -0
- package/dist/mcp/components/interaction.json +28 -0
- package/dist/mcp/components/layout.json +28 -0
- package/dist/mcp/components/metrics.json +28 -0
- package/dist/mcp/components/states.json +28 -0
- package/dist/mcp/components/status.json +28 -0
- package/dist/mcp/components/tables.json +32 -0
- package/dist/mcp/components/utilities.json +28 -0
- package/dist/mcp/examples/data-grid-advanced.json +22 -0
- package/dist/mcp/examples/demo.json +24 -0
- package/dist/mcp/examples/forms-and-validation.json +21 -0
- package/dist/mcp/examples/native-patterns.json +21 -0
- package/dist/mcp/examples/overlay-workflows.json +22 -0
- package/dist/mcp/examples/record-detail.json +21 -0
- package/dist/mcp/examples/reference.json +23 -0
- package/dist/mcp/examples/states.json +21 -0
- package/dist/mcp/examples/web-components.json +24 -0
- package/dist/mcp/examples/work-queue.json +21 -0
- package/dist/mcp/guides/allowed-web-component-families.json +19 -0
- package/dist/mcp/guides/choose-css-vs-scss-vs-js-vs-web-components.json +20 -0
- package/dist/mcp/guides/customization-order.json +20 -0
- package/dist/mcp/guides/decision-tree.json +28 -0
- package/dist/mcp/guides/guardrails.json +20 -0
- package/dist/mcp/guides/install.json +31 -0
- package/dist/mcp/guides/latest.json +25 -0
- package/dist/mcp/guides/overview.json +26 -0
- package/dist/mcp/guides/package-metadata.json +25 -0
- package/dist/mcp/guides/update.json +26 -0
- package/dist/mcp/guides/when-to-use-css-first.json +20 -0
- package/dist/mcp/install.json +36 -0
- package/dist/mcp/patterns/data-grid-advanced.json +22 -0
- package/dist/mcp/patterns/demo.json +24 -0
- package/dist/mcp/patterns/forms-and-validation.json +21 -0
- package/dist/mcp/patterns/native-patterns.json +21 -0
- package/dist/mcp/patterns/overlay-workflows.json +22 -0
- package/dist/mcp/patterns/record-detail.json +21 -0
- package/dist/mcp/patterns/reference.json +24 -0
- package/dist/mcp/patterns/states.json +21 -0
- package/dist/mcp/patterns/web-components.json +24 -0
- package/dist/mcp/patterns/work-queue.json +21 -0
- package/dist/mcp/resources.json +2100 -0
- package/dist/mcp/search-index.json +827 -0
- package/dist/mcp/specs/control-conventions.json +21 -0
- package/dist/mcp/specs/public-surface.json +21 -0
- package/dist/mcp/specs/requirements-index.json +21 -0
- package/dist/mcp/specs/verification-index.json +21 -0
- package/dist/mcp/update.json +24 -0
- package/dist/mcp/worker.mjs +59959 -0
- package/dist/mcp/worker.mjs.map +7 -0
- package/dist/web-components/README.md +10 -4
- package/dist/web-components/RUNTIME-NOTES.md +7 -2
- package/dist/web-components/components/actions.js +557 -0
- package/dist/web-components/components/collections.js +272 -0
- package/dist/web-components/components/dom-helpers.js +46 -0
- package/dist/web-components/components/feedback.js +165 -0
- package/dist/web-components/index.js +4350 -813
- package/package.json +19 -8
- package/src/inc-design-language.scss +193 -35
- package/src/mcp/worker.ts +858 -0
- package/src/web-components/README.md +10 -4
- package/src/web-components/RUNTIME-NOTES.md +7 -2
- package/src/web-components/components/actions.js +557 -0
- package/src/web-components/components/collections.js +272 -0
- package/src/web-components/components/dom-helpers.js +46 -0
- package/src/web-components/components/feedback.js +165 -0
- package/src/web-components/index.js +53 -847
|
@@ -45,6 +45,8 @@ The runtime auto-defines the shipped elements on load. If a consumer needs expli
|
|
|
45
45
|
The `IncElement` base class, including reflected attribute/property wiring and slot helpers.
|
|
46
46
|
- [`registry.js`](registry.js)
|
|
47
47
|
Idempotent registration helpers and the `IncWebComponents.registry` namespace.
|
|
48
|
+
- [`components/dom-helpers.js`](components/dom-helpers.js)
|
|
49
|
+
Shared DOM helpers used by the action and collection modules.
|
|
48
50
|
- [`controllers/focus.js`](controllers/focus.js)
|
|
49
51
|
Shared focus utilities for focus trapping, focus restoration, and focusable-element discovery.
|
|
50
52
|
- [`controllers/selection.js`](controllers/selection.js)
|
|
@@ -60,11 +62,15 @@ The runtime auto-defines the shipped elements on load. If a consumer needs expli
|
|
|
60
62
|
- [`components/forms.js`](components/forms.js)
|
|
61
63
|
Field wrappers, input groups, choice groups, read-only fields, and validation summary custom elements.
|
|
62
64
|
- [`components/feedback.js`](components/feedback.js)
|
|
63
|
-
State panel, live-region, auto-refresh, and theme-switcher custom elements.
|
|
65
|
+
State panel, live-region, auto-refresh, badge, spinner, and theme-switcher custom elements.
|
|
66
|
+
- [`components/actions.js`](components/actions.js)
|
|
67
|
+
Button, button-group, button-toolbar, close-button, alert, and empty-state custom elements.
|
|
68
|
+
- [`components/collections.js`](components/collections.js)
|
|
69
|
+
List-group, key-value-grid, and key-value custom elements.
|
|
64
70
|
- [`components/overlays.js`](components/overlays.js)
|
|
65
71
|
Disclosure, dialog, and drawer custom elements.
|
|
66
72
|
- [`index.js`](index.js)
|
|
67
|
-
|
|
73
|
+
Thin package bootstrap that imports the family modules, registers the shipped families, and exposes the public namespace.
|
|
68
74
|
|
|
69
75
|
As more families land, keep them in this same shape: small shared controllers plus family modules that register their public elements through the registry.
|
|
70
76
|
|
|
@@ -75,9 +81,9 @@ The Web Component layer should mirror the current CSS kit, not reinterpret it.
|
|
|
75
81
|
- Layout primitives should stay composable and slot-driven.
|
|
76
82
|
- Form wrappers should keep native controls native.
|
|
77
83
|
- Navigation components should reflect keyboard and focus state in the DOM.
|
|
78
|
-
- Feedback and status shells should announce state accessibly.
|
|
84
|
+
- Feedback and status shells should announce state accessibly, badge/spinner atoms should standardize the common tone and loading defaults, and action/detail plus collection hosts should keep buttons, alerts, list groups, and description-list pairs declarative without inventing a second styling vocabulary.
|
|
79
85
|
- Overlays should prefer native `<details>` and `<dialog>` behavior when that satisfies the contract.
|
|
80
|
-
- Tables, data presentation, utility atoms, and
|
|
86
|
+
- Tables, data presentation, utility atoms, and the remaining presentation-only surfaces should remain CSS-first until the component contract is explicit and worth the runtime cost.
|
|
81
87
|
|
|
82
88
|
## Maintenance Notes
|
|
83
89
|
|
|
@@ -9,20 +9,25 @@ The runtime defines the approved v1 host family set:
|
|
|
9
9
|
- layouts and shells: `inc-app-shell`, `inc-page`, `inc-page-header`, `inc-section`, `inc-card`, `inc-summary-overview`, `inc-summary-block`, `inc-footer-bar`
|
|
10
10
|
- navigation: `inc-navbar`, `inc-tabs`, `inc-user-menu`
|
|
11
11
|
- forms and inputs: `inc-field`, `inc-input-group`, `inc-choice-group`, `inc-readonly-field`, `inc-validation-summary`
|
|
12
|
-
- feedback and status: `inc-state-panel`, `inc-live-region`, `inc-auto-refresh`, `inc-theme-switcher`
|
|
12
|
+
- feedback and status: `inc-state-panel`, `inc-live-region`, `inc-auto-refresh`, `inc-theme-switcher`, `inc-badge`, `inc-spinner`
|
|
13
|
+
- actions and detail shells: `inc-button`, `inc-button-group`, `inc-button-toolbar`, `inc-close-button`, `inc-alert`, `inc-empty-state`
|
|
14
|
+
- collections: `inc-list-group`, `inc-key-value-grid`, `inc-key-value`
|
|
13
15
|
- overlays: `inc-disclosure`, `inc-dialog`, `inc-drawer`
|
|
14
16
|
|
|
15
17
|
## Contract shape
|
|
16
18
|
|
|
17
19
|
- CSS-first is still canonical. Components reuse existing `inc-*` class contracts.
|
|
20
|
+
- Badge and spinner hosts standardize the most common atomic status defaults while still reusing the same `inc-*` vocabulary, the action/detail hosts standardize repeated button, alert, and empty-state markup patterns, and the collection hosts standardize repeated list and key/value markup patterns.
|
|
18
21
|
- Package consumers should pair `@incursa/ui-kit/web-components` with `@incursa/ui-kit/web-components/style.css` when they want the default look out of the box.
|
|
19
22
|
- v1 stays light DOM first so current style selectors keep working.
|
|
20
23
|
- Native primitives are used for disclosure/menu/dialog behavior where practical.
|
|
21
|
-
- `index.js` is a thin bootstrap that registers family modules:
|
|
24
|
+
- `index.js` is a thin bootstrap that registers family modules and the promoted action/detail and collection hosts:
|
|
22
25
|
- `components/layout.js`
|
|
23
26
|
- `components/navigation.js`
|
|
24
27
|
- `components/forms.js`
|
|
25
28
|
- `components/feedback.js`
|
|
29
|
+
- `components/actions.js`
|
|
30
|
+
- `components/collections.js`
|
|
26
31
|
- `components/overlays.js`
|
|
27
32
|
- Public registration API is idempotent:
|
|
28
33
|
- `window.IncWebComponents.defineAll()`
|
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
import {
|
|
2
|
+
addClass,
|
|
3
|
+
ensureNode,
|
|
4
|
+
moveChildNodes,
|
|
5
|
+
normalizeToken,
|
|
6
|
+
removeMatchingClasses,
|
|
7
|
+
} from "./dom-helpers.js";
|
|
8
|
+
|
|
9
|
+
const FALSE_TOKENS = new Set(["false", "0", "off", "no"]);
|
|
10
|
+
const BADGE_TONES = new Set(["primary", "secondary", "success", "danger", "warning", "info"]);
|
|
11
|
+
const BUTTON_VARIANTS = new Set([
|
|
12
|
+
"primary",
|
|
13
|
+
"secondary",
|
|
14
|
+
"success",
|
|
15
|
+
"danger",
|
|
16
|
+
"warning",
|
|
17
|
+
"info",
|
|
18
|
+
"link",
|
|
19
|
+
"outline-primary",
|
|
20
|
+
"outline-secondary",
|
|
21
|
+
"outline-success",
|
|
22
|
+
"outline-danger",
|
|
23
|
+
"outline-warning",
|
|
24
|
+
"outline-info",
|
|
25
|
+
]);
|
|
26
|
+
const BUTTON_SIZES = new Set(["sm", "lg", "micro"]);
|
|
27
|
+
|
|
28
|
+
const HostElement = typeof HTMLElement === "undefined" ? class {} : HTMLElement;
|
|
29
|
+
|
|
30
|
+
function toBoolean(value, fallback = false) {
|
|
31
|
+
if (value == null) {
|
|
32
|
+
return fallback;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return !FALSE_TOKENS.has(String(value).toLowerCase());
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function emit(host, type, detail = {}, options = {}) {
|
|
39
|
+
return host.dispatchEvent(new CustomEvent(type, {
|
|
40
|
+
detail,
|
|
41
|
+
bubbles: options.bubbles !== false,
|
|
42
|
+
composed: options.composed !== false,
|
|
43
|
+
cancelable: options.cancelable === true,
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
class IncElement extends HostElement {
|
|
48
|
+
emit(type, detail = {}, options = {}) {
|
|
49
|
+
return emit(this, type, detail, options);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export class IncButtonElement extends IncElement {
|
|
54
|
+
static get observedAttributes() {
|
|
55
|
+
return ["tone", "variant", "size", "loading", "href", "type", "disabled", "label", "target", "rel", "download"];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
connectedCallback() {
|
|
59
|
+
addClass(this, "inc-button");
|
|
60
|
+
this.bindEvents();
|
|
61
|
+
this.sync();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
disconnectedCallback() {
|
|
65
|
+
if (this._boundClick) {
|
|
66
|
+
this.removeEventListener("click", this._boundClick);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
attributeChangedCallback() {
|
|
71
|
+
if (this.isConnected) {
|
|
72
|
+
this.sync();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
bindEvents() {
|
|
77
|
+
if (this._boundClick) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this._boundClick = (event) => {
|
|
82
|
+
if (!this._control || !this.contains(this._control)) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const isBlocked = this._control.hasAttribute("aria-disabled") || this._control.classList.contains("is-loading");
|
|
87
|
+
if (!isBlocked || this._control.tagName !== "A") {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
event.preventDefault();
|
|
92
|
+
event.stopPropagation();
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
this.addEventListener("click", this._boundClick);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
sync() {
|
|
99
|
+
addClass(this, "inc-button");
|
|
100
|
+
this.setAttribute("part", "button");
|
|
101
|
+
const control = this.ensureControl();
|
|
102
|
+
const variant = normalizeToken(this.getAttribute("variant") || this.getAttribute("tone")) || "secondary";
|
|
103
|
+
const resolvedVariant = BUTTON_VARIANTS.has(variant) ? variant : "secondary";
|
|
104
|
+
const size = normalizeToken(this.getAttribute("size"));
|
|
105
|
+
const loading = toBoolean(this.getAttribute("loading"));
|
|
106
|
+
const disabled = toBoolean(this.getAttribute("disabled")) || loading;
|
|
107
|
+
|
|
108
|
+
control.className = "inc-btn inc-button__control";
|
|
109
|
+
control.setAttribute("part", "control");
|
|
110
|
+
control.classList.add(`inc-btn--${resolvedVariant}`);
|
|
111
|
+
if (BUTTON_SIZES.has(size)) {
|
|
112
|
+
control.classList.add(`inc-btn--${size}`);
|
|
113
|
+
}
|
|
114
|
+
control.classList.toggle("is-loading", loading);
|
|
115
|
+
|
|
116
|
+
if (control.tagName === "BUTTON") {
|
|
117
|
+
control.type = this.getAttribute("type") || "button";
|
|
118
|
+
control.disabled = disabled;
|
|
119
|
+
} else {
|
|
120
|
+
control.setAttribute("href", this.getAttribute("href") || "#");
|
|
121
|
+
|
|
122
|
+
const target = this.getAttribute("target");
|
|
123
|
+
if (target) {
|
|
124
|
+
control.setAttribute("target", target);
|
|
125
|
+
} else {
|
|
126
|
+
control.removeAttribute("target");
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const rel = this.getAttribute("rel");
|
|
130
|
+
if (rel) {
|
|
131
|
+
control.setAttribute("rel", rel);
|
|
132
|
+
} else {
|
|
133
|
+
control.removeAttribute("rel");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const download = this.getAttribute("download");
|
|
137
|
+
if (download != null) {
|
|
138
|
+
control.setAttribute("download", download);
|
|
139
|
+
} else {
|
|
140
|
+
control.removeAttribute("download");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (disabled) {
|
|
144
|
+
control.setAttribute("aria-disabled", "true");
|
|
145
|
+
control.tabIndex = -1;
|
|
146
|
+
} else {
|
|
147
|
+
control.removeAttribute("aria-disabled");
|
|
148
|
+
control.removeAttribute("tabindex");
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (loading) {
|
|
153
|
+
control.setAttribute("aria-busy", "true");
|
|
154
|
+
this.ensureLoadingSpinner(control);
|
|
155
|
+
} else {
|
|
156
|
+
control.removeAttribute("aria-busy");
|
|
157
|
+
this.removeLoadingSpinner(control);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const label = this.getAttribute("label");
|
|
161
|
+
if (label) {
|
|
162
|
+
control.setAttribute("aria-label", label);
|
|
163
|
+
} else {
|
|
164
|
+
control.removeAttribute("aria-label");
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
ensureControl() {
|
|
169
|
+
const desiredTag = this.hasAttribute("href") ? "A" : "BUTTON";
|
|
170
|
+
const existing = this._control || this.querySelector(":scope > button.inc-button__control, :scope > a.inc-button__control");
|
|
171
|
+
|
|
172
|
+
if (existing && existing.tagName === desiredTag) {
|
|
173
|
+
this._control = existing;
|
|
174
|
+
return existing;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const control = document.createElement(desiredTag.toLowerCase());
|
|
178
|
+
if (desiredTag === "BUTTON") {
|
|
179
|
+
control.type = this.getAttribute("type") || "button";
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (existing) {
|
|
183
|
+
moveChildNodes(existing, control);
|
|
184
|
+
existing.replaceWith(control);
|
|
185
|
+
} else {
|
|
186
|
+
moveChildNodes(this, control);
|
|
187
|
+
this.append(control);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (!control.childNodes.length) {
|
|
191
|
+
control.textContent = this.textContent || "";
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
this._control = control;
|
|
195
|
+
return control;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
ensureLoadingSpinner(control) {
|
|
199
|
+
this.removeLoadingSpinner(control);
|
|
200
|
+
const spinner = document.createElement("span");
|
|
201
|
+
spinner.dataset.incButtonSpinner = "true";
|
|
202
|
+
spinner.className = "inc-spinner inc-spinner--border inc-spinner--border--sm";
|
|
203
|
+
spinner.setAttribute("aria-hidden", "true");
|
|
204
|
+
control.append(spinner);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
removeLoadingSpinner(control) {
|
|
208
|
+
if (!(control instanceof HTMLElement)) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
control.querySelectorAll(":scope > [data-inc-button-spinner]").forEach((node) => node.remove());
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export class IncButtonGroupElement extends IncElement {
|
|
217
|
+
static get observedAttributes() {
|
|
218
|
+
return ["size", "label"];
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
connectedCallback() {
|
|
222
|
+
addClass(this, "inc-button-group");
|
|
223
|
+
this.sync();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
attributeChangedCallback() {
|
|
227
|
+
if (this.isConnected) {
|
|
228
|
+
this.sync();
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
sync() {
|
|
233
|
+
addClass(this, "inc-button-group");
|
|
234
|
+
this.setAttribute("part", "button-group");
|
|
235
|
+
removeMatchingClasses(this, (token) => token.startsWith("inc-button-group--"));
|
|
236
|
+
|
|
237
|
+
const size = normalizeToken(this.getAttribute("size"));
|
|
238
|
+
if (BUTTON_SIZES.has(size)) {
|
|
239
|
+
this.classList.add(`inc-button-group--${size}`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
this.setAttribute("role", "group");
|
|
243
|
+
const label = this.getAttribute("label") || this.getAttribute("aria-label") || "";
|
|
244
|
+
if (label) {
|
|
245
|
+
this.setAttribute("aria-label", label);
|
|
246
|
+
} else {
|
|
247
|
+
this.removeAttribute("aria-label");
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export class IncButtonToolbarElement extends IncElement {
|
|
253
|
+
static get observedAttributes() {
|
|
254
|
+
return ["label", "orientation"];
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
connectedCallback() {
|
|
258
|
+
addClass(this, "inc-button-toolbar");
|
|
259
|
+
this.sync();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
attributeChangedCallback() {
|
|
263
|
+
if (this.isConnected) {
|
|
264
|
+
this.sync();
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
sync() {
|
|
269
|
+
addClass(this, "inc-button-toolbar");
|
|
270
|
+
this.setAttribute("part", "button-toolbar");
|
|
271
|
+
this.setAttribute("role", "toolbar");
|
|
272
|
+
|
|
273
|
+
const orientation = normalizeToken(this.getAttribute("orientation"));
|
|
274
|
+
if (orientation === "vertical") {
|
|
275
|
+
this.setAttribute("aria-orientation", "vertical");
|
|
276
|
+
} else {
|
|
277
|
+
this.removeAttribute("aria-orientation");
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const label = this.getAttribute("label") || this.getAttribute("aria-label") || "";
|
|
281
|
+
if (label) {
|
|
282
|
+
this.setAttribute("aria-label", label);
|
|
283
|
+
} else {
|
|
284
|
+
this.removeAttribute("aria-label");
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export class IncCloseButtonElement extends IncElement {
|
|
290
|
+
static get observedAttributes() {
|
|
291
|
+
return ["label", "variant"];
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
connectedCallback() {
|
|
295
|
+
addClass(this, "inc-close-button");
|
|
296
|
+
this.sync();
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
attributeChangedCallback() {
|
|
300
|
+
if (this.isConnected) {
|
|
301
|
+
this.sync();
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
sync() {
|
|
306
|
+
addClass(this, "inc-close-button");
|
|
307
|
+
this.setAttribute("part", "close-button");
|
|
308
|
+
const control = this.ensureControl();
|
|
309
|
+
const variant = normalizeToken(this.getAttribute("variant"));
|
|
310
|
+
|
|
311
|
+
control.className = "inc-close-button";
|
|
312
|
+
control.setAttribute("part", "control");
|
|
313
|
+
if (variant === "white") {
|
|
314
|
+
control.classList.add("inc-close-button--white");
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
control.type = "button";
|
|
318
|
+
control.setAttribute("aria-label", this.getAttribute("label") || "Close");
|
|
319
|
+
if (!control.childNodes.length) {
|
|
320
|
+
control.textContent = "×";
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
ensureControl() {
|
|
325
|
+
const existing = this._control || this.querySelector(":scope > button.inc-close-button");
|
|
326
|
+
if (existing) {
|
|
327
|
+
this._control = existing;
|
|
328
|
+
return existing;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const control = document.createElement("button");
|
|
332
|
+
const previous = this.querySelector(":scope > a.inc-close-button");
|
|
333
|
+
if (previous) {
|
|
334
|
+
moveChildNodes(previous, control);
|
|
335
|
+
previous.replaceWith(control);
|
|
336
|
+
} else {
|
|
337
|
+
moveChildNodes(this, control);
|
|
338
|
+
this.append(control);
|
|
339
|
+
}
|
|
340
|
+
this._control = control;
|
|
341
|
+
return control;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export class IncAlertElement extends IncElement {
|
|
346
|
+
static get observedAttributes() {
|
|
347
|
+
return ["tone", "variant", "dismissible", "dismiss-label"];
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
connectedCallback() {
|
|
351
|
+
addClass(this, "inc-alert");
|
|
352
|
+
this.bindEvents();
|
|
353
|
+
this.sync();
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
disconnectedCallback() {
|
|
357
|
+
if (this._boundClick) {
|
|
358
|
+
this.removeEventListener("click", this._boundClick);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
attributeChangedCallback() {
|
|
363
|
+
if (this.isConnected) {
|
|
364
|
+
this.sync();
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
bindEvents() {
|
|
369
|
+
if (this._boundClick) {
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
this._boundClick = (event) => {
|
|
374
|
+
const dismiss = event.target.closest("[data-inc-alert-dismiss]");
|
|
375
|
+
if (!dismiss || !this.contains(dismiss)) {
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
event.preventDefault();
|
|
380
|
+
this.hide();
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
this.addEventListener("click", this._boundClick);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
sync() {
|
|
387
|
+
addClass(this, "inc-alert");
|
|
388
|
+
this.setAttribute("part", "alert");
|
|
389
|
+
removeMatchingClasses(this, (token) => token.startsWith("inc-alert--"));
|
|
390
|
+
|
|
391
|
+
const tone = normalizeToken(this.getAttribute("tone") || this.getAttribute("variant")) || "info";
|
|
392
|
+
const resolvedTone = BADGE_TONES.has(tone) ? tone : "info";
|
|
393
|
+
this.classList.add(`inc-alert--${resolvedTone}`);
|
|
394
|
+
|
|
395
|
+
if (toBoolean(this.getAttribute("dismissible"))) {
|
|
396
|
+
this.classList.add("inc-alert--dismissible");
|
|
397
|
+
this.ensureDismissButton();
|
|
398
|
+
} else {
|
|
399
|
+
this.removeDismissButton();
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (!this.hasAttribute("role")) {
|
|
403
|
+
this.setAttribute("role", resolvedTone === "info" || resolvedTone === "secondary" ? "status" : "alert");
|
|
404
|
+
}
|
|
405
|
+
if (!this.hasAttribute("aria-live")) {
|
|
406
|
+
this.setAttribute("aria-live", this.getAttribute("role") === "alert" ? "assertive" : "polite");
|
|
407
|
+
}
|
|
408
|
+
this.setAttribute("aria-atomic", "true");
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
ensureDismissButton() {
|
|
412
|
+
let button = this.querySelector(":scope > [data-inc-alert-dismiss]");
|
|
413
|
+
if (!button) {
|
|
414
|
+
button = document.createElement("button");
|
|
415
|
+
button.type = "button";
|
|
416
|
+
button.dataset.incAlertDismiss = "true";
|
|
417
|
+
this.append(button);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
button.className = "inc-close-button";
|
|
421
|
+
button.setAttribute("part", "dismiss");
|
|
422
|
+
button.setAttribute("aria-label", this.getAttribute("dismiss-label") || "Dismiss alert");
|
|
423
|
+
if (!button.childNodes.length) {
|
|
424
|
+
button.textContent = "×";
|
|
425
|
+
}
|
|
426
|
+
return button;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
removeDismissButton() {
|
|
430
|
+
this.querySelectorAll(":scope > [data-inc-alert-dismiss]").forEach((node) => node.remove());
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
hide() {
|
|
434
|
+
this.hidden = true;
|
|
435
|
+
this.setAttribute("aria-hidden", "true");
|
|
436
|
+
this.emit("dismiss", { hidden: true });
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
export class IncEmptyStateElement extends IncElement {
|
|
441
|
+
connectedCallback() {
|
|
442
|
+
addClass(this, "inc-empty-state");
|
|
443
|
+
this.sync();
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
sync() {
|
|
447
|
+
addClass(this, "inc-empty-state");
|
|
448
|
+
this.setAttribute("part", "empty-state content icon body actions");
|
|
449
|
+
|
|
450
|
+
const content = ensureNode(this, ".inc-empty-state__content", () => {
|
|
451
|
+
const node = document.createElement("div");
|
|
452
|
+
node.className = "inc-empty-state__content";
|
|
453
|
+
node.innerHTML = [
|
|
454
|
+
'<div class="inc-empty-state__icon" part="icon"></div>',
|
|
455
|
+
'<div class="inc-empty-state__body" part="body"></div>',
|
|
456
|
+
'<div class="inc-empty-state__actions" part="actions"></div>',
|
|
457
|
+
].join("");
|
|
458
|
+
return node;
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
const icon = ensureNode(content, ".inc-empty-state__icon", () => {
|
|
462
|
+
const node = document.createElement("div");
|
|
463
|
+
node.className = "inc-empty-state__icon";
|
|
464
|
+
node.setAttribute("part", "icon");
|
|
465
|
+
return node;
|
|
466
|
+
});
|
|
467
|
+
const body = ensureNode(content, ".inc-empty-state__body", () => {
|
|
468
|
+
const node = document.createElement("div");
|
|
469
|
+
node.className = "inc-empty-state__body";
|
|
470
|
+
node.setAttribute("part", "body");
|
|
471
|
+
return node;
|
|
472
|
+
});
|
|
473
|
+
const actions = ensureNode(content, ".inc-empty-state__actions", () => {
|
|
474
|
+
const node = document.createElement("div");
|
|
475
|
+
node.className = "inc-empty-state__actions";
|
|
476
|
+
node.setAttribute("part", "actions");
|
|
477
|
+
return node;
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
Array.from(this.childNodes).forEach((node) => {
|
|
481
|
+
if (node === content) {
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (node.nodeType === Node.ELEMENT_NODE && node.getAttribute("slot") === "icon") {
|
|
486
|
+
node.removeAttribute("slot");
|
|
487
|
+
icon.append(node);
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
if (node.nodeType === Node.ELEMENT_NODE && node.getAttribute("slot") === "actions") {
|
|
492
|
+
node.removeAttribute("slot");
|
|
493
|
+
actions.append(node);
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
body.append(node);
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const actionDefinitions = [
|
|
503
|
+
["inc-button", IncButtonElement],
|
|
504
|
+
["inc-button-group", IncButtonGroupElement],
|
|
505
|
+
["inc-button-toolbar", IncButtonToolbarElement],
|
|
506
|
+
["inc-close-button", IncCloseButtonElement],
|
|
507
|
+
["inc-alert", IncAlertElement],
|
|
508
|
+
["inc-empty-state", IncEmptyStateElement],
|
|
509
|
+
];
|
|
510
|
+
|
|
511
|
+
const actionComponents = {
|
|
512
|
+
IncButtonElement,
|
|
513
|
+
IncButtonGroupElement,
|
|
514
|
+
IncButtonToolbarElement,
|
|
515
|
+
IncCloseButtonElement,
|
|
516
|
+
IncAlertElement,
|
|
517
|
+
IncEmptyStateElement,
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
function defineActionComponents(registry = globalThis.customElements) {
|
|
521
|
+
if (!registry || typeof registry.define !== "function" || typeof registry.get !== "function") {
|
|
522
|
+
return [];
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const defined = [];
|
|
526
|
+
for (const [tagName, ctor] of actionDefinitions) {
|
|
527
|
+
if (!registry.get(tagName)) {
|
|
528
|
+
registry.define(tagName, ctor);
|
|
529
|
+
defined.push(tagName);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
return defined;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
if (typeof module !== "undefined" && module.exports) {
|
|
537
|
+
module.exports = {
|
|
538
|
+
defineActionComponents,
|
|
539
|
+
actionDefinitions,
|
|
540
|
+
actionComponents,
|
|
541
|
+
IncButtonElement,
|
|
542
|
+
IncButtonGroupElement,
|
|
543
|
+
IncButtonToolbarElement,
|
|
544
|
+
IncCloseButtonElement,
|
|
545
|
+
IncAlertElement,
|
|
546
|
+
IncEmptyStateElement,
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
if (typeof globalThis !== "undefined") {
|
|
551
|
+
const namespace = globalThis.IncWebComponents || (globalThis.IncWebComponents = {});
|
|
552
|
+
namespace.actions = Object.assign({}, namespace.actions, {
|
|
553
|
+
defineActionComponents,
|
|
554
|
+
actionDefinitions,
|
|
555
|
+
components: actionComponents,
|
|
556
|
+
});
|
|
557
|
+
}
|