@nectary/components 5.38.1 → 5.39.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 (39) hide show
  1. package/bundle.d.ts +2 -0
  2. package/bundle.js +1689 -217
  3. package/bundle.ts +2 -0
  4. package/package.json +3 -3
  5. package/progress-stepper-item-v2/global/index.d.ts +1 -0
  6. package/progress-stepper-item-v2/global/index.js +2 -0
  7. package/progress-stepper-item-v2/index.d.ts +23 -0
  8. package/progress-stepper-item-v2/index.js +187 -0
  9. package/progress-stepper-item-v2/types.d.ts +69 -0
  10. package/progress-stepper-item-v2/types.js +1 -0
  11. package/progress-stepper-item-v2/utils.d.ts +22 -0
  12. package/progress-stepper-item-v2/utils.js +88 -0
  13. package/progress-stepper-v2/compact-format.d.ts +22 -0
  14. package/progress-stepper-v2/compact-format.js +56 -0
  15. package/progress-stepper-v2/compact.d.ts +26 -0
  16. package/progress-stepper-v2/compact.js +330 -0
  17. package/progress-stepper-v2/global/index.d.ts +1 -0
  18. package/progress-stepper-v2/global/index.js +2 -0
  19. package/progress-stepper-v2/index.d.ts +31 -0
  20. package/progress-stepper-v2/index.js +618 -0
  21. package/progress-stepper-v2/model.d.ts +32 -0
  22. package/progress-stepper-v2/model.js +59 -0
  23. package/progress-stepper-v2/orientation.d.ts +8 -0
  24. package/progress-stepper-v2/orientation.js +24 -0
  25. package/progress-stepper-v2/separators.d.ts +1 -0
  26. package/progress-stepper-v2/separators.js +48 -0
  27. package/progress-stepper-v2/step-chip.d.ts +9 -0
  28. package/progress-stepper-v2/step-chip.js +126 -0
  29. package/progress-stepper-v2/types.d.ts +66 -0
  30. package/progress-stepper-v2/types.js +1 -0
  31. package/standalone.d.ts +2 -0
  32. package/standalone.js +2 -0
  33. package/standalone.ts +2 -0
  34. package/utils/component-names.d.ts +2 -2
  35. package/utils/component-names.js +2 -0
  36. package/utils/dom.d.ts +1 -0
  37. package/utils/dom.js +9 -0
  38. package/utils/element.d.ts +2 -0
  39. package/utils/index.js +2 -1
@@ -0,0 +1,330 @@
1
+ import { isProgressStepperItemActive, ATTR_PROGRESS_STEPPER_ITEM_STATUS } from "../progress-stepper-item-v2/utils.js";
2
+ import { subscribeContext } from "../utils/context.js";
3
+ import { getBooleanAttribute, updateBooleanAttribute, getAttribute, createScopedElement, updateAttribute } from "../utils/dom.js";
4
+ import { resolveCompactMenuAndPopAriaLabel, formatCompactTemplate, resolveCompactCounterFormat, resolveCompactTriggerAriaLabel } from "./compact-format.js";
5
+ import { fillProgressStepperStepChipHost } from "./step-chip.js";
6
+ class ProgressStepperCompactController {
7
+ #$actionMenu;
8
+ #$compactPop;
9
+ #$compactTrigger;
10
+ #$compactLabel;
11
+ #$compactCounter;
12
+ #hostAriaLabel = null;
13
+ #compactCounterFormat = "";
14
+ #compactTriggerAriaLabelFormat = "";
15
+ #warnedMissingHostAriaLabel = false;
16
+ #compactCurrentIndex = -1;
17
+ constructor(elements) {
18
+ this.#$actionMenu = elements.actionMenu;
19
+ this.#$compactPop = elements.pop;
20
+ this.#$compactTrigger = elements.trigger;
21
+ this.#$compactLabel = elements.label;
22
+ this.#$compactCounter = elements.counter;
23
+ }
24
+ connect(signal) {
25
+ subscribeContext(this.#$actionMenu, "visibility", this.#onActionMenuVisibility, signal);
26
+ }
27
+ onTriggerClick(usesCompactActionMenu) {
28
+ if (!usesCompactActionMenu) {
29
+ return;
30
+ }
31
+ const willOpen = !getBooleanAttribute(this.#$compactPop, "open");
32
+ updateBooleanAttribute(this.#$compactPop, "open", willOpen);
33
+ }
34
+ onPopoverClose() {
35
+ updateBooleanAttribute(this.#$compactPop, "open", false);
36
+ queueMicrotask(() => this.#syncCompactMenuListboxSelection(false));
37
+ }
38
+ sync(params) {
39
+ const {
40
+ usesCompactActionMenu,
41
+ items,
42
+ hostAriaLabel,
43
+ compactCounterFormat,
44
+ compactTriggerAriaLabelFormat,
45
+ getCheckedItemIndex,
46
+ getFirstActiveItemIndex,
47
+ onChange
48
+ } = params;
49
+ if (!usesCompactActionMenu) {
50
+ this.#compactCurrentIndex = -1;
51
+ updateBooleanAttribute(this.#$compactPop, "open", false);
52
+ this.#clearCompactStateAttributes(this.#$compactTrigger);
53
+ this.#clearActionMenuChildren();
54
+ return;
55
+ }
56
+ this.#hostAriaLabel = hostAriaLabel;
57
+ this.#compactCounterFormat = compactCounterFormat;
58
+ this.#compactTriggerAriaLabelFormat = compactTriggerAriaLabelFormat;
59
+ this.#warnMissingHostAriaLabel(hostAriaLabel);
60
+ if (items.length === 0) {
61
+ this.#compactCurrentIndex = -1;
62
+ this.#clearActionMenuChildren();
63
+ this.#syncCompactTrigger(items, -1);
64
+ this.#syncCompactMenuAndPopAccessibleName(null, false);
65
+ return;
66
+ }
67
+ const currentIndex = this.#resolveCurrentIndex(items.length, getCheckedItemIndex, getFirstActiveItemIndex);
68
+ this.#compactCurrentIndex = currentIndex;
69
+ const formatVars = this.#buildCompactFormatVars(items, currentIndex);
70
+ this.#syncCompactMenuAndPopAccessibleName(formatVars, true);
71
+ if (this.#menuOptionsNeedFullRebuild(items)) {
72
+ this.#clearActionMenuChildren();
73
+ for (let i = 0; i < items.length; i++) {
74
+ const opt = this.#createMenuOptionForItem(items[i], onChange);
75
+ this.#$actionMenu.appendChild(opt);
76
+ this.#refreshCompactOptionPresentation(opt, items[i], i, i === currentIndex);
77
+ }
78
+ this.#syncCompactTrigger(items, currentIndex, formatVars);
79
+ this.#syncCompactMenuListboxSelection(false);
80
+ return;
81
+ }
82
+ for (let i = 0; i < items.length; i++) {
83
+ const item = items[i];
84
+ const opt = this.#$actionMenu.children[i];
85
+ opt.setAttribute("text", getAttribute(item, "text", ""));
86
+ opt.toggleAttribute("disabled", !isProgressStepperItemActive(item));
87
+ if (getBooleanAttribute(item, "invalid")) {
88
+ opt.setAttribute("aria-invalid", "true");
89
+ } else {
90
+ opt.removeAttribute("aria-invalid");
91
+ }
92
+ this.#refreshCompactOptionPresentation(opt, item, i, i === currentIndex);
93
+ }
94
+ this.#syncCompactTrigger(items, currentIndex, formatVars);
95
+ this.#syncCompactMenuListboxSelection(false);
96
+ }
97
+ #onActionMenuVisibility = (e) => {
98
+ if (e.detail) {
99
+ queueMicrotask(() => {
100
+ requestAnimationFrame(() => {
101
+ this.#syncCompactMenuListboxSelection(true);
102
+ });
103
+ });
104
+ return;
105
+ }
106
+ this.#syncCompactMenuListboxSelection(false);
107
+ };
108
+ /**
109
+ * `sinch-action-menu-option` maps `data-selected` to `aria-selected`. When the popover is open,
110
+ * highlight the first enabled row (listbox entry point). When closed, mark the current value step.
111
+ */
112
+ #syncCompactMenuListboxSelection(focusSelectedOption) {
113
+ if (getBooleanAttribute(this.#$compactPop, "open")) {
114
+ this.#syncCompactMenuOpenListboxSelection(focusSelectedOption);
115
+ return;
116
+ }
117
+ this.#syncCompactMenuClosedListboxSelection();
118
+ }
119
+ #syncCompactMenuOpenListboxSelection(focusSelectedOption) {
120
+ const count = this.#$actionMenu.childElementCount;
121
+ if (count === 0) {
122
+ return;
123
+ }
124
+ const selectedIndex = this.#getFirstActiveMenuOptionIndex();
125
+ if (selectedIndex < 0) {
126
+ return;
127
+ }
128
+ let selectedOption = null;
129
+ for (let i = 0; i < count; i++) {
130
+ const opt = this.#$actionMenu.children[i];
131
+ const isSelected = i === selectedIndex;
132
+ updateBooleanAttribute(opt, "data-selected", isSelected);
133
+ if (isSelected) {
134
+ selectedOption = opt;
135
+ }
136
+ }
137
+ if (focusSelectedOption && selectedOption !== null) {
138
+ selectedOption.focus();
139
+ }
140
+ }
141
+ #syncCompactMenuClosedListboxSelection() {
142
+ const count = this.#$actionMenu.childElementCount;
143
+ if (count === 0 || this.#compactCurrentIndex < 0) {
144
+ return;
145
+ }
146
+ for (let i = 0; i < count; i++) {
147
+ const opt = this.#$actionMenu.children[i];
148
+ const isCurrent = i === this.#compactCurrentIndex && !getBooleanAttribute(opt, "disabled");
149
+ updateBooleanAttribute(opt, "data-selected", isCurrent);
150
+ }
151
+ }
152
+ #getFirstActiveMenuOptionIndex() {
153
+ const count = this.#$actionMenu.childElementCount;
154
+ for (let i = 0; i < count; i++) {
155
+ const opt = this.#$actionMenu.children[i];
156
+ if (!getBooleanAttribute(opt, "disabled")) {
157
+ return i;
158
+ }
159
+ }
160
+ return -1;
161
+ }
162
+ #clearActionMenuChildren() {
163
+ while (this.#$actionMenu.firstChild !== null) {
164
+ this.#$actionMenu.removeChild(this.#$actionMenu.firstChild);
165
+ }
166
+ }
167
+ #menuOptionsNeedFullRebuild(items) {
168
+ if (this.#$actionMenu.childElementCount !== items.length) {
169
+ return true;
170
+ }
171
+ for (let i = 0; i < items.length; i++) {
172
+ const opt = this.#$actionMenu.children[i];
173
+ if (opt === void 0) {
174
+ return true;
175
+ }
176
+ if (getAttribute(opt, "data-step-value", "") !== getAttribute(items[i], "value", "")) {
177
+ return true;
178
+ }
179
+ }
180
+ return false;
181
+ }
182
+ #createMenuOptionForItem(item, onChange) {
183
+ const opt = createScopedElement(this.#$actionMenu, "sinch-action-menu-option");
184
+ const text = getAttribute(item, "text", "");
185
+ const value = getAttribute(item, "value", "");
186
+ opt.setAttribute("text", text);
187
+ opt.setAttribute("data-step-value", value);
188
+ opt.toggleAttribute("disabled", !isProgressStepperItemActive(item));
189
+ if (getBooleanAttribute(item, "invalid")) {
190
+ opt.setAttribute("aria-invalid", "true");
191
+ }
192
+ opt.addEventListener("-click", () => {
193
+ if (opt.hasAttribute("disabled")) {
194
+ return;
195
+ }
196
+ updateBooleanAttribute(this.#$compactPop, "open", false);
197
+ onChange(value);
198
+ });
199
+ return opt;
200
+ }
201
+ #buildCompactFormatVars(items, currentIndex) {
202
+ const labelIndex = currentIndex >= 0 ? currentIndex : 0;
203
+ const item = items[labelIndex];
204
+ return {
205
+ current: labelIndex + 1,
206
+ total: items.length,
207
+ step: getAttribute(item, "text", ""),
208
+ label: this.#hostAriaLabel ?? ""
209
+ };
210
+ }
211
+ #syncCompactMenuAndPopAccessibleName(formatVars, hasItems) {
212
+ if (!hasItems || formatVars === null) {
213
+ this.#$actionMenu.removeAttribute("aria-label");
214
+ this.#$compactPop.removeAttribute("aria-label");
215
+ return;
216
+ }
217
+ const name = resolveCompactMenuAndPopAriaLabel(
218
+ this.#hostAriaLabel,
219
+ this.#compactTriggerAriaLabelFormat,
220
+ formatVars
221
+ );
222
+ updateAttribute(this.#$actionMenu, "aria-label", name);
223
+ updateAttribute(this.#$compactPop, "aria-label", name);
224
+ }
225
+ #warnMissingHostAriaLabel(hostAriaLabel) {
226
+ if (hostAriaLabel != null && hostAriaLabel !== "") {
227
+ this.#warnedMissingHostAriaLabel = false;
228
+ return;
229
+ }
230
+ if (this.#warnedMissingHostAriaLabel) {
231
+ return;
232
+ }
233
+ this.#warnedMissingHostAriaLabel = true;
234
+ console.warn(
235
+ "[sinch-progress-stepper-v2] aria-label is recommended in compact layout so the step list has a concise widget name; a derived name from the current step is used when omitted."
236
+ );
237
+ }
238
+ #syncCompactTrigger(items, currentIndex, formatVars) {
239
+ const total = items.length;
240
+ if (total === 0) {
241
+ this.#$compactCounter.textContent = "";
242
+ this.#$compactLabel.textContent = " ";
243
+ this.#clearCompactStateAttributes(this.#$compactTrigger);
244
+ this.#$compactTrigger.removeAttribute("aria-label");
245
+ return;
246
+ }
247
+ const labelIndex = currentIndex >= 0 ? currentIndex : 0;
248
+ const item = items[labelIndex];
249
+ const vars = formatVars ?? this.#buildCompactFormatVars(items, currentIndex);
250
+ this.#$compactCounter.textContent = formatCompactTemplate(
251
+ resolveCompactCounterFormat(this.#compactCounterFormat),
252
+ vars
253
+ );
254
+ this.#$compactLabel.textContent = getAttribute(item, "text", " ");
255
+ updateAttribute(
256
+ this.#$compactTrigger,
257
+ "aria-label",
258
+ resolveCompactTriggerAriaLabel(
259
+ this.#compactTriggerAriaLabelFormat,
260
+ this.#hostAriaLabel,
261
+ vars
262
+ )
263
+ );
264
+ this.#syncCompactStateAttributes(this.#$compactTrigger, item, true);
265
+ }
266
+ #clearCompactStateAttributes(el) {
267
+ el.removeAttribute("data-step-status");
268
+ el.removeAttribute("data-step-checked");
269
+ el.removeAttribute("data-step-invalid");
270
+ }
271
+ #syncCompactStateAttributes(el, item, isCurrent = false) {
272
+ if (item === void 0) {
273
+ this.#clearCompactStateAttributes(el);
274
+ return;
275
+ }
276
+ updateAttribute(el, "data-step-status", getAttribute(item, ATTR_PROGRESS_STEPPER_ITEM_STATUS, "incomplete"));
277
+ updateBooleanAttribute(el, "data-step-checked", isCurrent);
278
+ updateBooleanAttribute(el, "data-step-invalid", getBooleanAttribute(item, "invalid"));
279
+ }
280
+ #ensureOptionIconSlot(opt) {
281
+ const found = opt.querySelector(':scope > [slot="icon"]');
282
+ if (found instanceof HTMLElement) {
283
+ return found;
284
+ }
285
+ const wrap = document.createElement("span");
286
+ wrap.slot = "icon";
287
+ opt.appendChild(wrap);
288
+ return wrap;
289
+ }
290
+ #renderCompactOptionIcon(wrap, item, index, isCurrent) {
291
+ Object.assign(wrap.style, {
292
+ display: "flex",
293
+ alignItems: "center",
294
+ justifyContent: "center",
295
+ flexShrink: "0",
296
+ marginLeft: "0"
297
+ });
298
+ const stepNum = String(index + 1);
299
+ const rawStepIndex = getAttribute(item, "data-step-index", "");
300
+ const stepIdx = rawStepIndex !== "" ? rawStepIndex : stepNum;
301
+ fillProgressStepperStepChipHost(wrap, {
302
+ stepIndexDisplay: stepIdx,
303
+ status: getAttribute(item, ATTR_PROGRESS_STEPPER_ITEM_STATUS, "incomplete"),
304
+ invalid: getBooleanAttribute(item, "invalid"),
305
+ checked: isCurrent
306
+ });
307
+ }
308
+ #refreshCompactOptionPresentation(opt, item, index, isCurrent) {
309
+ const wrap = this.#ensureOptionIconSlot(opt);
310
+ this.#renderCompactOptionIcon(wrap, item, index, isCurrent);
311
+ this.#syncCompactStateAttributes(opt, item, isCurrent);
312
+ }
313
+ #resolveCurrentIndex(totalItems, getCheckedItemIndex, getFirstActiveItemIndex) {
314
+ if (totalItems <= 0) {
315
+ return -1;
316
+ }
317
+ const checkedIndex = getCheckedItemIndex();
318
+ if (checkedIndex >= 0 && checkedIndex < totalItems) {
319
+ return checkedIndex;
320
+ }
321
+ const firstActiveIndex = getFirstActiveItemIndex();
322
+ if (firstActiveIndex >= 0 && firstActiveIndex < totalItems) {
323
+ return firstActiveIndex;
324
+ }
325
+ return 0;
326
+ }
327
+ }
328
+ export {
329
+ ProgressStepperCompactController
330
+ };
@@ -0,0 +1 @@
1
+ export * from '../types';
@@ -0,0 +1,2 @@
1
+ import { defineCustomElement } from "../../utils/element.js";
2
+ defineCustomElement("sinch-progress-stepper-v2");
@@ -0,0 +1,31 @@
1
+ import '../action-menu';
2
+ import '../action-menu-option';
3
+ import '../icon';
4
+ import '../pop';
5
+ import { NectaryElement } from '../utils';
6
+ import type { TRect } from '../types';
7
+ export * from './types';
8
+ export { computeOrientationLayout, deriveStepStatuses, } from './model';
9
+ export type { TDerivedStepStatus, TOrientationLayout, TOrientationMode, TStepItemSnapshot, } from './model';
10
+ export { OrientationScheduler } from './orientation';
11
+ export declare class ProgressStepperV2 extends NectaryElement {
12
+ #private;
13
+ constructor();
14
+ connectedCallback(): void;
15
+ disconnectedCallback(): void;
16
+ static get observedAttributes(): string[];
17
+ attributeChangedCallback(name: string, _: string | null, _newVal: string | null): void;
18
+ set value(value: string);
19
+ get value(): string;
20
+ set progressValue(value: string);
21
+ get progressValue(): string;
22
+ set noCompletionOrder(value: boolean);
23
+ get noCompletionOrder(): boolean;
24
+ set noAutoOrientation(value: boolean);
25
+ get noAutoOrientation(): boolean;
26
+ set compactCounterFormat(value: string);
27
+ get compactCounterFormat(): string;
28
+ set compactTriggerAriaLabelFormat(value: string);
29
+ get compactTriggerAriaLabelFormat(): string;
30
+ nthOptionRect(index: number): TRect | null;
31
+ }