@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,618 @@
1
+ import "../action-menu/index.js";
2
+ import "../action-menu-option/index.js";
3
+ import "../icon/index.js";
4
+ import "../pop/index.js";
5
+ import { TAG_PROGRESS_STEPPER, TAG_PROGRESS_STEPPER_ITEM, ATTR_PROGRESS_STEPPER_ITEM_STATUS, ATTR_PROGRESS_STEPPER_ITEM_LAYOUT, isProgressStepperItemActive, syncProgressStepperItemsRovingTabIndexes, setProgressStepperItemChecked, getProgressStepperFocusedItemIndex, setProgressStepperItemStatus, isProgressStepperItemChecked } from "../progress-stepper-item-v2/utils.js";
6
+ import { updateAttribute, getAttribute, updateBooleanAttribute, getBooleanAttribute } from "../utils/dom.js";
7
+ import { defineCustomElement, NectaryElement } from "../utils/element.js";
8
+ import { getRect } from "../utils/rect.js";
9
+ import { getReactEventHandler } from "../utils/get-react-event-handler.js";
10
+ import { getTargetByAttribute } from "../utils/event-target.js";
11
+ import { ProgressStepperCompactController } from "./compact.js";
12
+ import { computeOrientationLayout, deriveStepStatuses } from "./model.js";
13
+ import { OrientationScheduler } from "./orientation.js";
14
+ import { syncStepSeparators } from "./separators.js";
15
+ const templateHTML = '<style>:host{display:block;position:relative}#wrapper{display:flex;width:100%;align-items:stretch}#wrapper[data-orientation=horizontal]{flex-direction:row}#wrapper[data-orientation=horizontal] ::slotted(sinch-progress-stepper-item-v2){flex:0 0 auto;align-self:stretch;min-width:0}#wrapper[data-orientation=horizontal] ::slotted(sinch-icon[data-nectary-progress-stepper-v2-separator]){display:flex;flex-shrink:0;align-items:center;align-self:center;box-sizing:border-box;min-inline-size:24px;padding-inline:4px;pointer-events:none;--sinch-global-size-icon:24px;--sinch-global-color-icon:var(--sinch-comp-progress-stepper-v2-step-color-chevron)}#wrapper[data-orientation=vertical] ::slotted(sinch-icon[data-nectary-progress-stepper-v2-separator]){display:none!important}:host(:dir(rtl)) #wrapper[data-orientation=horizontal] ::slotted(sinch-icon[data-nectary-progress-stepper-v2-separator]){transform:scaleX(-1)}#wrapper[data-orientation=vertical]{position:absolute;width:100%;max-width:100%;height:0;overflow:hidden;visibility:hidden;pointer-events:none;flex-direction:row;align-items:stretch}#wrapper[data-orientation=vertical] ::slotted(sinch-progress-stepper-item-v2){flex:0 0 auto}#menu-wrapper{display:none;width:100%}#menu-wrapper[data-visible=true]{display:block;width:100%}#menu-wrapper[data-visible=true] sinch-pop{width:100%}#menu-wrapper[data-visible=true] #compact-stepper-pop-panel-gap{position:relative;padding-top:0;width:fit-content;min-width:100%;box-sizing:border-box}#menu-wrapper[data-visible=true] #compact-stepper-pop-panel{background-color:var(--sinch-comp-progress-stepper-v2-step-color-compact-background);border:1px solid var(--sinch-comp-progress-stepper-v2-step-color-compact-border);border-radius:var(--sinch-comp-popover-shape-radius);box-shadow:var(--sinch-comp-popover-shadow);overflow:hidden}#menu-wrapper[data-visible=true] #compact-stepper-trigger{all:unset;display:flex;align-items:center;gap:8px;width:100%;max-width:100%;min-height:var(--sinch-comp-button-size-container-l);padding:8px 10px;box-sizing:border-box;border:1px solid var(--sinch-comp-progress-stepper-v2-step-color-compact-border);border-radius:var(--sinch-comp-popover-shape-radius);background-color:var(--sinch-comp-progress-stepper-v2-step-color-compact-background);cursor:pointer;user-select:none}#menu-wrapper[data-visible=true] #compact-stepper-trigger:focus-visible{outline:2px solid var(--sinch-sys-color-focus);outline-offset:2px}#menu-wrapper[data-visible=true] #compact-stepper-label{flex:1 1 auto;min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;text-align:start;font:var(--sinch-comp-progress-stepper-v2-step-font-label-initial);color:var(--sinch-comp-progress-stepper-v2-step-color-complete-label-initial)}#menu-wrapper[data-visible=true] #compact-stepper-chevron{flex-shrink:0;color:var(--sinch-comp-progress-stepper-v2-step-color-compact-chevron);pointer-events:none;--sinch-global-color-icon:currentcolor;--sinch-global-size-icon:24px}#menu-wrapper[data-visible=true] #compact-stepper-counter{display:inline-flex;flex-shrink:0;align-items:center;justify-content:center;box-sizing:border-box;height:24px;min-height:24px;padding:2px 9px;border:none;border-radius:var(--sinch-comp-progress-stepper-v2-step-shape-radius-indicator);background-color:var(--sinch-comp-progress-stepper-v2-step-color-incomplete-icon-container-initial);color:var(--sinch-comp-progress-stepper-v2-step-color-incomplete-icon-initial);user-select:none;white-space:nowrap;font:var(--sinch-sys-font-body-s);line-height:1}#menu-wrapper[data-visible=true] #compact-stepper-trigger[data-step-invalid] #compact-stepper-counter{background-color:var(--sinch-comp-progress-stepper-v2-step-color-invalid-icon-container-initial);color:var(--sinch-comp-progress-stepper-v2-step-color-invalid-icon-initial)}#menu-wrapper[data-visible=true] #compact-stepper-trigger[data-step-status=complete]:not([data-step-invalid]) #compact-stepper-counter{background-color:var(--sinch-comp-progress-stepper-v2-step-color-complete-icon-container-initial);color:var(--sinch-comp-progress-stepper-v2-step-color-complete-icon-initial)}#menu-wrapper[data-visible=true] sinch-action-menu{display:block;width:100%;box-sizing:border-box}#menu-wrapper[data-visible=true] sinch-action-menu-option{width:100%;box-sizing:border-box;outline:0;-webkit-tap-highlight-color:transparent;--sinch-comp-text-align:start}#menu-wrapper[data-visible=true] sinch-action-menu-option:focus,#menu-wrapper[data-visible=true] sinch-action-menu-option:focus-visible{outline:0}#menu-wrapper[data-visible=true] sinch-action-menu-option:is([data-step-status=incomplete],[data-step-status=inactive]):not([data-step-invalid]){--sinch-comp-action-menu-font-option:var(--sinch-comp-progress-stepper-v2-step-font-label-initial);--sinch-comp-action-menu-color-default-text-initial:var(--sinch-comp-progress-stepper-v2-step-color-incomplete-label-initial);--sinch-comp-action-menu-color-default-background-hover:var(--sinch-comp-progress-stepper-v2-step-color-incomplete-background-hover);--sinch-comp-action-menu-color-disabled-text-initial:var(--sinch-comp-progress-stepper-v2-step-color-incomplete-label-initial)}#menu-wrapper[data-visible=true] sinch-action-menu-option:is([data-step-status=incomplete],[data-step-status=inactive]):not([data-step-invalid])[data-step-checked]{--sinch-comp-action-menu-font-option:var(--sinch-comp-progress-stepper-v2-step-font-label-current);--sinch-comp-action-menu-color-default-text-initial:var(--sinch-comp-progress-stepper-v2-step-color-incomplete-label-current)}#menu-wrapper[data-visible=true] sinch-action-menu-option[data-step-status=complete]:not([data-step-invalid]){--sinch-comp-action-menu-font-option:var(--sinch-comp-progress-stepper-v2-step-font-label-initial);--sinch-comp-action-menu-color-default-text-initial:var(--sinch-comp-progress-stepper-v2-step-color-complete-label-initial);--sinch-comp-action-menu-color-default-background-hover:var(--sinch-comp-progress-stepper-v2-step-color-complete-background-hover)}#menu-wrapper[data-visible=true] sinch-action-menu-option[data-step-status=complete]:not([data-step-invalid])[data-step-checked]{--sinch-comp-action-menu-font-option:var(--sinch-comp-progress-stepper-v2-step-font-label-current);--sinch-comp-action-menu-color-default-text-initial:var(--sinch-comp-progress-stepper-v2-step-color-complete-label-current)}#menu-wrapper[data-visible=true] sinch-action-menu-option[data-step-invalid]{--sinch-comp-action-menu-font-option:var(--sinch-comp-progress-stepper-v2-step-font-label-initial);--sinch-comp-action-menu-color-default-text-initial:var(--sinch-comp-progress-stepper-v2-step-color-invalid-label-initial);--sinch-comp-action-menu-color-default-background-hover:var(--sinch-comp-progress-stepper-v2-step-color-invalid-background-hover)}#menu-wrapper[data-visible=true] sinch-action-menu-option[data-step-invalid][data-step-checked]{--sinch-comp-action-menu-font-option:var(--sinch-comp-progress-stepper-v2-step-font-label-current);--sinch-comp-action-menu-color-default-text-initial:var(--sinch-comp-progress-stepper-v2-step-color-invalid-label-current)}</style><div id="wrapper" data-orientation="horizontal"><slot></slot></div><div id="menu-wrapper" data-visible="false"><sinch-pop id="compact-stepper-pop" inset="4" orientation="bottom-stretch" allow-scroll><button id="compact-stepper-trigger" slot="target" type="button" tabindex="0"><span id="compact-stepper-counter" aria-hidden="true"></span> <span id="compact-stepper-label"></span><sinch-icon id="compact-stepper-chevron" icons-version="2" name="fa-chevron-down" aria-hidden="true"></sinch-icon></button><div id="compact-stepper-pop-panel-gap" slot="content"><div id="compact-stepper-pop-panel"><sinch-action-menu></sinch-action-menu></div></div></sinch-pop></div>';
16
+ const template = document.createElement("template");
17
+ template.innerHTML = templateHTML;
18
+ const ITEM_ATTR_FILTER = [
19
+ "text",
20
+ "invalid",
21
+ "value",
22
+ "complete",
23
+ "disabled",
24
+ ATTR_PROGRESS_STEPPER_ITEM_STATUS,
25
+ "data-checked"
26
+ ];
27
+ const ITEM_ATTR_RECOMPUTE_STEP_STATUS = /* @__PURE__ */ new Set(["complete", "disabled", "value"]);
28
+ const ATTR_PROGRESS_STEPPER_ITEM_INDEX = "data-step-index";
29
+ const ATTR_PROGRESS_STEPPER_ITEM_FORCE_FOCUS_VISIBLE = "data-force-focus-visible";
30
+ class ProgressStepperV2 extends NectaryElement {
31
+ #$slot;
32
+ #$wrapper;
33
+ #$menuWrapper;
34
+ #$actionMenu;
35
+ #$compactPop;
36
+ #$compactTrigger;
37
+ #$compactLabel;
38
+ #$compactCounter;
39
+ #controller = null;
40
+ #$items = [];
41
+ #isGoingToFocusItem = false;
42
+ #resizeObserver;
43
+ #isHorizontal = true;
44
+ #usesCompactActionMenu = false;
45
+ #itemAttrObserver = null;
46
+ #childListObserver = null;
47
+ #orientationScheduler = new OrientationScheduler(() => this.#scheduleRender({ orientation: true }));
48
+ #renderQueued = false;
49
+ #renderNeedsItems = false;
50
+ #renderNeedsValue = false;
51
+ #renderNeedsStepStatuses = false;
52
+ #renderNeedsOrientation = false;
53
+ #renderNeedsCompactSync = false;
54
+ #renderShouldFocusValue = false;
55
+ #compactController;
56
+ constructor() {
57
+ super();
58
+ const shadowRoot = this.attachShadow();
59
+ shadowRoot.appendChild(template.content.cloneNode(true));
60
+ this.#$slot = shadowRoot.querySelector("slot:not([name])");
61
+ this.#$wrapper = shadowRoot.querySelector("#wrapper");
62
+ this.#$menuWrapper = shadowRoot.querySelector("#menu-wrapper");
63
+ this.#$compactPop = shadowRoot.querySelector("#compact-stepper-pop");
64
+ this.#$compactTrigger = shadowRoot.querySelector("#compact-stepper-trigger");
65
+ this.#$compactLabel = shadowRoot.querySelector("#compact-stepper-label");
66
+ this.#$compactCounter = shadowRoot.querySelector("#compact-stepper-counter");
67
+ this.#$actionMenu = shadowRoot.querySelector("sinch-action-menu");
68
+ this.#compactController = new ProgressStepperCompactController({
69
+ actionMenu: this.#$actionMenu,
70
+ pop: this.#$compactPop,
71
+ trigger: this.#$compactTrigger,
72
+ label: this.#$compactLabel,
73
+ counter: this.#$compactCounter
74
+ });
75
+ this.#resizeObserver = new ResizeObserver(this.#onResize);
76
+ }
77
+ connectedCallback() {
78
+ this.setAttribute("data-orientation", "horizontal");
79
+ this.#controller = new AbortController();
80
+ const { signal } = this.#controller;
81
+ const options = { signal };
82
+ this.#updateHostAriaRole();
83
+ this.#$slot.addEventListener("click", this.#onOptionClick, options);
84
+ this.#$slot.addEventListener("keydown", this.#onOptionKeydown, options);
85
+ this.addEventListener("focusout", this.#onStepperFocusOut, { ...options, capture: true });
86
+ this.addEventListener("-change", this.#onChangeReactHandler, options);
87
+ this.addEventListener("focusin", this.#onHostFocusInCompactOnly, options);
88
+ this.#$compactTrigger.addEventListener("click", this.#onCompactTriggerClick, options);
89
+ this.#$compactPop.addEventListener("-close", this.#onCompactPopoverClose, options);
90
+ this.#compactController.connect(signal);
91
+ this.#resizeObserver.observe(this);
92
+ this.#resizeObserver.observe(this.#$wrapper);
93
+ this.#setupChildListObserver();
94
+ this.#primeAssignedSlotStepStatuses();
95
+ window.addEventListener("resize", this.#onResize, { signal });
96
+ queueMicrotask(() => {
97
+ this.#$slot.addEventListener("slotchange", this.#onSlotChange, options);
98
+ this.#scheduleRender({ items: true });
99
+ });
100
+ }
101
+ disconnectedCallback() {
102
+ this.#orientationScheduler.cancel();
103
+ this.#controller.abort();
104
+ this.#controller = null;
105
+ this.#resizeObserver.disconnect();
106
+ this.#teardownItemAttrObserver();
107
+ this.#teardownChildListObserver();
108
+ }
109
+ static get observedAttributes() {
110
+ return [
111
+ "value",
112
+ "progressvalue",
113
+ "nocompletionorder",
114
+ "noautoorientation",
115
+ "aria-label",
116
+ "compact-counter-format",
117
+ "compact-trigger-aria-label-format"
118
+ ];
119
+ }
120
+ attributeChangedCallback(name, _, _newVal) {
121
+ switch (name) {
122
+ case "value": {
123
+ const activeElement = document.activeElement;
124
+ const shouldFocusValue = activeElement === this || activeElement != null && this.contains(activeElement);
125
+ this.#scheduleRender({
126
+ value: true,
127
+ compact: true,
128
+ focusValue: shouldFocusValue
129
+ });
130
+ break;
131
+ }
132
+ case "progressvalue":
133
+ case "nocompletionorder": {
134
+ this.#scheduleRender({
135
+ stepStatuses: true,
136
+ compact: true
137
+ });
138
+ break;
139
+ }
140
+ case "noautoorientation": {
141
+ this.#scheduleRender({
142
+ orientation: true,
143
+ compact: true
144
+ });
145
+ break;
146
+ }
147
+ case "aria-label":
148
+ case "compact-counter-format":
149
+ case "compact-trigger-aria-label-format": {
150
+ this.#scheduleRender({ compact: true });
151
+ break;
152
+ }
153
+ }
154
+ }
155
+ set value(value) {
156
+ updateAttribute(this, "value", value);
157
+ }
158
+ get value() {
159
+ return getAttribute(this, "value", "");
160
+ }
161
+ set progressValue(value) {
162
+ updateAttribute(this, "progressvalue", value);
163
+ }
164
+ get progressValue() {
165
+ return getAttribute(this, "progressvalue", "");
166
+ }
167
+ set noCompletionOrder(value) {
168
+ updateBooleanAttribute(this, "nocompletionorder", value);
169
+ }
170
+ get noCompletionOrder() {
171
+ return getBooleanAttribute(this, "nocompletionorder");
172
+ }
173
+ set noAutoOrientation(value) {
174
+ updateBooleanAttribute(this, "noautoorientation", value);
175
+ }
176
+ get noAutoOrientation() {
177
+ return getBooleanAttribute(this, "noautoorientation");
178
+ }
179
+ set compactCounterFormat(value) {
180
+ updateAttribute(this, "compact-counter-format", value);
181
+ }
182
+ get compactCounterFormat() {
183
+ return getAttribute(this, "compact-counter-format", "");
184
+ }
185
+ set compactTriggerAriaLabelFormat(value) {
186
+ updateAttribute(this, "compact-trigger-aria-label-format", value);
187
+ }
188
+ get compactTriggerAriaLabelFormat() {
189
+ return getAttribute(this, "compact-trigger-aria-label-format", "");
190
+ }
191
+ nthOptionRect(index) {
192
+ if (this.#usesCompactActionMenu) {
193
+ const $opt = this.#$actionMenu.children[index];
194
+ if ($opt != null) {
195
+ return getRect($opt);
196
+ }
197
+ return null;
198
+ }
199
+ const $el = this.#$items[index];
200
+ if ($el != null) {
201
+ return getRect($el);
202
+ }
203
+ return null;
204
+ }
205
+ #scheduleRender({
206
+ items = false,
207
+ value = false,
208
+ stepStatuses = false,
209
+ orientation = false,
210
+ compact = false,
211
+ focusValue = false
212
+ }) {
213
+ this.#renderNeedsItems ||= items;
214
+ this.#renderNeedsValue ||= value;
215
+ this.#renderNeedsStepStatuses ||= stepStatuses;
216
+ this.#renderNeedsOrientation ||= orientation;
217
+ this.#renderNeedsCompactSync ||= compact;
218
+ this.#renderShouldFocusValue ||= focusValue;
219
+ if (this.#renderQueued) {
220
+ return;
221
+ }
222
+ this.#renderQueued = true;
223
+ queueMicrotask(() => this.#flushRender());
224
+ }
225
+ #flushRender() {
226
+ this.#renderQueued = false;
227
+ const needsItems = this.#renderNeedsItems;
228
+ let needsValue = this.#renderNeedsValue;
229
+ let needsStepStatuses = this.#renderNeedsStepStatuses;
230
+ let needsOrientation = this.#renderNeedsOrientation;
231
+ let needsCompactSync = this.#renderNeedsCompactSync;
232
+ const shouldFocusValue = this.#renderShouldFocusValue;
233
+ this.#renderNeedsItems = false;
234
+ this.#renderNeedsValue = false;
235
+ this.#renderNeedsStepStatuses = false;
236
+ this.#renderNeedsOrientation = false;
237
+ this.#renderNeedsCompactSync = false;
238
+ this.#renderShouldFocusValue = false;
239
+ if (needsItems) {
240
+ this.#$items = this.#getAssignedStepItems();
241
+ this.#updateStepIndexes();
242
+ this.#setupItemAttrObserver();
243
+ needsValue = true;
244
+ needsStepStatuses = true;
245
+ needsOrientation = true;
246
+ needsCompactSync = true;
247
+ }
248
+ if (needsValue) {
249
+ this.#onValueChange(this.value, shouldFocusValue);
250
+ }
251
+ if (needsStepStatuses) {
252
+ this.#syncStepItemStatuses();
253
+ }
254
+ if (needsValue || needsStepStatuses) {
255
+ needsCompactSync = true;
256
+ }
257
+ if (needsOrientation) {
258
+ this.#updateOrientation();
259
+ needsCompactSync = true;
260
+ }
261
+ if (needsCompactSync) {
262
+ this.#syncCompactMenuOptions();
263
+ }
264
+ }
265
+ #onSlotChange = () => {
266
+ this.#primeAssignedSlotStepStatuses();
267
+ this.#scheduleRender({ items: true });
268
+ };
269
+ #updateStepIndexes() {
270
+ for (let i = 0; i < this.#$items.length; i++) {
271
+ this.#$items[i].setAttribute(ATTR_PROGRESS_STEPPER_ITEM_INDEX, String(i + 1));
272
+ }
273
+ }
274
+ #getAssignedStepItems() {
275
+ return [...this.#$slot.assignedElements()].filter(
276
+ (el) => el.tagName.toLowerCase() === TAG_PROGRESS_STEPPER_ITEM
277
+ );
278
+ }
279
+ #setupChildListObserver() {
280
+ this.#teardownChildListObserver();
281
+ this.#childListObserver = new MutationObserver(() => {
282
+ this.#primeAssignedSlotStepStatuses();
283
+ this.#scheduleRender({ items: true });
284
+ });
285
+ this.#childListObserver.observe(this, { childList: true });
286
+ }
287
+ #teardownChildListObserver() {
288
+ this.#childListObserver?.disconnect();
289
+ this.#childListObserver = null;
290
+ }
291
+ #onResize = () => {
292
+ this.#orientationScheduler.schedule();
293
+ };
294
+ #updateHostAriaRole() {
295
+ if (this.#usesCompactActionMenu) {
296
+ this.setAttribute("role", "group");
297
+ } else {
298
+ this.setAttribute("role", "tablist");
299
+ }
300
+ }
301
+ #teardownItemAttrObserver() {
302
+ this.#itemAttrObserver?.disconnect();
303
+ this.#itemAttrObserver = null;
304
+ }
305
+ #setupItemAttrObserver() {
306
+ this.#teardownItemAttrObserver();
307
+ if (this.#$items.length === 0) {
308
+ return;
309
+ }
310
+ this.#itemAttrObserver = new MutationObserver((records) => {
311
+ const needsStepStatusSync = records.some(
312
+ (r) => r.type === "attributes" && r.attributeName != null && ITEM_ATTR_RECOMPUTE_STEP_STATUS.has(r.attributeName)
313
+ );
314
+ if (needsStepStatusSync) {
315
+ this.#scheduleRender({
316
+ stepStatuses: true,
317
+ compact: true
318
+ });
319
+ } else {
320
+ this.#scheduleRender({ compact: true });
321
+ }
322
+ });
323
+ for (const el of this.#$items) {
324
+ this.#itemAttrObserver.observe(el, {
325
+ attributes: true,
326
+ attributeFilter: [...ITEM_ATTR_FILTER]
327
+ });
328
+ }
329
+ }
330
+ #updateOrientation() {
331
+ if (!this.isConnected || !this.#$wrapper.isConnected) {
332
+ return;
333
+ }
334
+ const containerWidth = Math.max(this.clientWidth, 1);
335
+ syncStepSeparators(this, TAG_PROGRESS_STEPPER_ITEM);
336
+ this.#$wrapper.dataset.orientation = "horizontal";
337
+ this.#syncItemLayoutAttributes(true);
338
+ let wrapperScrollWidth = 0;
339
+ if (!this.noAutoOrientation) {
340
+ void this.#$wrapper.offsetWidth;
341
+ wrapperScrollWidth = this.#$wrapper.scrollWidth;
342
+ }
343
+ const layout = computeOrientationLayout({
344
+ noAutoOrientation: this.noAutoOrientation,
345
+ containerWidth,
346
+ wrapperScrollWidth
347
+ });
348
+ const { isHorizontal, dataOrientation: orientation, mode } = layout;
349
+ const usesCompactActionMenu = mode === "compact";
350
+ this.#$wrapper.dataset.orientation = orientation;
351
+ this.setAttribute("data-orientation", orientation);
352
+ this.#$menuWrapper.dataset.visible = usesCompactActionMenu ? "true" : "false";
353
+ this.#$wrapper.toggleAttribute("inert", usesCompactActionMenu);
354
+ const prevCompact = this.#usesCompactActionMenu;
355
+ this.#usesCompactActionMenu = usesCompactActionMenu;
356
+ if (prevCompact !== usesCompactActionMenu) {
357
+ this.#updateHostAriaRole();
358
+ }
359
+ if (isHorizontal !== this.#isHorizontal) {
360
+ this.#isHorizontal = isHorizontal;
361
+ }
362
+ if (!isHorizontal) {
363
+ this.#syncItemLayoutAttributes(false);
364
+ }
365
+ }
366
+ #syncItemLayoutAttributes(isHorizontal) {
367
+ const layout = isHorizontal ? "horizontal" : "vertical";
368
+ for (const item of this.#$items) {
369
+ updateAttribute(item, ATTR_PROGRESS_STEPPER_ITEM_LAYOUT, layout);
370
+ }
371
+ }
372
+ #onCompactTriggerClick = () => {
373
+ this.#compactController.onTriggerClick(this.#usesCompactActionMenu);
374
+ };
375
+ #onCompactPopoverClose = () => {
376
+ this.#compactController.onPopoverClose();
377
+ };
378
+ #syncCompactMenuOptions() {
379
+ this.#compactController.sync({
380
+ usesCompactActionMenu: this.#usesCompactActionMenu,
381
+ items: this.#$items,
382
+ hostAriaLabel: this.getAttribute("aria-label"),
383
+ compactCounterFormat: getAttribute(this, "compact-counter-format", ""),
384
+ compactTriggerAriaLabelFormat: getAttribute(this, "compact-trigger-aria-label-format", ""),
385
+ getCheckedItemIndex: () => this.#getCheckedItemIndex(),
386
+ getFirstActiveItemIndex: () => this.#getFirstActiveItemIndex(),
387
+ onChange: (value) => {
388
+ this.dispatchEvent(new CustomEvent("-change", {
389
+ detail: value
390
+ }));
391
+ }
392
+ });
393
+ }
394
+ #onOptionClick = (e) => {
395
+ if (this.#usesCompactActionMenu) {
396
+ return;
397
+ }
398
+ this.#clearForcedFocusVisible();
399
+ const target = getTargetByAttribute(e, "value");
400
+ if (target !== null && isProgressStepperItemActive(target)) {
401
+ this.dispatchEvent(new CustomEvent("-change", {
402
+ detail: getAttribute(target, "value", "")
403
+ }));
404
+ }
405
+ };
406
+ #onValueChange(value, shouldFocus) {
407
+ for (const $item of this.#$items) {
408
+ const isChecked = value === getAttribute($item, "value");
409
+ if (shouldFocus && isChecked && this.#isItemKeyboardFocusable($item)) {
410
+ requestAnimationFrame(() => {
411
+ if (this.#usesCompactActionMenu) {
412
+ this.#$compactTrigger.focus();
413
+ } else {
414
+ $item.focus();
415
+ syncProgressStepperItemsRovingTabIndexes(this.#$items);
416
+ }
417
+ });
418
+ }
419
+ setProgressStepperItemChecked($item, isChecked);
420
+ }
421
+ queueMicrotask(() => syncProgressStepperItemsRovingTabIndexes(this.#$items));
422
+ }
423
+ #isItemKeyboardFocusable(item) {
424
+ return !getBooleanAttribute(item, "disabled") && isProgressStepperItemActive(item);
425
+ }
426
+ #findFirstKeyboardFocusableIndex() {
427
+ for (let i = 0; i < this.#$items.length; i++) {
428
+ if (this.#isItemKeyboardFocusable(this.#$items[i])) {
429
+ return i;
430
+ }
431
+ }
432
+ return -1;
433
+ }
434
+ #getArrowNavigationOriginIndex(e) {
435
+ const focusIdx = getProgressStepperFocusedItemIndex(
436
+ this.#$items,
437
+ document.activeElement,
438
+ e
439
+ );
440
+ if (focusIdx >= 0 && this.#isItemKeyboardFocusable(this.#$items[focusIdx])) {
441
+ return focusIdx;
442
+ }
443
+ const checkedIdx = this.#getCheckedItemIndex();
444
+ if (checkedIdx >= 0 && this.#isItemKeyboardFocusable(this.#$items[checkedIdx])) {
445
+ return checkedIdx;
446
+ }
447
+ return this.#findFirstKeyboardFocusableIndex();
448
+ }
449
+ #focusStepItemAtIndex(nextIndex) {
450
+ const item = this.#$items[nextIndex];
451
+ if (!this.#isItemKeyboardFocusable(item)) {
452
+ return;
453
+ }
454
+ this.#setForcedFocusVisible(item);
455
+ const alreadyFocused = getProgressStepperFocusedItemIndex(this.#$items, document.activeElement) === nextIndex;
456
+ if (!alreadyFocused) {
457
+ this.#isGoingToFocusItem = true;
458
+ item.focus();
459
+ }
460
+ syncProgressStepperItemsRovingTabIndexes(this.#$items);
461
+ }
462
+ #focusNextItem(e) {
463
+ if (this.#usesCompactActionMenu) {
464
+ return;
465
+ }
466
+ const origin = this.#getArrowNavigationOriginIndex(e);
467
+ if (origin < 0) {
468
+ return;
469
+ }
470
+ for (let i = 0; i < this.#$items.length; i++) {
471
+ const nextIndex = (origin + 1 + i) % this.#$items.length;
472
+ if (this.#isItemKeyboardFocusable(this.#$items[nextIndex])) {
473
+ this.#focusStepItemAtIndex(nextIndex);
474
+ break;
475
+ }
476
+ }
477
+ }
478
+ #focusPrevItem(e) {
479
+ if (this.#usesCompactActionMenu) {
480
+ return;
481
+ }
482
+ const origin = this.#getArrowNavigationOriginIndex(e);
483
+ if (origin < 0) {
484
+ return;
485
+ }
486
+ for (let i = 0; i < this.#$items.length; i++) {
487
+ const nextIndex = (origin - i - 1 + this.#$items.length) % this.#$items.length;
488
+ if (this.#isItemKeyboardFocusable(this.#$items[nextIndex])) {
489
+ this.#focusStepItemAtIndex(nextIndex);
490
+ break;
491
+ }
492
+ }
493
+ }
494
+ /**
495
+ * Apply derived `data-status` before the queued `#flushRender` microtask so styling/state match
496
+ * assigned slot items on the first paint (frameworks may leave `data-status` as `""` briefly).
497
+ */
498
+ #primeAssignedSlotStepStatuses() {
499
+ this.#deriveAndApplyStepStatuses(this.#getAssignedStepItems());
500
+ }
501
+ #deriveAndApplyStepStatuses($items) {
502
+ if ($items.length === 0) {
503
+ return;
504
+ }
505
+ const snapshots = $items.map((item) => ({
506
+ value: getAttribute(item, "value", ""),
507
+ disabled: getBooleanAttribute(item, "disabled"),
508
+ complete: getBooleanAttribute(item, "complete")
509
+ }));
510
+ const statuses = deriveStepStatuses(snapshots, {
511
+ noCompletionOrder: this.noCompletionOrder,
512
+ progressValue: this.progressValue
513
+ });
514
+ for (let i = 0; i < $items.length; i++) {
515
+ setProgressStepperItemStatus($items[i], statuses[i]);
516
+ }
517
+ }
518
+ #syncStepItemStatuses() {
519
+ this.#deriveAndApplyStepStatuses(this.#$items);
520
+ }
521
+ #getCheckedItemIndex() {
522
+ for (let i = 0; i < this.#$items.length; i++) {
523
+ if (isProgressStepperItemChecked(this.#$items[i])) {
524
+ return i;
525
+ }
526
+ }
527
+ return -1;
528
+ }
529
+ #getFirstActiveItemIndex() {
530
+ for (let i = 0; i < this.#$items.length; i++) {
531
+ if (isProgressStepperItemActive(this.#$items[i])) {
532
+ return i;
533
+ }
534
+ }
535
+ return -1;
536
+ }
537
+ #onOptionKeydown = (e) => {
538
+ if (this.#usesCompactActionMenu) {
539
+ return;
540
+ }
541
+ switch (e.code) {
542
+ case "Enter":
543
+ case "Space": {
544
+ e.preventDefault();
545
+ getTargetByAttribute(e, "value")?.click();
546
+ break;
547
+ }
548
+ case "ArrowRight": {
549
+ e.preventDefault();
550
+ this.#focusNextItem(e);
551
+ break;
552
+ }
553
+ case "ArrowLeft": {
554
+ e.preventDefault();
555
+ this.#focusPrevItem(e);
556
+ break;
557
+ }
558
+ }
559
+ };
560
+ #onStepperFocusOut = (e) => {
561
+ if (this.#usesCompactActionMenu) {
562
+ return;
563
+ }
564
+ if (this.#isGoingToFocusItem) {
565
+ this.#isGoingToFocusItem = false;
566
+ return;
567
+ }
568
+ const related = e.relatedTarget;
569
+ if (related instanceof Element && getProgressStepperFocusedItemIndex(this.#$items, related) >= 0) {
570
+ return;
571
+ }
572
+ queueMicrotask(() => {
573
+ if (this.#usesCompactActionMenu) {
574
+ return;
575
+ }
576
+ const active = document.activeElement;
577
+ if (active instanceof Element && getProgressStepperFocusedItemIndex(this.#$items, active) >= 0) {
578
+ return;
579
+ }
580
+ this.#clearForcedFocusVisible();
581
+ syncProgressStepperItemsRovingTabIndexes(this.#$items);
582
+ });
583
+ };
584
+ #setForcedFocusVisible(nextItem) {
585
+ for (const item of this.#$items) {
586
+ if (item === nextItem) {
587
+ item.setAttribute(ATTR_PROGRESS_STEPPER_ITEM_FORCE_FOCUS_VISIBLE, "");
588
+ } else {
589
+ item.removeAttribute(ATTR_PROGRESS_STEPPER_ITEM_FORCE_FOCUS_VISIBLE);
590
+ }
591
+ }
592
+ }
593
+ #clearForcedFocusVisible() {
594
+ for (const item of this.#$items) {
595
+ item.removeAttribute(ATTR_PROGRESS_STEPPER_ITEM_FORCE_FOCUS_VISIBLE);
596
+ }
597
+ }
598
+ #onChangeReactHandler = (e) => {
599
+ getReactEventHandler(this, "on-change")?.(e);
600
+ getReactEventHandler(this, "onChange")?.(e);
601
+ };
602
+ #onHostFocusInCompactOnly = (e) => {
603
+ if (e.target !== this) {
604
+ return;
605
+ }
606
+ if (!this.#usesCompactActionMenu) {
607
+ return;
608
+ }
609
+ this.#$compactTrigger.focus();
610
+ };
611
+ }
612
+ defineCustomElement(TAG_PROGRESS_STEPPER, ProgressStepperV2);
613
+ export {
614
+ OrientationScheduler,
615
+ ProgressStepperV2,
616
+ computeOrientationLayout,
617
+ deriveStepStatuses
618
+ };
@@ -0,0 +1,32 @@
1
+ export type TStepItemSnapshot = {
2
+ value: string;
3
+ disabled: boolean;
4
+ complete: boolean;
5
+ };
6
+ export type TDerivedStepStatus = 'inactive' | 'incomplete' | 'complete';
7
+ export type TOrientationMode = 'horizontal' | 'compact';
8
+ export type TOrientationLayout = {
9
+ isHorizontal: boolean;
10
+ dataOrientation: 'horizontal' | 'vertical';
11
+ mode: TOrientationMode;
12
+ };
13
+ /**
14
+ * Derives each step’s `data-status` from host props and per-item flags.
15
+ * Mirrors `#syncStepItemStatuses` in `ProgressStepperV2` (must stay in sync).
16
+ *
17
+ * When `progressvalue` is unset or does not match any step `value`, steps would all become `inactive`
18
+ * (no tab stops). Treat that as “progress at the first step” so the stepper stays keyboard-reachable.
19
+ */
20
+ export declare function deriveStepStatuses(items: readonly TStepItemSnapshot[], params: {
21
+ noCompletionOrder: boolean;
22
+ progressValue: string;
23
+ }): TDerivedStepStatus[];
24
+ /**
25
+ * Chooses layout mode from measured widths.
26
+ * Callers must force layout (`offsetWidth`) before reading `wrapperScrollWidth` when `noAutoOrientation` is false.
27
+ */
28
+ export declare function computeOrientationLayout(params: {
29
+ noAutoOrientation: boolean;
30
+ containerWidth: number;
31
+ wrapperScrollWidth: number;
32
+ }): TOrientationLayout;