@ixfx/components 0.14.7 → 0.15.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.
@@ -0,0 +1,766 @@
1
+ import { t as themeFallbacks } from "./fallbacks-DQ_5OsmR.js";
2
+ import { n as safeCustomElement, t as __decorate } from "./decorate-VRK8rslU.js";
3
+ import { t as scrollFadeStyles } from "./scroll-fade-Xx28D7mH.js";
4
+ import { t as createDragGhost } from "./drag-ghost-DtSDvjri.js";
5
+ import { LitElement, css, html, nothing } from "lit";
6
+ import { property } from "lit/decorators.js";
7
+ //#region src/tabs/indicator-transition.ts
8
+ /**
9
+ * Smoothstep, 3t² − 2t³, expressed exactly as a cubic Bézier: control points
10
+ * at x = 1/3 and 2/3 make x linear in the curve parameter, leaving y as the
11
+ * smoothstep polynomial.
12
+ */
13
+ const SMOOTHSTEP = `cubic-bezier(0.3333, 0, 0.6667, 1)`;
14
+ let counter = 0;
15
+ /** A document-unique `view-transition-name` for one tab list's indicator. */
16
+ function nextIndicatorName() {
17
+ counter += 1;
18
+ return `ixfx-tab-indicator-${counter}`;
19
+ }
20
+ /**
21
+ * Read the transition settings from an element's computed style. Returns
22
+ * undefined when transitions are off (`--ixfx-tab-transition-duration` unset
23
+ * or zero), unsupported by the browser, or the user prefers reduced motion.
24
+ */
25
+ function readTiming(el) {
26
+ if (!(`startViewTransition` in document)) return void 0;
27
+ if (globalThis.matchMedia?.(`(prefers-reduced-motion: reduce)`).matches) return void 0;
28
+ const style = getComputedStyle(el);
29
+ const duration = style.getPropertyValue(`--ixfx-tab-transition-duration`).trim();
30
+ if (!(Number.parseFloat(duration) > 0)) return void 0;
31
+ return {
32
+ duration,
33
+ easing: style.getPropertyValue(`--ixfx-tab-transition-easing`).trim() || SMOOTHSTEP
34
+ };
35
+ }
36
+ const baseRule = `ixfx-tab-list-item[selected]::after { view-transition-name: var(--ixfx-tab-indicator-name, none); }`;
37
+ const rules = /* @__PURE__ */ new Map();
38
+ let sheet;
39
+ function writeSheet() {
40
+ if (sheet === void 0) {
41
+ sheet = new CSSStyleSheet();
42
+ document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
43
+ }
44
+ sheet.replaceSync([baseRule, ...rules.values()].join(`\n`));
45
+ }
46
+ /** Install the document-level indicator rule. Safe to call repeatedly. */
47
+ function installIndicatorStyles() {
48
+ if (sheet === void 0) writeSheet();
49
+ }
50
+ function applyTiming(name, timing) {
51
+ rules.set(name, `::view-transition-group(${name}) { animation-duration: ${timing.duration}; animation-timing-function: ${timing.easing}; }`);
52
+ writeSheet();
53
+ }
54
+ /**
55
+ * Run `update` (which must change the DOM synchronously) inside a view
56
+ * transition. The update itself is deferred by the browser until the old
57
+ * state has been captured.
58
+ */
59
+ function runTransition(name, timing, update) {
60
+ applyTiming(name, timing);
61
+ return document.startViewTransition(update);
62
+ }
63
+ //#endregion
64
+ //#region src/tabs/tab-list.ts
65
+ /** Pointer movement threshold (px) before a press becomes a drag. */
66
+ const DRAG_THRESHOLD_PX = 5;
67
+ let TabListElement = class TabListElement extends LitElement {
68
+ constructor(..._args) {
69
+ super(..._args);
70
+ this.orientation = `horizontal`;
71
+ this.flip = false;
72
+ this.scrollable = false;
73
+ this.hideLabels = false;
74
+ this.labelNogrow = false;
75
+ this.shrink = false;
76
+ this.dragMode = `off`;
77
+ this.#items = [];
78
+ this.#indicatorName = nextIndicatorName();
79
+ this.#dragState = null;
80
+ this.#suppressNextClick = false;
81
+ this.#onSlotChange = () => {
82
+ this.updateComplete.then(() => {
83
+ this.#refreshItems();
84
+ this.#syncIndicatorColors();
85
+ this.#syncHideLabels();
86
+ this.#syncLabelNogrow();
87
+ });
88
+ };
89
+ this.#onToolbarSlotChange = () => {
90
+ const assigned = (this.shadowRoot?.querySelector(`slot[name="toolbar"]`))?.assignedElements() ?? [];
91
+ const has = assigned.length > 0;
92
+ this.toggleAttribute(`has-toolbar`, has);
93
+ const toolbarDiv = this.shadowRoot?.querySelector(`.list-toolbar`);
94
+ if (toolbarDiv) toolbarDiv.hidden = !has;
95
+ if (has) {
96
+ const toolbar = assigned[0];
97
+ if (toolbar?.tagName === `IXFX-TOOLBAR`) requestAnimationFrame(() => {
98
+ toolbar?._scheduleMeasure?.();
99
+ });
100
+ }
101
+ };
102
+ this.#onKeyUp = (event) => {
103
+ const key = event.key;
104
+ if (this.#isVertical) {
105
+ if (key === `ArrowUp`) this.#selectPrevious();
106
+ else if (key === `ArrowDown`) this.#selectNext();
107
+ } else if (key === `ArrowRight`) this.#selectNext();
108
+ else if (key === `ArrowLeft`) this.#selectPrevious();
109
+ };
110
+ this.#onKeyDown = (event) => {
111
+ const state = this.#dragState;
112
+ if (event.key !== `Escape` || !state?.active) return;
113
+ event.preventDefault();
114
+ const pointerId = state.pointerId;
115
+ this.#abortDrag();
116
+ this.#endDrag(pointerId);
117
+ };
118
+ this.#onWheel = (event) => {
119
+ if (!this.scrollable) return;
120
+ const el = this.#containerEl;
121
+ if (!el) return;
122
+ if (!this.#isVertical) {
123
+ if (event.deltaY === 0 || el.scrollWidth <= el.clientWidth) return;
124
+ el.scrollLeft = el.scrollLeft + event.deltaY;
125
+ event.preventDefault();
126
+ } else {
127
+ if (event.deltaX === 0 || el.scrollHeight <= el.clientHeight) return;
128
+ el.scrollTop = el.scrollTop + event.deltaX;
129
+ event.preventDefault();
130
+ }
131
+ };
132
+ this.#onSlotClick = (event) => {
133
+ if (this.#suppressNextClick) {
134
+ this.#suppressNextClick = false;
135
+ event.preventDefault();
136
+ event.stopPropagation();
137
+ return;
138
+ }
139
+ this.focus();
140
+ const t = event.target.closest(`ixfx-tab-list-item`);
141
+ if (!t) return;
142
+ const previous = this.getSelectedElement();
143
+ const forId = t.getAttribute(`for`);
144
+ const previousId = previous?.id;
145
+ const apply = () => {
146
+ this.selectElement(t);
147
+ this.dispatchEvent(new CustomEvent(`change`, {
148
+ bubbles: true,
149
+ detail: {
150
+ previous: previousId,
151
+ selected: t.id,
152
+ for: forId ?? ``
153
+ }
154
+ }));
155
+ };
156
+ if (previous === t) apply();
157
+ else this.#transition(apply);
158
+ };
159
+ this.#onPointerDown = (event) => {
160
+ if (this.dragMode !== `reorder`) return;
161
+ if (event.button !== 0) return;
162
+ const target = this.#findDragTarget(event);
163
+ if (!target) return;
164
+ const fromIndex = this.#items.indexOf(target);
165
+ if (fromIndex < 0) return;
166
+ this.#dragState = {
167
+ pointerId: event.pointerId,
168
+ source: target,
169
+ fromIndex,
170
+ startX: event.clientX,
171
+ startY: event.clientY,
172
+ active: false,
173
+ ghost: null,
174
+ gapIndex: void 0
175
+ };
176
+ window.addEventListener(`pointermove`, this.#onPointerMove);
177
+ window.addEventListener(`pointerup`, this.#onPointerUp);
178
+ window.addEventListener(`pointercancel`, this.#onPointerCancel);
179
+ };
180
+ this.#pendingMove = null;
181
+ this.#moveRaf = null;
182
+ this.#onPointerMove = (event) => {
183
+ const state = this.#dragState;
184
+ if (!state || event.pointerId !== state.pointerId) return;
185
+ this.#pendingMove = event;
186
+ this.#moveRaf ??= requestAnimationFrame(this.#flushMove);
187
+ };
188
+ this.#flushMove = () => {
189
+ const event = this.#pendingMove;
190
+ this.#pendingMove = null;
191
+ this.#moveRaf = null;
192
+ const state = this.#dragState;
193
+ if (!state || !event) return;
194
+ const dx = event.clientX - state.startX;
195
+ const dy = event.clientY - state.startY;
196
+ const distance = Math.hypot(dx, dy);
197
+ if (!state.active) {
198
+ if (distance < DRAG_THRESHOLD_PX) return;
199
+ this.#beginDrag(event);
200
+ }
201
+ state.ghost?.move(event.clientX, event.clientY);
202
+ const gap = this.#computeGapIndex(event.clientX, event.clientY);
203
+ if (gap !== state.gapIndex) {
204
+ state.gapIndex = gap;
205
+ this.requestUpdate();
206
+ }
207
+ };
208
+ this.#onPointerUp = (event) => {
209
+ const state = this.#dragState;
210
+ if (!state || event.pointerId !== state.pointerId) return;
211
+ if (state.active) {
212
+ const gap = this.#computeGapIndex(event.clientX, event.clientY);
213
+ this.#commitDrag(state.source, state.fromIndex, gap);
214
+ }
215
+ this.#endDrag(event.pointerId);
216
+ };
217
+ this.#onPointerCancel = (event) => {
218
+ const state = this.#dragState;
219
+ if (!state || event.pointerId !== state.pointerId) return;
220
+ this.#abortDrag();
221
+ this.#endDrag(event.pointerId);
222
+ };
223
+ }
224
+ #items;
225
+ #indicatorName;
226
+ #dragState;
227
+ #suppressNextClick;
228
+ get #isVertical() {
229
+ return this.orientation === `vertical-stack` || this.orientation === `vertical-side`;
230
+ }
231
+ get #containerClass() {
232
+ return [
233
+ this.scrollable ? `${this.orientation} scrollable` : this.orientation,
234
+ this.scrollable ? this.#isVertical ? `scroll-fade-y` : `scroll-fade-x` : ``,
235
+ this.dragMode === `reorder` ? `reorder` : ``
236
+ ].filter(Boolean).join(` `);
237
+ }
238
+ render() {
239
+ const caretStyle = this.#computeCaretStyle();
240
+ return html`
241
+ <div class="outer ${this.orientation}">
242
+ <div
243
+ tabindex=0
244
+ @keyup=${this.#onKeyUp}
245
+ @click=${this.#onSlotClick}
246
+ @pointerdown=${this.#onPointerDown}
247
+ @keydown=${this.#onKeyDown}
248
+ @wheel=${this.#onWheel}
249
+ id="container"
250
+ class=${this.#containerClass}
251
+ >
252
+ <slot @slotchange=${this.#onSlotChange}></slot>
253
+ ${caretStyle ? html`<div class="drop-caret" style=${caretStyle}></div>` : nothing}
254
+ </div>
255
+ <div class="list-toolbar ${this.orientation}" part="list-toolbar">
256
+ <slot name="toolbar" @slotchange=${this.#onToolbarSlotChange}></slot>
257
+ </div>
258
+ </div>`;
259
+ }
260
+ #onSlotChange;
261
+ /**
262
+ * Re-read the assigned tab items. Some environments do not fire slotchange
263
+ * for a slot's initial assignment, so firstUpdated also calls this before
264
+ * syncing item attributes.
265
+ */
266
+ #refreshItems() {
267
+ this.#items = (this.shadowRoot?.querySelector(`slot:not([name])`))?.assignedElements({ flatten: true }) ?? [];
268
+ }
269
+ #onToolbarSlotChange;
270
+ firstUpdated() {
271
+ this.updateComplete.then(() => {
272
+ this.#refreshItems();
273
+ this.#syncIndicatorColors();
274
+ this.#syncHideLabels();
275
+ this.#syncLabelNogrow();
276
+ this.#onToolbarSlotChange();
277
+ });
278
+ }
279
+ updated(changedProperties) {
280
+ super.updated(changedProperties);
281
+ this.classList.toggle(`scrollable`, this.scrollable);
282
+ if (changedProperties.has(`dragMode`)) this.toggleAttribute(`drag-mode`, this.dragMode !== `off`);
283
+ if (changedProperties.has(`hideLabels`)) this.#syncHideLabels();
284
+ if (changedProperties.has(`labelNogrow`)) this.#syncLabelNogrow();
285
+ if (changedProperties.has(`orientation`) || changedProperties.has(`flip`)) for (const item of this.#items) item.setAttribute(`indicator-edge`, this.#indicatorEdge);
286
+ }
287
+ connectedCallback() {
288
+ super.connectedCallback();
289
+ installIndicatorStyles();
290
+ }
291
+ /**
292
+ * Run a selection change inside a view transition when the page opted in
293
+ * via `--ixfx-tab-transition-duration`. The browser defers `update`, so only
294
+ * user-initiated changes (click, arrow keys) use this; the public
295
+ * `selectElement` stays synchronous.
296
+ */
297
+ #transition(update) {
298
+ const timing = readTiming(this);
299
+ if (timing === void 0) {
300
+ update();
301
+ return;
302
+ }
303
+ this.style.setProperty(`--ixfx-tab-indicator-name`, this.#indicatorName);
304
+ const transition = runTransition(this.#indicatorName, timing, update);
305
+ const clear = () => this.style.removeProperty(`--ixfx-tab-indicator-name`);
306
+ transition.finished.then(clear, clear);
307
+ }
308
+ #syncHideLabels() {
309
+ for (const item of this.#items) if (this.hideLabels) item.setAttribute(`hide-labels`, ``);
310
+ else item.removeAttribute(`hide-labels`);
311
+ }
312
+ #syncLabelNogrow() {
313
+ for (const item of this.#items) if (this.labelNogrow) item.setAttribute(`label-nogrow`, ``);
314
+ else item.removeAttribute(`label-nogrow`);
315
+ }
316
+ get #indicatorEdge() {
317
+ if (this.#isVertical) return this.flip ? `left` : `right`;
318
+ return this.flip ? `top` : `bottom`;
319
+ }
320
+ #syncIndicatorColors() {
321
+ for (const item of this.#items) {
322
+ item.setAttribute(`indicator-edge`, this.#indicatorEdge);
323
+ item.style.setProperty(`--indicator-color-bottom`, `transparent`);
324
+ item.style.setProperty(`--indicator-color-top`, `transparent`);
325
+ item.style.setProperty(`--indicator-color-left`, `transparent`);
326
+ item.style.setProperty(`--indicator-color-right`, `transparent`);
327
+ if (item.hasAttribute(`selected`)) {
328
+ if (this.orientation === `horizontal` && !this.flip) item.style.setProperty(`--indicator-color-bottom`, `var(--accent)`);
329
+ else if (this.orientation === `horizontal` && this.flip) item.style.setProperty(`--indicator-color-top`, `var(--accent)`);
330
+ else if (this.#isVertical && !this.flip) item.style.setProperty(`--indicator-color-right`, `var(--accent)`);
331
+ else if (this.#isVertical && this.flip) item.style.setProperty(`--indicator-color-left`, `var(--accent)`);
332
+ }
333
+ }
334
+ }
335
+ #onKeyUp;
336
+ #onKeyDown;
337
+ /**
338
+ * Translate vertical wheel deltas into horizontal scroll on a scrollable
339
+ * horizontal list (and vice-versa for vertical lists). Without this, the
340
+ * default browser behaviour only scrolls horizontally with Shift+wheel or a
341
+ * physical horizontal wheel — neither of which most users reach for on a
342
+ * VS Code-style tab strip.
343
+ *
344
+ * Only kicks in when the list is actually scrollable (overflow content
345
+ * present), and is a no-op when nothing is scrollable in that direction.
346
+ */
347
+ #onWheel;
348
+ getSelectedElement() {
349
+ for (const el of this.#items) if (this.#isSelected(el)) return el;
350
+ const slot = this.shadowRoot?.querySelector(`slot`);
351
+ if (slot) {
352
+ const els = slot.assignedElements({ flatten: true });
353
+ for (const el of els) if (this.#isSelected(el)) return el;
354
+ }
355
+ }
356
+ #isSelected(el) {
357
+ if (el.getAttribute(`selected`) === null) return false;
358
+ return el.hasAttribute(`selected`);
359
+ }
360
+ selectElement(el) {
361
+ const existing = this.getSelectedElement();
362
+ if (existing) {
363
+ existing.removeAttribute(`selected`);
364
+ existing.style.setProperty(`--indicator-color-bottom`, `transparent`);
365
+ existing.style.setProperty(`--indicator-color-top`, `transparent`);
366
+ existing.style.setProperty(`--indicator-color-left`, `transparent`);
367
+ existing.style.setProperty(`--indicator-color-right`, `transparent`);
368
+ }
369
+ el.setAttribute(`selected`, ``);
370
+ if (this.orientation === `horizontal` && !this.flip) el.style.setProperty(`--indicator-color-bottom`, `var(--accent)`);
371
+ else if (this.orientation === `horizontal` && this.flip) el.style.setProperty(`--indicator-color-top`, `var(--accent)`);
372
+ else if (this.#isVertical && !this.flip) el.style.setProperty(`--indicator-color-right`, `var(--accent)`);
373
+ else if (this.#isVertical && this.flip) el.style.setProperty(`--indicator-color-left`, `var(--accent)`);
374
+ }
375
+ #selectNext() {
376
+ let found = false;
377
+ for (const el of this.#items) if (this.#isSelected(el) && !found) found = true;
378
+ else if (found) {
379
+ this.#transition(() => this.selectElement(el));
380
+ break;
381
+ }
382
+ }
383
+ #selectPrevious() {
384
+ let prev;
385
+ for (const el of this.#items) if (this.#isSelected(el) && prev) {
386
+ const target = prev;
387
+ this.#transition(() => this.selectElement(target));
388
+ } else prev = el;
389
+ }
390
+ #onSlotClick;
391
+ /**
392
+ * Walk composedPath looking for the nearest ancestor tab-list-item, but bail
393
+ * if the path goes through a `.close-btn` or `.toolbar` (so close/toolbar
394
+ * buttons always win — same delegation pattern used by tooltip-controller).
395
+ */
396
+ #findDragTarget(event) {
397
+ if (this.dragMode === `off`) return null;
398
+ const path = event.composedPath();
399
+ let item = null;
400
+ for (const node of path) {
401
+ if (!(node instanceof HTMLElement)) continue;
402
+ if (node.classList.contains(`close-btn`) || node.classList.contains(`toolbar`)) return null;
403
+ if (!item && node.tagName === `IXFX-TAB-LIST-ITEM`) item = node;
404
+ }
405
+ return item;
406
+ }
407
+ #onPointerDown;
408
+ #pendingMove;
409
+ #moveRaf;
410
+ #onPointerMove;
411
+ #cancelPendingMove() {
412
+ if (this.#moveRaf !== null) {
413
+ cancelAnimationFrame(this.#moveRaf);
414
+ this.#moveRaf = null;
415
+ }
416
+ this.#pendingMove = null;
417
+ }
418
+ #flushMove;
419
+ #onPointerUp;
420
+ #onPointerCancel;
421
+ #beginDrag(event) {
422
+ const state = this.#dragState;
423
+ if (!state || state.active) return;
424
+ try {
425
+ this.#containerEl?.setPointerCapture(event.pointerId);
426
+ } catch {}
427
+ state.active = true;
428
+ state.source.setAttribute(`drag-source`, ``);
429
+ state.ghost = this.#createGhost(state.source);
430
+ if (state.ghost) state.ghost.move(event.clientX, event.clientY);
431
+ }
432
+ #abortDrag() {
433
+ const state = this.#dragState;
434
+ if (!state) return;
435
+ state.source.removeAttribute(`drag-source`);
436
+ if (state.gapIndex !== void 0) {
437
+ state.gapIndex = void 0;
438
+ this.requestUpdate();
439
+ }
440
+ }
441
+ #endDrag(pointerId) {
442
+ const state = this.#dragState;
443
+ if (!state) return;
444
+ this.#cancelPendingMove();
445
+ state.ghost?.destroy();
446
+ state.ghost = null;
447
+ this.#dragState = null;
448
+ window.removeEventListener(`pointermove`, this.#onPointerMove);
449
+ window.removeEventListener(`pointerup`, this.#onPointerUp);
450
+ window.removeEventListener(`pointercancel`, this.#onPointerCancel);
451
+ try {
452
+ this.#containerEl?.releasePointerCapture(pointerId);
453
+ } catch {}
454
+ this.requestUpdate();
455
+ }
456
+ #commitDrag(source, fromIndex, gapIndex) {
457
+ source.removeAttribute(`drag-source`);
458
+ const siblings = this.#items.filter((el) => el !== source);
459
+ const target = gapIndex === void 0 ? siblings.length : Math.max(0, Math.min(gapIndex, siblings.length));
460
+ const parent = source.parentNode;
461
+ if (!parent) return;
462
+ const ref = siblings[target] ?? null;
463
+ parent.insertBefore(source, ref);
464
+ const toIndex = Array.prototype.indexOf.call(parent.children, source);
465
+ if (toIndex === fromIndex) return;
466
+ this.#items = (this.shadowRoot?.querySelector(`slot`))?.assignedElements({ flatten: true }) ?? [];
467
+ this.#syncIndicatorColors();
468
+ this.#suppressNextClick = true;
469
+ this.dispatchEvent(new CustomEvent(`reorder`, {
470
+ bubbles: true,
471
+ detail: {
472
+ id: source.id,
473
+ fromIndex,
474
+ toIndex
475
+ }
476
+ }));
477
+ }
478
+ #createGhost(source) {
479
+ const content = document.createElement(`span`);
480
+ content.style.display = `inline-flex`;
481
+ content.style.alignItems = `center`;
482
+ content.style.gap = `var(--space-xs, 4px)`;
483
+ content.style.minWidth = `0`;
484
+ const iconName = source.iconName;
485
+ if (iconName) {
486
+ const icon = document.createElement(`ixfx-icon`);
487
+ icon.name = iconName;
488
+ content.appendChild(icon);
489
+ }
490
+ const label = document.createElement(`span`);
491
+ label.textContent = (source.textContent ?? ``).trim();
492
+ label.style.overflow = `hidden`;
493
+ label.style.textOverflow = `ellipsis`;
494
+ label.style.whiteSpace = `nowrap`;
495
+ content.appendChild(label);
496
+ try {
497
+ return createDragGhost({
498
+ anchor: source,
499
+ content
500
+ });
501
+ } catch {
502
+ return null;
503
+ }
504
+ }
505
+ /**
506
+ * Compute the index of the gap nearest the pointer, ignoring the source item.
507
+ * Returns 0..#items.length-1; undefined if no siblings.
508
+ *
509
+ * Per the plan: drag-mode="reorder" has "no sense of breaking out", so the
510
+ * perpendicular axis is ignored — the pointer always resolves to some gap.
511
+ */
512
+ #computeGapIndex(clientX, clientY) {
513
+ const state = this.#dragState;
514
+ if (!state) return void 0;
515
+ const siblings = this.#items.filter((el) => el !== state.source);
516
+ if (siblings.length === 0) return 0;
517
+ const useX = !this.#isVertical;
518
+ let bestGap = 0;
519
+ let bestDist = Infinity;
520
+ for (let i = 0; i < siblings.length; i++) {
521
+ const rect = siblings[i].getBoundingClientRect();
522
+ const start = useX ? rect.left : rect.top;
523
+ const end = useX ? rect.right : rect.bottom;
524
+ const mid = (start + end) / 2;
525
+ const pos = useX ? clientX : clientY;
526
+ const dStart = Math.abs(pos - start);
527
+ Math.abs(pos - mid);
528
+ const dEnd = Math.abs(pos - end);
529
+ if (pos < mid) {
530
+ if (dStart < bestDist) {
531
+ bestDist = dStart;
532
+ bestGap = i;
533
+ }
534
+ } else if (dEnd < bestDist) {
535
+ bestDist = dEnd;
536
+ bestGap = i + 1;
537
+ }
538
+ }
539
+ return bestGap;
540
+ }
541
+ #computeCaretStyle() {
542
+ const state = this.#dragState;
543
+ if (!state?.active || state.gapIndex === void 0) return void 0;
544
+ const siblings = this.#items.filter((el) => el !== state.source);
545
+ if (siblings.length === 0) return void 0;
546
+ const useX = !this.#isVertical;
547
+ const containerRect = this.#containerEl?.getBoundingClientRect();
548
+ if (!containerRect) return void 0;
549
+ const gap = Math.max(0, Math.min(state.gapIndex, siblings.length));
550
+ let rect = null;
551
+ if (gap < siblings.length) rect = siblings[gap].getBoundingClientRect();
552
+ else rect = siblings[siblings.length - 1].getBoundingClientRect();
553
+ if (useX) {
554
+ const x = (gap < siblings.length ? rect.left : rect.right) - containerRect.left;
555
+ const y = 0;
556
+ const h = containerRect.height;
557
+ const w = 2;
558
+ return `transform: translate(${x - w / 2}px, ${y}px); width: ${w}px; height: ${h}px;`;
559
+ } else {
560
+ const y = (gap < siblings.length ? rect.top : rect.bottom) - containerRect.top;
561
+ const x = 0;
562
+ const w = containerRect.width;
563
+ const h = 2;
564
+ return `transform: translate(${x}px, ${y - h / 2}px); width: ${w}px; height: ${h}px;`;
565
+ }
566
+ }
567
+ get #containerEl() {
568
+ return this.shadowRoot?.getElementById(`container`) ?? null;
569
+ }
570
+ static {
571
+ this.styles = [
572
+ themeFallbacks,
573
+ scrollFadeStyles(`x`),
574
+ scrollFadeStyles(`y`),
575
+ css`
576
+ :host {
577
+ display: inline-block;
578
+ --activated-colour: var(--accent);
579
+ --colour: var(--surface-3-text);
580
+ --scroll-max-height: 200px;
581
+ --allow-overflow: hidden;
582
+ color: var(--colour);
583
+ border: var(--border, none);
584
+ border-color: var(--surface-5);
585
+ border-radius: var(--radius-l, 8px) var(--radius-l, 8px) 0 0;
586
+
587
+ }
588
+ :host(.scrollable) {
589
+ max-height: var(--scroll-max-height);
590
+ }
591
+ :host([hide-labels]) {
592
+ display: inline-flex;
593
+ width: auto;
594
+ }
595
+ text {
596
+ user-select:none;
597
+ }
598
+ .outer {
599
+ display: flex;
600
+ width: 100%;
601
+ height: 100%;
602
+ gap: var(--space-s);
603
+ align-items: stretch;
604
+ box-sizing: border-box;
605
+ }
606
+ .outer.horizontal {
607
+ flex-direction: row;
608
+ align-items: center;
609
+ }
610
+ .outer.vertical-stack,
611
+ .outer.vertical-side {
612
+ flex-direction: column;
613
+ }
614
+ .list-toolbar {
615
+ display: flex;
616
+ align-items: center;
617
+ gap: var(--space-xs);
618
+ flex: 0 0 auto;
619
+ width: max-content;
620
+ max-width: 50%;
621
+ overflow: visible;
622
+ }
623
+ .list-toolbar.horizontal ::slotted(*:not(ixfx-toolbar)) {
624
+ margin-left: auto;
625
+ }
626
+ .list-toolbar[hidden] {
627
+ display: none !important;
628
+ }
629
+ .list-toolbar[hidden] {
630
+ display: none !important;
631
+ }
632
+ ::slotted(ixfx-toolbar) {
633
+ --toolbar-bg: transparent;
634
+ --toolbar-border: none;
635
+ --toolbar-padding: 0;
636
+ width: max-content;
637
+ max-width: 100%;
638
+ min-width: 150px;
639
+ flex-shrink: 0;
640
+ }
641
+ #container {
642
+ display:flex;
643
+ position: relative;
644
+ flex: 1 1 0%;
645
+ min-width: 0;
646
+ min-height: 0;
647
+ }
648
+ .outer.vertical-stack #container,
649
+ .outer.vertical-side #container {
650
+ flex: 1 1 auto;
651
+ height: 100%;
652
+ }
653
+ #container.horizontal {
654
+ flex-direction: row;
655
+ overflow: hidden;
656
+ }
657
+ #container.horizontal.scrollable {
658
+ overflow-x: scroll;
659
+ --allow-overflow: visible;
660
+ scrollbar-width: none;
661
+ }
662
+ #container.horizontal.scrollable::-webkit-scrollbar {
663
+ display: none;
664
+ }
665
+ #container.horizontal.scrollable ::slotted(ixfx-tab-list-item) {
666
+ flex-shrink: 0;
667
+ }
668
+ #container.vertical-stack {
669
+ flex-direction: column;
670
+ height: 100%;
671
+ }
672
+ #container.vertical-stack.flip {
673
+ flex-direction: column-reverse;
674
+ }
675
+ #container.vertical-stack.scrollable {
676
+ overflow-y: scroll;
677
+ max-height: var(--scroll-max-height);
678
+ --allow-overflow: visible;
679
+ scrollbar-width: none;
680
+ }
681
+ #container.vertical-stack.scrollable::-webkit-scrollbar {
682
+ display: none;
683
+ }
684
+ #container.vertical-side {
685
+ flex-direction: column;
686
+ height: 100%;
687
+ }
688
+ #container.vertical-side.flip {
689
+ flex-direction: column-reverse;
690
+ }
691
+ #container.vertical-side.scrollable {
692
+ overflow-y: scroll;
693
+ max-height: var(--scroll-max-height);
694
+ --allow-overflow: visible;
695
+ scrollbar-width: none;
696
+ }
697
+ #container.vertical-side.scrollable::-webkit-scrollbar {
698
+ display: none;
699
+ }
700
+
701
+ ::slotted(ixfx-tab-list-item) {
702
+ flex: 1 1 auto;
703
+ min-width: 0;
704
+ }
705
+
706
+ :host([shrink]) ::slotted(ixfx-tab-list-item) {
707
+ flex: 0 0 auto;
708
+ }
709
+
710
+ #container.vertical-side ::slotted(ixfx-tab-list-item) {
711
+ writing-mode: vertical-lr;
712
+ text-orientation: mixed;
713
+ }
714
+ #container.vertical-side.flip ::slotted(ixfx-tab-list-item) {
715
+ writing-mode: vertical-rl;
716
+ text-orientation: mixed;
717
+ }
718
+
719
+ /* Drag affordances — opt-in via drag-mode="reorder" on the host (list). */
720
+ #container.reorder ::slotted(ixfx-tab-list-item) {
721
+ cursor: grab;
722
+ }
723
+ :host([drag-mode]) ::slotted(ixfx-tab-list-item[drag-source]) {
724
+ cursor: grabbing;
725
+ opacity: 0.4;
726
+ transition: opacity var(--transition, 0.15s ease-out);
727
+ }
728
+
729
+ /* Insertion caret shown during an active drag. */
730
+ .drop-caret {
731
+ position: absolute;
732
+ background: var(--accent);
733
+ border-radius: 1px;
734
+ pointer-events: none;
735
+ z-index: 1;
736
+ }
737
+
738
+ ::slotted(.activated) {
739
+ --colour: var(--activated-colour);
740
+ }
741
+ `
742
+ ];
743
+ }
744
+ };
745
+ __decorate([property({ type: String })], TabListElement.prototype, "orientation", void 0);
746
+ __decorate([property({ type: Boolean })], TabListElement.prototype, "flip", void 0);
747
+ __decorate([property({ type: Boolean })], TabListElement.prototype, "scrollable", void 0);
748
+ __decorate([property({
749
+ type: Boolean,
750
+ attribute: `hide-labels`
751
+ })], TabListElement.prototype, "hideLabels", void 0);
752
+ __decorate([property({
753
+ type: Boolean,
754
+ attribute: `label-nogrow`
755
+ })], TabListElement.prototype, "labelNogrow", void 0);
756
+ __decorate([property({
757
+ type: Boolean,
758
+ reflect: true
759
+ })], TabListElement.prototype, "shrink", void 0);
760
+ __decorate([property({
761
+ type: String,
762
+ attribute: `drag-mode`
763
+ })], TabListElement.prototype, "dragMode", void 0);
764
+ TabListElement = __decorate([safeCustomElement(`ixfx-tab-list`)], TabListElement);
765
+ //#endregion
766
+ export { TabListElement as t };