@ixfx/components 0.10.4 → 0.10.6

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.
@@ -12,12 +12,20 @@ import { CommandRegistry } from "@clinth/ui-commands";
12
12
  declare class TabListItemElement extends LitElement {
13
13
  closeable: boolean;
14
14
  hideLabels: boolean;
15
+ /**
16
+ * Label does not stretch to fill the tab (`flex: 0 0 auto` instead of
17
+ * `1 1 auto`), so status content sits next to the label instead of being
18
+ * pushed to the far edge. Set on the item, or on `ixfx-tab-list` which
19
+ * mirrors it onto every item.
20
+ */
21
+ labelNogrow: boolean;
15
22
  iconName: string | undefined;
16
23
  description: string | undefined;
17
24
  commandActive: string | undefined;
18
25
  registry: CommandRegistry<unknown> | undefined;
19
26
  private _compact;
20
27
  private _hasToolbarContent;
28
+ private _hasStatusContent;
21
29
  private _resizeObserver?;
22
30
  private _commandCleanup?;
23
31
  private _labelObserver;
@@ -27,13 +35,28 @@ declare class TabListItemElement extends LitElement {
27
35
  private _syncTooltipAttrs;
28
36
  private _syncCommandListener;
29
37
  private _onToolbarSlotChange;
38
+ private _onStatusSlotChange;
30
39
  private _onLabelSlotChange;
40
+ /**
41
+ * Attach slotchange listeners. The shadow root is empty during the first
42
+ * connectedCallback (the template only exists once rendered), so this is
43
+ * called again from firstUpdated. addEventListener dedupes identical
44
+ * listener registrations, so double-wiring is harmless.
45
+ */
46
+ private _wireSlotListeners;
47
+ private _unwireSlotListeners;
31
48
  /**
32
49
  * Mirror the default-slot text into `.label[data-label]` for the `::after`
33
50
  * width sizer. Slotted toolbar content is excluded.
34
51
  */
35
52
  private _syncLabelSizer;
36
53
  private _updateToolbarContent;
54
+ /**
55
+ * Named slots only render once content has been detected, so reading
56
+ * assignedElements() cannot bootstrap detection. Direct light-DOM children
57
+ * are exactly what a slot assigns, so scan those instead.
58
+ */
59
+ private _updateStatusContent;
37
60
  firstUpdated(): void;
38
61
  render(): import("lit").TemplateResult<1>;
39
62
  private _onCloseClick;
@@ -70,6 +93,12 @@ declare class TabListElement extends LitElement {
70
93
  flip: boolean;
71
94
  scrollable: boolean;
72
95
  hideLabels: boolean;
96
+ /**
97
+ * Labels of the child items do not stretch to fill each tab
98
+ * (`flex: 0 0 auto`), so status content sits next to the label text.
99
+ * Mirrored onto every item as a `label-nogrow` attribute.
100
+ */
101
+ labelNogrow: boolean;
73
102
  shrink: boolean;
74
103
  dragMode: TabListDragMode;
75
104
  render(): import("lit").TemplateResult<1>;
@@ -86,4 +115,4 @@ declare global {
86
115
  }
87
116
  //#endregion
88
117
  export { TabListReorderDetail as a, TabListElement as i, TabListChangeEvent as n, TabListReorderEvent as o, TabListDragMode as r, TabListItemElement as s, TabListChangeDetail as t };
89
- //# sourceMappingURL=tab-list-CGyPt07Z.d.ts.map
118
+ //# sourceMappingURL=tab-list-pqfKaVAE.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tab-list-pqfKaVAE.d.ts","names":[],"sources":["../src/tabs/tab-list-item.ts","../src/tabs/tab-list.ts"],"mappings":";;;;;;;;;;;cAiBa,2BAA2B;EAEtC;EAGA;;;;;;;EASA;EAGA;EAGA;EAGA;EAGA,UAAU;UAEO;UACA;UACA;UACT;UACA;UACA;EAEC;EAwBA;EAWA,QAAQ,SAAS;UAQlB;UAUA;UAWA;UAIA;UAIA;;;;;;;UAUA;UASA;;;;;UAaA;UAiBA;;;;;;UASA;EAIC;EAOA,wBAAM;UA+BP;SAWQ,sBAAM;;QAyLhB;YACI;IACR,sBAAsB;;;;;;;;;;KCvZd;WACD;WACA;WACA;;KAGC,qBAAqB,YAAY;KAEjC;WACD;WACA;WACA;;KAGC,sBAAsB,YAAY;KAElC;cAMC,uBAAuB;;EAElC;EAGA;EAGA;EAGA;;;;;;EAQA;EAGA;EAGA,UAAU;EA4BD,wBAAM;EAgEN;EAUA,QAAQ,mBAAmB;EAiHpC,sBAAkB;EAwBlB,cAAc,IAAI;SAwaX,sBAAM;;QAyKP;YACI;IACR,iBAAiB"}
package/dist/tabs.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { a as TabListReorderDetail, i as TabListElement, n as TabListChangeEvent, o as TabListReorderEvent, r as TabListDragMode, t as TabListChangeDetail } from "./tab-list-CGyPt07Z.js";
1
+ import { a as TabListReorderDetail, i as TabListElement, n as TabListChangeEvent, o as TabListReorderEvent, r as TabListDragMode, t as TabListChangeDetail } from "./tab-list-pqfKaVAE.js";
2
2
  export { TabListChangeDetail, TabListChangeEvent, TabListDragMode, TabListElement, TabListReorderDetail, TabListReorderEvent };
package/dist/tabs.js CHANGED
@@ -14,6 +14,7 @@ let TabListElement = class TabListElement extends LitElement {
14
14
  this.flip = false;
15
15
  this.scrollable = false;
16
16
  this.hideLabels = false;
17
+ this.labelNogrow = false;
17
18
  this.shrink = false;
18
19
  this.dragMode = `off`;
19
20
  this.#items = [];
@@ -21,9 +22,10 @@ let TabListElement = class TabListElement extends LitElement {
21
22
  this.#suppressNextClick = false;
22
23
  this.#onSlotChange = () => {
23
24
  this.updateComplete.then(() => {
24
- this.#items = (this.shadowRoot?.querySelector(`slot:not([name])`))?.assignedElements({ flatten: true }) ?? [];
25
+ this.#refreshItems();
25
26
  this.#syncIndicatorColors();
26
27
  this.#syncHideLabels();
28
+ this.#syncLabelNogrow();
27
29
  });
28
30
  };
29
31
  this.#onToolbarSlotChange = () => {
@@ -182,13 +184,21 @@ let TabListElement = class TabListElement extends LitElement {
182
184
  </div>`;
183
185
  }
184
186
  #onSlotChange;
187
+ /**
188
+ * Re-read the assigned tab items. Some environments do not fire slotchange
189
+ * for a slot's initial assignment, so firstUpdated also calls this before
190
+ * syncing item attributes.
191
+ */
192
+ #refreshItems() {
193
+ this.#items = (this.shadowRoot?.querySelector(`slot:not([name])`))?.assignedElements({ flatten: true }) ?? [];
194
+ }
185
195
  #onToolbarSlotChange;
186
196
  firstUpdated() {
187
197
  this.updateComplete.then(() => {
188
- if (this.#items.length > 0) {
189
- this.#syncIndicatorColors();
190
- this.#syncHideLabels();
191
- }
198
+ this.#refreshItems();
199
+ this.#syncIndicatorColors();
200
+ this.#syncHideLabels();
201
+ this.#syncLabelNogrow();
192
202
  this.#onToolbarSlotChange();
193
203
  });
194
204
  }
@@ -197,11 +207,16 @@ let TabListElement = class TabListElement extends LitElement {
197
207
  this.classList.toggle(`scrollable`, this.scrollable);
198
208
  if (changedProperties.has(`dragMode`)) this.toggleAttribute(`drag-mode`, this.dragMode !== `off`);
199
209
  if (changedProperties.has(`hideLabels`)) this.#syncHideLabels();
210
+ if (changedProperties.has(`labelNogrow`)) this.#syncLabelNogrow();
200
211
  }
201
212
  #syncHideLabels() {
202
213
  for (const item of this.#items) if (this.hideLabels) item.setAttribute(`hide-labels`, ``);
203
214
  else item.removeAttribute(`hide-labels`);
204
215
  }
216
+ #syncLabelNogrow() {
217
+ for (const item of this.#items) if (this.labelNogrow) item.setAttribute(`label-nogrow`, ``);
218
+ else item.removeAttribute(`label-nogrow`);
219
+ }
205
220
  #syncIndicatorColors() {
206
221
  for (const item of this.#items) {
207
222
  item.style.setProperty(`--indicator-color-bottom`, `transparent`);
@@ -620,6 +635,10 @@ __decorate([property({
620
635
  type: Boolean,
621
636
  attribute: `hide-labels`
622
637
  })], TabListElement.prototype, "hideLabels", void 0);
638
+ __decorate([property({
639
+ type: Boolean,
640
+ attribute: `label-nogrow`
641
+ })], TabListElement.prototype, "labelNogrow", void 0);
623
642
  __decorate([property({
624
643
  type: Boolean,
625
644
  reflect: true
package/dist/tabs.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"tabs.js","names":["#items","#syncIndicatorColors","#syncHideLabels","#isVertical","#selectPrevious","#selectNext","#dragState","#abortDrag","#endDrag","#containerEl","#suppressNextClick","#findDragTarget","#onPointerMove","#onPointerUp","#onPointerCancel","#beginDrag","#computeGapIndex","#commitDrag","#containerClass","#computeCaretStyle","#onKeyUp","#onSlotClick","#onPointerDown","#onKeyDown","#onWheel","#onSlotChange","#onToolbarSlotChange","#isSelected","#createGhost","customElement"],"sources":["../src/tabs/tab-list.ts"],"sourcesContent":["import type { PropertyValues } from \"lit\";\nimport type { DragGhost } from \"../util/drag-ghost.js\";\nimport type { TabListItemElement } from \"./tab-list-item.js\";\nimport { css, html, LitElement, nothing } from \"lit\";\nimport { property } from \"lit/decorators.js\";\nimport { themeFallbacks } from \"../styles/fallbacks.js\";\nimport { scrollFadeStyles } from \"../styles/scroll-fade.js\";\nimport { safeCustomElement as customElement } from \"../util/custom-elements.js\";\nimport { createDragGhost } from \"../util/drag-ghost.js\";\n\n/**\n * Events\n * 'change' - new tab selected { previous:string, selected:string, for:string }\n * 'reorder' - tab moved within the list { id:string, fromIndex:number, toIndex:number }\n */\nexport type TabListChangeDetail = {\n readonly previous: string | undefined;\n readonly selected: string;\n readonly for: string;\n};\n\nexport type TabListChangeEvent = CustomEvent<TabListChangeDetail>;\n\nexport type TabListReorderDetail = {\n readonly id: string;\n readonly fromIndex: number;\n readonly toIndex: number;\n};\n\nexport type TabListReorderEvent = CustomEvent<TabListReorderDetail>;\n\nexport type TabListDragMode = `off` | `reorder`;\n\n/** Pointer movement threshold (px) before a press becomes a drag. */\nconst DRAG_THRESHOLD_PX = 5;\n\n@customElement(`ixfx-tab-list`)\nexport class TabListElement extends LitElement {\n @property({ type: String })\n orientation: `horizontal` | `vertical-stack` | `vertical-side` = `horizontal`;\n\n @property({ type: Boolean })\n flip = false;\n\n @property({ type: Boolean })\n scrollable = false;\n\n @property({ type: Boolean, attribute: `hide-labels` })\n hideLabels = false;\n\n @property({ type: Boolean, reflect: true })\n shrink = false;\n\n @property({ type: String, attribute: `drag-mode` })\n dragMode: TabListDragMode = `off`;\n\n #items: TabListItemElement[] = [];\n\n // ── Drag state (not reactive) ────────────────────────────────────────\n #dragState: {\n pointerId: number;\n source: TabListItemElement;\n fromIndex: number;\n startX: number;\n startY: number;\n active: boolean;\n ghost: DragGhost | null;\n gapIndex: number | undefined;\n } | null = null;\n\n #suppressNextClick = false;\n\n get #isVertical(): boolean {\n return this.orientation === `vertical-stack` || this.orientation === `vertical-side`;\n }\n\n get #containerClass(): string {\n const base = this.scrollable ? `${this.orientation} scrollable` : this.orientation;\n const fadeClass = this.scrollable ? (this.#isVertical ? `scroll-fade-y` : `scroll-fade-x`) : ``;\n return [base, fadeClass, this.dragMode === `reorder` ? `reorder` : ``].filter(Boolean).join(` `);\n }\n\n override render() {\n const caretStyle = this.#computeCaretStyle();\n return html`\n <div class=\"outer ${this.orientation}\">\n <div\n tabindex=0\n @keyup=${this.#onKeyUp}\n @click=${this.#onSlotClick}\n @pointerdown=${this.#onPointerDown}\n @keydown=${this.#onKeyDown}\n @wheel=${this.#onWheel}\n id=\"container\"\n class=${this.#containerClass}\n >\n <slot @slotchange=${this.#onSlotChange}></slot>\n ${caretStyle\n ? html`<div class=\"drop-caret\" style=${caretStyle}></div>`\n : nothing}\n </div>\n <div class=\"list-toolbar ${this.orientation}\" part=\"list-toolbar\">\n <slot name=\"toolbar\" @slotchange=${this.#onToolbarSlotChange}></slot>\n </div>\n </div>`;\n }\n\n #onSlotChange = () => {\n this.updateComplete.then(() => {\n this.#items = (this.shadowRoot?.querySelector(`slot:not([name])`) as HTMLSlotElement)\n ?.assignedElements({ flatten: true }) as TabListItemElement[] ?? [];\n this.#syncIndicatorColors();\n this.#syncHideLabels();\n });\n };\n\n #onToolbarSlotChange = () => {\n const slot = this.shadowRoot?.querySelector(`slot[name=\"toolbar\"]`) as HTMLSlotElement | null;\n const assigned = slot?.assignedElements() ?? [];\n const has = assigned.length > 0;\n this.toggleAttribute(`has-toolbar`, has);\n const toolbarDiv = this.shadowRoot?.querySelector(`.list-toolbar`) as HTMLElement | null;\n if (toolbarDiv)\n toolbarDiv.hidden = !has;\n\n // Force ixfx-toolbar to re-measure after slot assignment\n if (has) {\n const toolbar = assigned[0];\n if (toolbar?.tagName === `IXFX-TOOLBAR`) {\n requestAnimationFrame(() => {\n (toolbar as any)?._scheduleMeasure?.();\n });\n }\n }\n };\n\n override firstUpdated() {\n this.updateComplete.then(() => {\n if (this.#items.length > 0) {\n this.#syncIndicatorColors();\n this.#syncHideLabels();\n }\n this.#onToolbarSlotChange();\n });\n }\n\n override updated(changedProperties: PropertyValues): void {\n super.updated(changedProperties);\n this.classList.toggle(`scrollable`, this.scrollable);\n if (changedProperties.has(`dragMode`)) {\n this.toggleAttribute(`drag-mode`, this.dragMode !== `off`);\n }\n if (changedProperties.has(`hideLabels`)) {\n this.#syncHideLabels();\n }\n }\n\n #syncHideLabels() {\n for (const item of this.#items) {\n if (this.hideLabels) {\n item.setAttribute(`hide-labels`, ``);\n } else {\n item.removeAttribute(`hide-labels`);\n }\n }\n }\n\n #syncIndicatorColors() {\n for (const item of this.#items) {\n item.style.setProperty(`--indicator-color-bottom`, `transparent`);\n item.style.setProperty(`--indicator-color-top`, `transparent`);\n item.style.setProperty(`--indicator-color-left`, `transparent`);\n item.style.setProperty(`--indicator-color-right`, `transparent`);\n\n if (item.hasAttribute(`selected`)) {\n if (this.orientation === `horizontal` && !this.flip) {\n item.style.setProperty(`--indicator-color-bottom`, `var(--accent)`);\n } else if (this.orientation === `horizontal` && this.flip) {\n item.style.setProperty(`--indicator-color-top`, `var(--accent)`);\n } else if (this.#isVertical && !this.flip) {\n item.style.setProperty(`--indicator-color-right`, `var(--accent)`);\n } else if (this.#isVertical && this.flip) {\n item.style.setProperty(`--indicator-color-left`, `var(--accent)`);\n }\n }\n }\n }\n\n #onKeyUp = (event: KeyboardEvent) => {\n const key = event.key;\n if (this.#isVertical) {\n if (key === `ArrowUp`) {\n this.#selectPrevious();\n } else if (key === `ArrowDown`) {\n this.#selectNext();\n }\n } else {\n if (key === `ArrowRight`) {\n this.#selectNext();\n } else if (key === `ArrowLeft`) {\n this.#selectPrevious();\n }\n }\n };\n\n // Escape during drag aborts it (and tears down listeners / releases capture)\n #onKeyDown = (event: KeyboardEvent): void => {\n const state = this.#dragState;\n if (event.key !== `Escape` || !state?.active)\n return;\n event.preventDefault();\n const pointerId = state.pointerId;\n this.#abortDrag();\n this.#endDrag(pointerId);\n };\n\n /**\n * Translate vertical wheel deltas into horizontal scroll on a scrollable\n * horizontal list (and vice-versa for vertical lists). Without this, the\n * default browser behaviour only scrolls horizontally with Shift+wheel or a\n * physical horizontal wheel — neither of which most users reach for on a\n * VS Code-style tab strip.\n *\n * Only kicks in when the list is actually scrollable (overflow content\n * present), and is a no-op when nothing is scrollable in that direction.\n */\n #onWheel = (event: WheelEvent): void => {\n if (!this.scrollable)\n return;\n const el = this.#containerEl;\n if (!el)\n return;\n\n if (!this.#isVertical) {\n if (event.deltaY === 0 || el.scrollWidth <= el.clientWidth)\n return;\n el.scrollLeft = el.scrollLeft + event.deltaY;\n event.preventDefault();\n } else {\n if (event.deltaX === 0 || el.scrollHeight <= el.clientHeight)\n return;\n el.scrollTop = el.scrollTop + event.deltaX;\n event.preventDefault();\n }\n };\n\n getSelectedElement() {\n for (const el of this.#items) {\n if (this.#isSelected(el))\n return el;\n }\n // Fallback: #items may not be populated yet (slotchange hasn't fired).\n // Query the slot directly to find the selected element.\n const slot = this.shadowRoot?.querySelector(`slot`);\n if (slot) {\n const els = (slot as HTMLSlotElement).assignedElements({ flatten: true });\n for (const el of els) {\n if (this.#isSelected(el as HTMLElement))\n return el as TabListItemElement;\n }\n }\n }\n\n #isSelected(el: HTMLElement) {\n const v = el.getAttribute(`selected`);\n if (v === null)\n return false;\n return (el.hasAttribute(`selected`));\n }\n\n selectElement(el: HTMLElement) {\n const existing = this.getSelectedElement();\n if (existing) {\n existing.removeAttribute(`selected`);\n existing.style.setProperty(`--indicator-color-bottom`, `transparent`);\n existing.style.setProperty(`--indicator-color-top`, `transparent`);\n existing.style.setProperty(`--indicator-color-left`, `transparent`);\n existing.style.setProperty(`--indicator-color-right`, `transparent`);\n }\n\n el.setAttribute(`selected`, ``);\n\n if (this.orientation === `horizontal` && !this.flip) {\n el.style.setProperty(`--indicator-color-bottom`, `var(--accent)`);\n } else if (this.orientation === `horizontal` && this.flip) {\n el.style.setProperty(`--indicator-color-top`, `var(--accent)`);\n } else if (this.#isVertical && !this.flip) {\n el.style.setProperty(`--indicator-color-right`, `var(--accent)`);\n } else if (this.#isVertical && this.flip) {\n el.style.setProperty(`--indicator-color-left`, `var(--accent)`);\n }\n }\n\n #selectNext() {\n let found = false;\n for (const el of this.#items) {\n if (this.#isSelected(el) && !found) {\n found = true;\n } else if (found) {\n this.selectElement(el);\n break;\n }\n }\n }\n\n #selectPrevious() {\n let prev: HTMLElement | undefined;\n for (const el of this.#items) {\n if (this.#isSelected(el) && prev) {\n this.selectElement(prev);\n } else {\n prev = el;\n }\n }\n }\n\n #onSlotClick = (event: MouseEvent) => {\n if (this.#suppressNextClick) {\n this.#suppressNextClick = false;\n event.preventDefault();\n event.stopPropagation();\n return;\n }\n\n this.focus();\n const t = (event.target as HTMLElement).closest(`ixfx-tab-list-item`);\n if (!t)\n return;\n const previous = this.getSelectedElement();\n this.selectElement(t);\n\n const forId = t.getAttribute(`for`);\n const previousId = previous?.id;\n this.dispatchEvent(new CustomEvent<TabListChangeDetail>(`change`, {\n bubbles: true,\n detail: {\n previous: previousId,\n selected: t.id,\n for: forId ?? ``,\n },\n }));\n };\n\n // ── Drag handling ────────────────────────────────────────────────────\n\n /**\n * Walk composedPath looking for the nearest ancestor tab-list-item, but bail\n * if the path goes through a `.close-btn` or `.toolbar` (so close/toolbar\n * buttons always win — same delegation pattern used by tooltip-controller).\n */\n #findDragTarget(event: PointerEvent): TabListItemElement | null {\n if (this.dragMode === `off`)\n return null;\n\n const path = event.composedPath();\n let item: TabListItemElement | null = null;\n for (const node of path) {\n if (!(node instanceof HTMLElement))\n continue;\n if (node.classList.contains(`close-btn`) || node.classList.contains(`toolbar`)) {\n return null;\n }\n if (!item && node.tagName === `IXFX-TAB-LIST-ITEM`) {\n item = node as TabListItemElement;\n }\n }\n return item;\n }\n\n #onPointerDown = (event: PointerEvent): void => {\n if (this.dragMode !== `reorder`)\n return;\n if (event.button !== 0)\n return;\n const target = this.#findDragTarget(event);\n if (!target)\n return;\n\n const fromIndex = this.#items.indexOf(target);\n if (fromIndex < 0)\n return;\n\n // Don't capture the pointer yet — doing so on pointerdown would redirect\n // the trailing click event to the capture target (the container), so the\n // click handler would see a container DIV as its target instead of the\n // tab-list-item and `closest('ixfx-tab-list-item')` would return null.\n // We capture only once the drag actually activates (#beginDrag). Until\n // then, an ordinary press still produces a normal click on the tab.\n\n this.#dragState = {\n pointerId: event.pointerId,\n source: target,\n fromIndex,\n startX: event.clientX,\n startY: event.clientY,\n active: false,\n ghost: null,\n gapIndex: undefined,\n };\n\n // Listen at window level for move/up so we don't lose events if the\n // pointer exits the host before pointerup fires.\n window.addEventListener(`pointermove`, this.#onPointerMove);\n window.addEventListener(`pointerup`, this.#onPointerUp);\n window.addEventListener(`pointercancel`, this.#onPointerCancel);\n };\n\n #onPointerMove = (event: PointerEvent): void => {\n const state = this.#dragState;\n if (!state || event.pointerId !== state.pointerId)\n return;\n\n const dx = event.clientX - state.startX;\n const dy = event.clientY - state.startY;\n const distance = Math.hypot(dx, dy);\n\n if (!state.active) {\n if (distance < DRAG_THRESHOLD_PX)\n return;\n this.#beginDrag(event);\n }\n\n state.ghost?.move(event.clientX, event.clientY);\n\n const gap = this.#computeGapIndex(event.clientX, event.clientY);\n if (gap !== state.gapIndex) {\n state.gapIndex = gap;\n this.requestUpdate();\n }\n };\n\n #onPointerUp = (event: PointerEvent): void => {\n const state = this.#dragState;\n if (!state || event.pointerId !== state.pointerId)\n return;\n\n if (state.active) {\n const gap = this.#computeGapIndex(event.clientX, event.clientY);\n this.#commitDrag(state.source, state.fromIndex, gap);\n }\n this.#endDrag(event.pointerId);\n };\n\n #onPointerCancel = (event: PointerEvent): void => {\n const state = this.#dragState;\n if (!state || event.pointerId !== state.pointerId)\n return;\n this.#abortDrag();\n this.#endDrag(event.pointerId);\n };\n\n #beginDrag(event: PointerEvent): void {\n const state = this.#dragState;\n if (!state || state.active)\n return;\n\n // Now that we know we're dragging, capture the pointer so pointermove/up\n // events keep flowing even if the pointer leaves the host element.\n try {\n this.#containerEl?.setPointerCapture(event.pointerId);\n } catch {\n // setPointerCapture can throw if pointer is already captured elsewhere;\n // fall through — window listeners still catch move/up.\n }\n\n state.active = true;\n state.source.setAttribute(`drag-source`, ``);\n\n state.ghost = this.#createGhost(state.source);\n if (state.ghost) {\n state.ghost.move(event.clientX, event.clientY);\n }\n }\n\n #abortDrag(): void {\n const state = this.#dragState;\n if (!state)\n return;\n state.source.removeAttribute(`drag-source`);\n if (state.gapIndex !== undefined) {\n state.gapIndex = undefined;\n this.requestUpdate();\n }\n // Caller is responsible for invoking #endDrag to release pointer capture,\n // tear down listeners, and destroy the ghost.\n }\n\n #endDrag(pointerId: number): void {\n const state = this.#dragState;\n if (!state)\n return;\n\n state.ghost?.destroy();\n state.ghost = null;\n this.#dragState = null;\n\n window.removeEventListener(`pointermove`, this.#onPointerMove);\n window.removeEventListener(`pointerup`, this.#onPointerUp);\n window.removeEventListener(`pointercancel`, this.#onPointerCancel);\n\n try {\n this.#containerEl?.releasePointerCapture(pointerId);\n } catch { /* already released */ }\n\n // #dragState is a private field, not a Lit-reactive property — setting it\n // to null doesn't trigger a re-render on its own. Without this, the caret\n // (and any other drag-time UI) would remain in the DOM after the drag\n // ends, even though the next render would clear them.\n this.requestUpdate();\n }\n\n #commitDrag(source: TabListItemElement, fromIndex: number, gapIndex: number | undefined): void {\n source.removeAttribute(`drag-source`);\n\n // gapIndex is an index into the without-source siblings array: 0 = before\n // the first sibling, siblings.length = after the last. The siblings array\n // already excludes source, so we can insert directly at that position\n // using siblings[gapIndex] as the reference (or null to append).\n const siblings = this.#items.filter(el => el !== source);\n const target = gapIndex === undefined\n ? siblings.length\n : Math.max(0, Math.min(gapIndex, siblings.length));\n\n const parent = source.parentNode;\n if (!parent)\n return;\n\n const ref = siblings[target] ?? null;\n parent.insertBefore(source, ref);\n\n const toIndex = Array.prototype.indexOf.call(parent.children, source);\n if (toIndex === fromIndex) {\n // Dropped in the same gap the item already occupies — no reorder, and no\n // click suppression (a real click on the same tab is still meaningful).\n return;\n }\n\n // Refresh cached #items so subsequent drags / queries see the new order\n // (slotchange does NOT fire on reorder, only on add/remove).\n this.#items = (this.shadowRoot?.querySelector(`slot`) as HTMLSlotElement)\n ?.assignedElements({ flatten: true }) as TabListItemElement[] ?? [];\n this.#syncIndicatorColors();\n\n // A real reorder just happened. The trailing click event (which the\n // browser still fires after pointerup) would re-select whatever is under\n // the release point — not what the user wanted. Suppress it.\n this.#suppressNextClick = true;\n\n this.dispatchEvent(new CustomEvent<TabListReorderDetail>(`reorder`, {\n bubbles: true,\n detail: {\n id: source.id,\n fromIndex,\n toIndex,\n },\n }));\n }\n\n #createGhost(source: TabListItemElement): DragGhost | null {\n const content = document.createElement(`span`);\n content.style.display = `inline-flex`;\n content.style.alignItems = `center`;\n content.style.gap = `var(--space-xs, 4px)`;\n content.style.minWidth = `0`;\n\n const iconName = source.iconName;\n if (iconName) {\n const icon = document.createElement(`ixfx-icon`) as HTMLElement & { name: string };\n icon.name = iconName;\n content.appendChild(icon);\n }\n\n // Reconstruct label text from light-DOM children that aren't slotted.\n // We want the text content of the source item, excluding `slot=\"toolbar\"`\n // descendants (the close button lives inside the item's shadow DOM, not\n // its light DOM, so it isn't part of childNodes here).\n const label = document.createElement(`span`);\n label.textContent = (source.textContent ?? ``).trim();\n label.style.overflow = `hidden`;\n label.style.textOverflow = `ellipsis`;\n label.style.whiteSpace = `nowrap`;\n content.appendChild(label);\n\n try {\n return createDragGhost({ anchor: source, content });\n } catch {\n return null;\n }\n }\n\n /**\n * Compute the index of the gap nearest the pointer, ignoring the source item.\n * Returns 0..#items.length-1; undefined if no siblings.\n *\n * Per the plan: drag-mode=\"reorder\" has \"no sense of breaking out\", so the\n * perpendicular axis is ignored — the pointer always resolves to some gap.\n */\n #computeGapIndex(clientX: number, clientY: number): number | undefined {\n const state = this.#dragState;\n if (!state)\n return undefined;\n const siblings = this.#items.filter(el => el !== state.source);\n if (siblings.length === 0)\n return 0;\n\n const useX = !this.#isVertical;\n\n let bestGap = 0;\n let bestDist = Infinity;\n\n // For each sibling, decide: gap is \"before this sibling\" or \"after\" based\n // on which side of the midpoint the pointer is on. Pick the gap whose\n // position (the midpoint if \"before\", the edge if \"after\") is closest to\n // the pointer on the relevant axis.\n for (let i = 0; i < siblings.length; i++) {\n const rect = siblings[i].getBoundingClientRect();\n const start = useX ? rect.left : rect.top;\n const end = useX ? rect.right : rect.bottom;\n const mid = (start + end) / 2;\n const pos = useX ? clientX : clientY;\n\n // Two candidate gaps: before this sibling (at `start`) and after (at `end`).\n // \"Before\" gap index = i; \"after\" gap index = i + 1.\n const dStart = Math.abs(pos - start);\n const dMid = Math.abs(pos - mid);\n const dEnd = Math.abs(pos - end);\n\n // Heuristic: if pointer is left/above midpoint, prefer the \"before\" gap;\n // otherwise prefer the \"after\" gap. Within those, the closer edge wins.\n const preferBefore = pos < mid;\n if (preferBefore) {\n // Gap i (before this sibling)\n if (dStart < bestDist) {\n bestDist = dStart;\n bestGap = i;\n }\n } else {\n // Gap i+1 (after this sibling)\n if (dEnd < bestDist) {\n bestDist = dEnd;\n bestGap = i + 1;\n }\n }\n // The midpoint-distance is mostly redundant with the above but handles\n // ties near the boundary cleanly — no-op for the chosen gap.\n void dMid;\n }\n\n return bestGap;\n }\n\n #computeCaretStyle(): string | undefined {\n const state = this.#dragState;\n if (!state?.active || state.gapIndex === undefined)\n return undefined;\n\n const siblings = this.#items.filter(el => el !== state.source);\n if (siblings.length === 0)\n return undefined;\n\n const useX = !this.#isVertical;\n const containerRect = this.#containerEl?.getBoundingClientRect();\n if (!containerRect)\n return undefined;\n\n const gap = Math.max(0, Math.min(state.gapIndex, siblings.length));\n let rect: DOMRect | null = null;\n if (gap < siblings.length) {\n rect = siblings[gap].getBoundingClientRect();\n } else {\n // After the last sibling: use its trailing edge\n const last = siblings[siblings.length - 1].getBoundingClientRect();\n rect = last;\n }\n\n if (useX) {\n const x = (gap < siblings.length ? rect.left : rect.right) - containerRect.left;\n const y = 0;\n const h = containerRect.height;\n const w = 2;\n return `transform: translate(${x - w / 2}px, ${y}px); width: ${w}px; height: ${h}px;`;\n } else {\n const y = (gap < siblings.length ? rect.top : rect.bottom) - containerRect.top;\n const x = 0;\n const w = containerRect.width;\n const h = 2;\n return `transform: translate(${x}px, ${y - h / 2}px); width: ${w}px; height: ${h}px;`;\n }\n }\n\n get #containerEl(): HTMLElement | null {\n return this.shadowRoot?.getElementById(`container`) ?? null;\n }\n\n static styles = [themeFallbacks, scrollFadeStyles(`x`), scrollFadeStyles(`y`), css`\n :host {\n display: inline-block;\n --activated-colour: var(--accent);\n --colour: var(--surface-3-text);\n --scroll-max-height: 200px;\n --allow-overflow: hidden;\n color: var(--colour);\n border: var(--border, none);\n border-color: var(--surface-5);\n border-radius: var(--radius-l, 8px) var(--radius-l, 8px) 0 0;\n\n }\n :host(.scrollable) {\n max-height: var(--scroll-max-height);\n }\n :host([hide-labels]) {\n display: inline-flex;\n width: auto;\n }\n text {\n user-select:none;\n }\n .outer {\n display: flex;\n width: 100%;\n height: 100%;\n gap: var(--space-s);\n align-items: stretch;\n box-sizing: border-box;\n }\n .outer.horizontal {\n flex-direction: row;\n align-items: center;\n }\n .outer.vertical-stack,\n .outer.vertical-side {\n flex-direction: column;\n }\n .list-toolbar {\n display: flex;\n align-items: center;\n gap: var(--space-xs);\n flex: 0 0 auto;\n width: max-content;\n max-width: 50%;\n overflow: visible;\n }\n .list-toolbar.horizontal ::slotted(*:not(ixfx-toolbar)) {\n margin-left: auto;\n }\n .list-toolbar[hidden] {\n display: none !important;\n }\n .list-toolbar[hidden] {\n display: none !important;\n }\n ::slotted(ixfx-toolbar) {\n --toolbar-bg: transparent;\n --toolbar-border: none;\n --toolbar-padding: 0;\n width: max-content;\n max-width: 100%;\n min-width: 150px;\n flex-shrink: 0;\n }\n #container {\n display:flex;\n position: relative;\n flex: 1 1 0%;\n min-width: 0;\n min-height: 0;\n }\n .outer.vertical-stack #container,\n .outer.vertical-side #container {\n flex: 1 1 auto;\n height: 100%;\n }\n #container.horizontal {\n flex-direction: row;\n overflow: hidden;\n }\n #container.horizontal.scrollable {\n overflow-x: scroll;\n --allow-overflow: visible;\n scrollbar-width: none;\n }\n #container.horizontal.scrollable::-webkit-scrollbar {\n display: none;\n }\n #container.horizontal.scrollable ::slotted(ixfx-tab-list-item) {\n flex-shrink: 0;\n }\n #container.vertical-stack {\n flex-direction: column;\n height: 100%;\n }\n #container.vertical-stack.flip {\n flex-direction: column-reverse;\n }\n #container.vertical-stack.scrollable {\n overflow-y: scroll;\n max-height: var(--scroll-max-height);\n --allow-overflow: visible;\n scrollbar-width: none;\n }\n #container.vertical-stack.scrollable::-webkit-scrollbar {\n display: none;\n }\n #container.vertical-side {\n flex-direction: column;\n height: 100%;\n }\n #container.vertical-side.flip {\n flex-direction: column-reverse;\n }\n #container.vertical-side.scrollable {\n overflow-y: scroll;\n max-height: var(--scroll-max-height);\n --allow-overflow: visible;\n scrollbar-width: none;\n }\n #container.vertical-side.scrollable::-webkit-scrollbar {\n display: none;\n }\n\n ::slotted(ixfx-tab-list-item) {\n flex: 1 1 auto;\n min-width: 0;\n }\n\n :host([shrink]) ::slotted(ixfx-tab-list-item) {\n flex: 0 0 auto;\n }\n\n #container.vertical-side ::slotted(ixfx-tab-list-item) {\n writing-mode: vertical-lr;\n text-orientation: mixed;\n }\n #container.vertical-side.flip ::slotted(ixfx-tab-list-item) {\n writing-mode: vertical-rl;\n text-orientation: mixed;\n }\n\n /* Drag affordances — opt-in via drag-mode=\"reorder\" on the host (list). */\n #container.reorder ::slotted(ixfx-tab-list-item) {\n cursor: grab;\n }\n :host([drag-mode]) ::slotted(ixfx-tab-list-item[drag-source]) {\n cursor: grabbing;\n opacity: 0.4;\n transition: opacity var(--transition, 0.15s ease-out);\n }\n\n /* Insertion caret shown during an active drag. */\n .drop-caret {\n position: absolute;\n background: var(--accent);\n border-radius: 1px;\n pointer-events: none;\n z-index: 1;\n }\n\n ::slotted(.activated) {\n --colour: var(--activated-colour);\n }\n `];\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ixfx-tab-list\": TabListElement;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"tabs.js","names":["#refreshItems","#syncIndicatorColors","#syncHideLabels","#syncLabelNogrow","#isVertical","#selectPrevious","#selectNext","#dragState","#abortDrag","#endDrag","#containerEl","#suppressNextClick","#findDragTarget","#items","#onPointerMove","#onPointerUp","#onPointerCancel","#beginDrag","#computeGapIndex","#commitDrag","#containerClass","#computeCaretStyle","#onKeyUp","#onSlotClick","#onPointerDown","#onKeyDown","#onWheel","#onSlotChange","#onToolbarSlotChange","#isSelected","#createGhost","customElement"],"sources":["../src/tabs/tab-list.ts"],"sourcesContent":["import type { PropertyValues } from \"lit\";\nimport type { DragGhost } from \"../util/drag-ghost.js\";\nimport type { TabListItemElement } from \"./tab-list-item.js\";\nimport { css, html, LitElement, nothing } from \"lit\";\nimport { property } from \"lit/decorators.js\";\nimport { themeFallbacks } from \"../styles/fallbacks.js\";\nimport { scrollFadeStyles } from \"../styles/scroll-fade.js\";\nimport { safeCustomElement as customElement } from \"../util/custom-elements.js\";\nimport { createDragGhost } from \"../util/drag-ghost.js\";\n\n/**\n * Events\n * 'change' - new tab selected { previous:string, selected:string, for:string }\n * 'reorder' - tab moved within the list { id:string, fromIndex:number, toIndex:number }\n */\nexport type TabListChangeDetail = {\n readonly previous: string | undefined;\n readonly selected: string;\n readonly for: string;\n};\n\nexport type TabListChangeEvent = CustomEvent<TabListChangeDetail>;\n\nexport type TabListReorderDetail = {\n readonly id: string;\n readonly fromIndex: number;\n readonly toIndex: number;\n};\n\nexport type TabListReorderEvent = CustomEvent<TabListReorderDetail>;\n\nexport type TabListDragMode = `off` | `reorder`;\n\n/** Pointer movement threshold (px) before a press becomes a drag. */\nconst DRAG_THRESHOLD_PX = 5;\n\n@customElement(`ixfx-tab-list`)\nexport class TabListElement extends LitElement {\n @property({ type: String })\n orientation: `horizontal` | `vertical-stack` | `vertical-side` = `horizontal`;\n\n @property({ type: Boolean })\n flip = false;\n\n @property({ type: Boolean })\n scrollable = false;\n\n @property({ type: Boolean, attribute: `hide-labels` })\n hideLabels = false;\n\n /**\n * Labels of the child items do not stretch to fill each tab\n * (`flex: 0 0 auto`), so status content sits next to the label text.\n * Mirrored onto every item as a `label-nogrow` attribute.\n */\n @property({ type: Boolean, attribute: `label-nogrow` })\n labelNogrow = false;\n\n @property({ type: Boolean, reflect: true })\n shrink = false;\n\n @property({ type: String, attribute: `drag-mode` })\n dragMode: TabListDragMode = `off`;\n\n #items: TabListItemElement[] = [];\n\n // ── Drag state (not reactive) ────────────────────────────────────────\n #dragState: {\n pointerId: number;\n source: TabListItemElement;\n fromIndex: number;\n startX: number;\n startY: number;\n active: boolean;\n ghost: DragGhost | null;\n gapIndex: number | undefined;\n } | null = null;\n\n #suppressNextClick = false;\n\n get #isVertical(): boolean {\n return this.orientation === `vertical-stack` || this.orientation === `vertical-side`;\n }\n\n get #containerClass(): string {\n const base = this.scrollable ? `${this.orientation} scrollable` : this.orientation;\n const fadeClass = this.scrollable ? (this.#isVertical ? `scroll-fade-y` : `scroll-fade-x`) : ``;\n return [base, fadeClass, this.dragMode === `reorder` ? `reorder` : ``].filter(Boolean).join(` `);\n }\n\n override render() {\n const caretStyle = this.#computeCaretStyle();\n return html`\n <div class=\"outer ${this.orientation}\">\n <div\n tabindex=0\n @keyup=${this.#onKeyUp}\n @click=${this.#onSlotClick}\n @pointerdown=${this.#onPointerDown}\n @keydown=${this.#onKeyDown}\n @wheel=${this.#onWheel}\n id=\"container\"\n class=${this.#containerClass}\n >\n <slot @slotchange=${this.#onSlotChange}></slot>\n ${caretStyle\n ? html`<div class=\"drop-caret\" style=${caretStyle}></div>`\n : nothing}\n </div>\n <div class=\"list-toolbar ${this.orientation}\" part=\"list-toolbar\">\n <slot name=\"toolbar\" @slotchange=${this.#onToolbarSlotChange}></slot>\n </div>\n </div>`;\n }\n\n #onSlotChange = () => {\n this.updateComplete.then(() => {\n this.#refreshItems();\n this.#syncIndicatorColors();\n this.#syncHideLabels();\n this.#syncLabelNogrow();\n });\n };\n\n /**\n * Re-read the assigned tab items. Some environments do not fire slotchange\n * for a slot's initial assignment, so firstUpdated also calls this before\n * syncing item attributes.\n */\n #refreshItems(): void {\n this.#items = (this.shadowRoot?.querySelector(`slot:not([name])`) as HTMLSlotElement)\n ?.assignedElements({ flatten: true }) as TabListItemElement[] ?? [];\n }\n\n #onToolbarSlotChange = () => {\n const slot = this.shadowRoot?.querySelector(`slot[name=\"toolbar\"]`) as HTMLSlotElement | null;\n const assigned = slot?.assignedElements() ?? [];\n const has = assigned.length > 0;\n this.toggleAttribute(`has-toolbar`, has);\n const toolbarDiv = this.shadowRoot?.querySelector(`.list-toolbar`) as HTMLElement | null;\n if (toolbarDiv)\n toolbarDiv.hidden = !has;\n\n // Force ixfx-toolbar to re-measure after slot assignment\n if (has) {\n const toolbar = assigned[0];\n if (toolbar?.tagName === `IXFX-TOOLBAR`) {\n requestAnimationFrame(() => {\n (toolbar as any)?._scheduleMeasure?.();\n });\n }\n }\n };\n\n override firstUpdated() {\n this.updateComplete.then(() => {\n this.#refreshItems();\n this.#syncIndicatorColors();\n this.#syncHideLabels();\n this.#syncLabelNogrow();\n this.#onToolbarSlotChange();\n });\n }\n\n override updated(changedProperties: PropertyValues): void {\n super.updated(changedProperties);\n this.classList.toggle(`scrollable`, this.scrollable);\n if (changedProperties.has(`dragMode`)) {\n this.toggleAttribute(`drag-mode`, this.dragMode !== `off`);\n }\n if (changedProperties.has(`hideLabels`)) {\n this.#syncHideLabels();\n }\n if (changedProperties.has(`labelNogrow`)) {\n this.#syncLabelNogrow();\n }\n }\n\n #syncHideLabels() {\n for (const item of this.#items) {\n if (this.hideLabels) {\n item.setAttribute(`hide-labels`, ``);\n } else {\n item.removeAttribute(`hide-labels`);\n }\n }\n }\n\n #syncLabelNogrow() {\n for (const item of this.#items) {\n if (this.labelNogrow) {\n item.setAttribute(`label-nogrow`, ``);\n } else {\n item.removeAttribute(`label-nogrow`);\n }\n }\n }\n\n #syncIndicatorColors() {\n for (const item of this.#items) {\n item.style.setProperty(`--indicator-color-bottom`, `transparent`);\n item.style.setProperty(`--indicator-color-top`, `transparent`);\n item.style.setProperty(`--indicator-color-left`, `transparent`);\n item.style.setProperty(`--indicator-color-right`, `transparent`);\n\n if (item.hasAttribute(`selected`)) {\n if (this.orientation === `horizontal` && !this.flip) {\n item.style.setProperty(`--indicator-color-bottom`, `var(--accent)`);\n } else if (this.orientation === `horizontal` && this.flip) {\n item.style.setProperty(`--indicator-color-top`, `var(--accent)`);\n } else if (this.#isVertical && !this.flip) {\n item.style.setProperty(`--indicator-color-right`, `var(--accent)`);\n } else if (this.#isVertical && this.flip) {\n item.style.setProperty(`--indicator-color-left`, `var(--accent)`);\n }\n }\n }\n }\n\n #onKeyUp = (event: KeyboardEvent) => {\n const key = event.key;\n if (this.#isVertical) {\n if (key === `ArrowUp`) {\n this.#selectPrevious();\n } else if (key === `ArrowDown`) {\n this.#selectNext();\n }\n } else {\n if (key === `ArrowRight`) {\n this.#selectNext();\n } else if (key === `ArrowLeft`) {\n this.#selectPrevious();\n }\n }\n };\n\n // Escape during drag aborts it (and tears down listeners / releases capture)\n #onKeyDown = (event: KeyboardEvent): void => {\n const state = this.#dragState;\n if (event.key !== `Escape` || !state?.active)\n return;\n event.preventDefault();\n const pointerId = state.pointerId;\n this.#abortDrag();\n this.#endDrag(pointerId);\n };\n\n /**\n * Translate vertical wheel deltas into horizontal scroll on a scrollable\n * horizontal list (and vice-versa for vertical lists). Without this, the\n * default browser behaviour only scrolls horizontally with Shift+wheel or a\n * physical horizontal wheel — neither of which most users reach for on a\n * VS Code-style tab strip.\n *\n * Only kicks in when the list is actually scrollable (overflow content\n * present), and is a no-op when nothing is scrollable in that direction.\n */\n #onWheel = (event: WheelEvent): void => {\n if (!this.scrollable)\n return;\n const el = this.#containerEl;\n if (!el)\n return;\n\n if (!this.#isVertical) {\n if (event.deltaY === 0 || el.scrollWidth <= el.clientWidth)\n return;\n el.scrollLeft = el.scrollLeft + event.deltaY;\n event.preventDefault();\n } else {\n if (event.deltaX === 0 || el.scrollHeight <= el.clientHeight)\n return;\n el.scrollTop = el.scrollTop + event.deltaX;\n event.preventDefault();\n }\n };\n\n getSelectedElement() {\n for (const el of this.#items) {\n if (this.#isSelected(el))\n return el;\n }\n // Fallback: #items may not be populated yet (slotchange hasn't fired).\n // Query the slot directly to find the selected element.\n const slot = this.shadowRoot?.querySelector(`slot`);\n if (slot) {\n const els = (slot as HTMLSlotElement).assignedElements({ flatten: true });\n for (const el of els) {\n if (this.#isSelected(el as HTMLElement))\n return el as TabListItemElement;\n }\n }\n }\n\n #isSelected(el: HTMLElement) {\n const v = el.getAttribute(`selected`);\n if (v === null)\n return false;\n return (el.hasAttribute(`selected`));\n }\n\n selectElement(el: HTMLElement) {\n const existing = this.getSelectedElement();\n if (existing) {\n existing.removeAttribute(`selected`);\n existing.style.setProperty(`--indicator-color-bottom`, `transparent`);\n existing.style.setProperty(`--indicator-color-top`, `transparent`);\n existing.style.setProperty(`--indicator-color-left`, `transparent`);\n existing.style.setProperty(`--indicator-color-right`, `transparent`);\n }\n\n el.setAttribute(`selected`, ``);\n\n if (this.orientation === `horizontal` && !this.flip) {\n el.style.setProperty(`--indicator-color-bottom`, `var(--accent)`);\n } else if (this.orientation === `horizontal` && this.flip) {\n el.style.setProperty(`--indicator-color-top`, `var(--accent)`);\n } else if (this.#isVertical && !this.flip) {\n el.style.setProperty(`--indicator-color-right`, `var(--accent)`);\n } else if (this.#isVertical && this.flip) {\n el.style.setProperty(`--indicator-color-left`, `var(--accent)`);\n }\n }\n\n #selectNext() {\n let found = false;\n for (const el of this.#items) {\n if (this.#isSelected(el) && !found) {\n found = true;\n } else if (found) {\n this.selectElement(el);\n break;\n }\n }\n }\n\n #selectPrevious() {\n let prev: HTMLElement | undefined;\n for (const el of this.#items) {\n if (this.#isSelected(el) && prev) {\n this.selectElement(prev);\n } else {\n prev = el;\n }\n }\n }\n\n #onSlotClick = (event: MouseEvent) => {\n if (this.#suppressNextClick) {\n this.#suppressNextClick = false;\n event.preventDefault();\n event.stopPropagation();\n return;\n }\n\n this.focus();\n const t = (event.target as HTMLElement).closest(`ixfx-tab-list-item`);\n if (!t)\n return;\n const previous = this.getSelectedElement();\n this.selectElement(t);\n\n const forId = t.getAttribute(`for`);\n const previousId = previous?.id;\n this.dispatchEvent(new CustomEvent<TabListChangeDetail>(`change`, {\n bubbles: true,\n detail: {\n previous: previousId,\n selected: t.id,\n for: forId ?? ``,\n },\n }));\n };\n\n // ── Drag handling ────────────────────────────────────────────────────\n\n /**\n * Walk composedPath looking for the nearest ancestor tab-list-item, but bail\n * if the path goes through a `.close-btn` or `.toolbar` (so close/toolbar\n * buttons always win — same delegation pattern used by tooltip-controller).\n */\n #findDragTarget(event: PointerEvent): TabListItemElement | null {\n if (this.dragMode === `off`)\n return null;\n\n const path = event.composedPath();\n let item: TabListItemElement | null = null;\n for (const node of path) {\n if (!(node instanceof HTMLElement))\n continue;\n if (node.classList.contains(`close-btn`) || node.classList.contains(`toolbar`)) {\n return null;\n }\n if (!item && node.tagName === `IXFX-TAB-LIST-ITEM`) {\n item = node as TabListItemElement;\n }\n }\n return item;\n }\n\n #onPointerDown = (event: PointerEvent): void => {\n if (this.dragMode !== `reorder`)\n return;\n if (event.button !== 0)\n return;\n const target = this.#findDragTarget(event);\n if (!target)\n return;\n\n const fromIndex = this.#items.indexOf(target);\n if (fromIndex < 0)\n return;\n\n // Don't capture the pointer yet — doing so on pointerdown would redirect\n // the trailing click event to the capture target (the container), so the\n // click handler would see a container DIV as its target instead of the\n // tab-list-item and `closest('ixfx-tab-list-item')` would return null.\n // We capture only once the drag actually activates (#beginDrag). Until\n // then, an ordinary press still produces a normal click on the tab.\n\n this.#dragState = {\n pointerId: event.pointerId,\n source: target,\n fromIndex,\n startX: event.clientX,\n startY: event.clientY,\n active: false,\n ghost: null,\n gapIndex: undefined,\n };\n\n // Listen at window level for move/up so we don't lose events if the\n // pointer exits the host before pointerup fires.\n window.addEventListener(`pointermove`, this.#onPointerMove);\n window.addEventListener(`pointerup`, this.#onPointerUp);\n window.addEventListener(`pointercancel`, this.#onPointerCancel);\n };\n\n #onPointerMove = (event: PointerEvent): void => {\n const state = this.#dragState;\n if (!state || event.pointerId !== state.pointerId)\n return;\n\n const dx = event.clientX - state.startX;\n const dy = event.clientY - state.startY;\n const distance = Math.hypot(dx, dy);\n\n if (!state.active) {\n if (distance < DRAG_THRESHOLD_PX)\n return;\n this.#beginDrag(event);\n }\n\n state.ghost?.move(event.clientX, event.clientY);\n\n const gap = this.#computeGapIndex(event.clientX, event.clientY);\n if (gap !== state.gapIndex) {\n state.gapIndex = gap;\n this.requestUpdate();\n }\n };\n\n #onPointerUp = (event: PointerEvent): void => {\n const state = this.#dragState;\n if (!state || event.pointerId !== state.pointerId)\n return;\n\n if (state.active) {\n const gap = this.#computeGapIndex(event.clientX, event.clientY);\n this.#commitDrag(state.source, state.fromIndex, gap);\n }\n this.#endDrag(event.pointerId);\n };\n\n #onPointerCancel = (event: PointerEvent): void => {\n const state = this.#dragState;\n if (!state || event.pointerId !== state.pointerId)\n return;\n this.#abortDrag();\n this.#endDrag(event.pointerId);\n };\n\n #beginDrag(event: PointerEvent): void {\n const state = this.#dragState;\n if (!state || state.active)\n return;\n\n // Now that we know we're dragging, capture the pointer so pointermove/up\n // events keep flowing even if the pointer leaves the host element.\n try {\n this.#containerEl?.setPointerCapture(event.pointerId);\n } catch {\n // setPointerCapture can throw if pointer is already captured elsewhere;\n // fall through — window listeners still catch move/up.\n }\n\n state.active = true;\n state.source.setAttribute(`drag-source`, ``);\n\n state.ghost = this.#createGhost(state.source);\n if (state.ghost) {\n state.ghost.move(event.clientX, event.clientY);\n }\n }\n\n #abortDrag(): void {\n const state = this.#dragState;\n if (!state)\n return;\n state.source.removeAttribute(`drag-source`);\n if (state.gapIndex !== undefined) {\n state.gapIndex = undefined;\n this.requestUpdate();\n }\n // Caller is responsible for invoking #endDrag to release pointer capture,\n // tear down listeners, and destroy the ghost.\n }\n\n #endDrag(pointerId: number): void {\n const state = this.#dragState;\n if (!state)\n return;\n\n state.ghost?.destroy();\n state.ghost = null;\n this.#dragState = null;\n\n window.removeEventListener(`pointermove`, this.#onPointerMove);\n window.removeEventListener(`pointerup`, this.#onPointerUp);\n window.removeEventListener(`pointercancel`, this.#onPointerCancel);\n\n try {\n this.#containerEl?.releasePointerCapture(pointerId);\n } catch { /* already released */ }\n\n // #dragState is a private field, not a Lit-reactive property — setting it\n // to null doesn't trigger a re-render on its own. Without this, the caret\n // (and any other drag-time UI) would remain in the DOM after the drag\n // ends, even though the next render would clear them.\n this.requestUpdate();\n }\n\n #commitDrag(source: TabListItemElement, fromIndex: number, gapIndex: number | undefined): void {\n source.removeAttribute(`drag-source`);\n\n // gapIndex is an index into the without-source siblings array: 0 = before\n // the first sibling, siblings.length = after the last. The siblings array\n // already excludes source, so we can insert directly at that position\n // using siblings[gapIndex] as the reference (or null to append).\n const siblings = this.#items.filter(el => el !== source);\n const target = gapIndex === undefined\n ? siblings.length\n : Math.max(0, Math.min(gapIndex, siblings.length));\n\n const parent = source.parentNode;\n if (!parent)\n return;\n\n const ref = siblings[target] ?? null;\n parent.insertBefore(source, ref);\n\n const toIndex = Array.prototype.indexOf.call(parent.children, source);\n if (toIndex === fromIndex) {\n // Dropped in the same gap the item already occupies — no reorder, and no\n // click suppression (a real click on the same tab is still meaningful).\n return;\n }\n\n // Refresh cached #items so subsequent drags / queries see the new order\n // (slotchange does NOT fire on reorder, only on add/remove).\n this.#items = (this.shadowRoot?.querySelector(`slot`) as HTMLSlotElement)\n ?.assignedElements({ flatten: true }) as TabListItemElement[] ?? [];\n this.#syncIndicatorColors();\n\n // A real reorder just happened. The trailing click event (which the\n // browser still fires after pointerup) would re-select whatever is under\n // the release point — not what the user wanted. Suppress it.\n this.#suppressNextClick = true;\n\n this.dispatchEvent(new CustomEvent<TabListReorderDetail>(`reorder`, {\n bubbles: true,\n detail: {\n id: source.id,\n fromIndex,\n toIndex,\n },\n }));\n }\n\n #createGhost(source: TabListItemElement): DragGhost | null {\n const content = document.createElement(`span`);\n content.style.display = `inline-flex`;\n content.style.alignItems = `center`;\n content.style.gap = `var(--space-xs, 4px)`;\n content.style.minWidth = `0`;\n\n const iconName = source.iconName;\n if (iconName) {\n const icon = document.createElement(`ixfx-icon`) as HTMLElement & { name: string };\n icon.name = iconName;\n content.appendChild(icon);\n }\n\n // Reconstruct label text from light-DOM children that aren't slotted.\n // We want the text content of the source item, excluding `slot=\"toolbar\"`\n // descendants (the close button lives inside the item's shadow DOM, not\n // its light DOM, so it isn't part of childNodes here).\n const label = document.createElement(`span`);\n label.textContent = (source.textContent ?? ``).trim();\n label.style.overflow = `hidden`;\n label.style.textOverflow = `ellipsis`;\n label.style.whiteSpace = `nowrap`;\n content.appendChild(label);\n\n try {\n return createDragGhost({ anchor: source, content });\n } catch {\n return null;\n }\n }\n\n /**\n * Compute the index of the gap nearest the pointer, ignoring the source item.\n * Returns 0..#items.length-1; undefined if no siblings.\n *\n * Per the plan: drag-mode=\"reorder\" has \"no sense of breaking out\", so the\n * perpendicular axis is ignored — the pointer always resolves to some gap.\n */\n #computeGapIndex(clientX: number, clientY: number): number | undefined {\n const state = this.#dragState;\n if (!state)\n return undefined;\n const siblings = this.#items.filter(el => el !== state.source);\n if (siblings.length === 0)\n return 0;\n\n const useX = !this.#isVertical;\n\n let bestGap = 0;\n let bestDist = Infinity;\n\n // For each sibling, decide: gap is \"before this sibling\" or \"after\" based\n // on which side of the midpoint the pointer is on. Pick the gap whose\n // position (the midpoint if \"before\", the edge if \"after\") is closest to\n // the pointer on the relevant axis.\n for (let i = 0; i < siblings.length; i++) {\n const rect = siblings[i].getBoundingClientRect();\n const start = useX ? rect.left : rect.top;\n const end = useX ? rect.right : rect.bottom;\n const mid = (start + end) / 2;\n const pos = useX ? clientX : clientY;\n\n // Two candidate gaps: before this sibling (at `start`) and after (at `end`).\n // \"Before\" gap index = i; \"after\" gap index = i + 1.\n const dStart = Math.abs(pos - start);\n const dMid = Math.abs(pos - mid);\n const dEnd = Math.abs(pos - end);\n\n // Heuristic: if pointer is left/above midpoint, prefer the \"before\" gap;\n // otherwise prefer the \"after\" gap. Within those, the closer edge wins.\n const preferBefore = pos < mid;\n if (preferBefore) {\n // Gap i (before this sibling)\n if (dStart < bestDist) {\n bestDist = dStart;\n bestGap = i;\n }\n } else {\n // Gap i+1 (after this sibling)\n if (dEnd < bestDist) {\n bestDist = dEnd;\n bestGap = i + 1;\n }\n }\n // The midpoint-distance is mostly redundant with the above but handles\n // ties near the boundary cleanly — no-op for the chosen gap.\n void dMid;\n }\n\n return bestGap;\n }\n\n #computeCaretStyle(): string | undefined {\n const state = this.#dragState;\n if (!state?.active || state.gapIndex === undefined)\n return undefined;\n\n const siblings = this.#items.filter(el => el !== state.source);\n if (siblings.length === 0)\n return undefined;\n\n const useX = !this.#isVertical;\n const containerRect = this.#containerEl?.getBoundingClientRect();\n if (!containerRect)\n return undefined;\n\n const gap = Math.max(0, Math.min(state.gapIndex, siblings.length));\n let rect: DOMRect | null = null;\n if (gap < siblings.length) {\n rect = siblings[gap].getBoundingClientRect();\n } else {\n // After the last sibling: use its trailing edge\n const last = siblings[siblings.length - 1].getBoundingClientRect();\n rect = last;\n }\n\n if (useX) {\n const x = (gap < siblings.length ? rect.left : rect.right) - containerRect.left;\n const y = 0;\n const h = containerRect.height;\n const w = 2;\n return `transform: translate(${x - w / 2}px, ${y}px); width: ${w}px; height: ${h}px;`;\n } else {\n const y = (gap < siblings.length ? rect.top : rect.bottom) - containerRect.top;\n const x = 0;\n const w = containerRect.width;\n const h = 2;\n return `transform: translate(${x}px, ${y - h / 2}px); width: ${w}px; height: ${h}px;`;\n }\n }\n\n get #containerEl(): HTMLElement | null {\n return this.shadowRoot?.getElementById(`container`) ?? null;\n }\n\n static styles = [themeFallbacks, scrollFadeStyles(`x`), scrollFadeStyles(`y`), css`\n :host {\n display: inline-block;\n --activated-colour: var(--accent);\n --colour: var(--surface-3-text);\n --scroll-max-height: 200px;\n --allow-overflow: hidden;\n color: var(--colour);\n border: var(--border, none);\n border-color: var(--surface-5);\n border-radius: var(--radius-l, 8px) var(--radius-l, 8px) 0 0;\n\n }\n :host(.scrollable) {\n max-height: var(--scroll-max-height);\n }\n :host([hide-labels]) {\n display: inline-flex;\n width: auto;\n }\n text {\n user-select:none;\n }\n .outer {\n display: flex;\n width: 100%;\n height: 100%;\n gap: var(--space-s);\n align-items: stretch;\n box-sizing: border-box;\n }\n .outer.horizontal {\n flex-direction: row;\n align-items: center;\n }\n .outer.vertical-stack,\n .outer.vertical-side {\n flex-direction: column;\n }\n .list-toolbar {\n display: flex;\n align-items: center;\n gap: var(--space-xs);\n flex: 0 0 auto;\n width: max-content;\n max-width: 50%;\n overflow: visible;\n }\n .list-toolbar.horizontal ::slotted(*:not(ixfx-toolbar)) {\n margin-left: auto;\n }\n .list-toolbar[hidden] {\n display: none !important;\n }\n .list-toolbar[hidden] {\n display: none !important;\n }\n ::slotted(ixfx-toolbar) {\n --toolbar-bg: transparent;\n --toolbar-border: none;\n --toolbar-padding: 0;\n width: max-content;\n max-width: 100%;\n min-width: 150px;\n flex-shrink: 0;\n }\n #container {\n display:flex;\n position: relative;\n flex: 1 1 0%;\n min-width: 0;\n min-height: 0;\n }\n .outer.vertical-stack #container,\n .outer.vertical-side #container {\n flex: 1 1 auto;\n height: 100%;\n }\n #container.horizontal {\n flex-direction: row;\n overflow: hidden;\n }\n #container.horizontal.scrollable {\n overflow-x: scroll;\n --allow-overflow: visible;\n scrollbar-width: none;\n }\n #container.horizontal.scrollable::-webkit-scrollbar {\n display: none;\n }\n #container.horizontal.scrollable ::slotted(ixfx-tab-list-item) {\n flex-shrink: 0;\n }\n #container.vertical-stack {\n flex-direction: column;\n height: 100%;\n }\n #container.vertical-stack.flip {\n flex-direction: column-reverse;\n }\n #container.vertical-stack.scrollable {\n overflow-y: scroll;\n max-height: var(--scroll-max-height);\n --allow-overflow: visible;\n scrollbar-width: none;\n }\n #container.vertical-stack.scrollable::-webkit-scrollbar {\n display: none;\n }\n #container.vertical-side {\n flex-direction: column;\n height: 100%;\n }\n #container.vertical-side.flip {\n flex-direction: column-reverse;\n }\n #container.vertical-side.scrollable {\n overflow-y: scroll;\n max-height: var(--scroll-max-height);\n --allow-overflow: visible;\n scrollbar-width: none;\n }\n #container.vertical-side.scrollable::-webkit-scrollbar {\n display: none;\n }\n\n ::slotted(ixfx-tab-list-item) {\n flex: 1 1 auto;\n min-width: 0;\n }\n\n :host([shrink]) ::slotted(ixfx-tab-list-item) {\n flex: 0 0 auto;\n }\n\n #container.vertical-side ::slotted(ixfx-tab-list-item) {\n writing-mode: vertical-lr;\n text-orientation: mixed;\n }\n #container.vertical-side.flip ::slotted(ixfx-tab-list-item) {\n writing-mode: vertical-rl;\n text-orientation: mixed;\n }\n\n /* Drag affordances — opt-in via drag-mode=\"reorder\" on the host (list). */\n #container.reorder ::slotted(ixfx-tab-list-item) {\n cursor: grab;\n }\n :host([drag-mode]) ::slotted(ixfx-tab-list-item[drag-source]) {\n cursor: grabbing;\n opacity: 0.4;\n transition: opacity var(--transition, 0.15s ease-out);\n }\n\n /* Insertion caret shown during an active drag. */\n .drop-caret {\n position: absolute;\n background: var(--accent);\n border-radius: 1px;\n pointer-events: none;\n z-index: 1;\n }\n\n ::slotted(.activated) {\n --colour: var(--activated-colour);\n }\n `];\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ixfx-tab-list\": TabListElement;\n }\n}\n"],"mappings":"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"}
@@ -40,6 +40,7 @@ This directory contains documentation for each component and some architectural
40
40
  - [range-input](./range-input.md)
41
41
  - [rating](./rating.md)
42
42
  - [select-horiz](./select-horiz.md)
43
+ - [shapefill-value](./shapefill-value.md)
43
44
  - [slider-input](./slider-input.md)
44
45
  - [snackbar](./snackbar.md)
45
46
  - [snap-container](./snap-container.md)
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ixfx/components",
3
- "version": "0.10.4",
4
- "generated": "2026-09-15T17:26:48.640Z",
3
+ "version": "0.10.6",
4
+ "generated": "2026-09-16T09:39:40.476Z",
5
5
  "components": [
6
6
  {
7
7
  "name": "ac-text",
@@ -183,6 +183,11 @@
183
183
  "summary": "A horizontally scrolling, snap-to-center single-item picker. Children are ordinary elements you provide — the component doesn't render its own item markup, it just lays them out with scroll-snap, keep",
184
184
  "path": "docs-user/select-horiz.md"
185
185
  },
186
+ {
187
+ "name": "shapefill-value",
188
+ "summary": "A click-and-drag value input, sharing interaction semantics with `ixfx-radial-input`, but depicted as a shape outline (`rectangle` or `circle`) containing a concentric inner shape scaled to the curren",
189
+ "path": "docs-user/shapefill-value.md"
190
+ },
186
191
  {
187
192
  "name": "slider-input",
188
193
  "summary": "A linear SVG-based slider. Supports horizontal and vertical orientations, three value modes (single/dual/triple), three single-mode styles (range/bipolar/unipolar), triangle/needle/dot markers, and `v",
@@ -47,6 +47,7 @@ import '@ixfx/components/data-grid';
47
47
  - [range-input](docs-user/range-input.md): A label-free, fully styled range slider wrapping `<input type="range">`. Supports horizontal and vertical orientations, CSS variable theming, gradient fills, custom thumb shapes, and an optional value
48
48
  - [rating](docs-user/rating.md): A rating input that renders a row of glyphs (star / heart / square). Supports half-step increments, keyboard control, per-rank theming via CSS variables, and prominence-based hover feedback. Clicking
49
49
  - [select-horiz](docs-user/select-horiz.md): A horizontally scrolling, snap-to-center single-item picker. Children are ordinary elements you provide — the component doesn't render its own item markup, it just lays them out with scroll-snap, keep
50
+ - [shapefill-value](docs-user/shapefill-value.md): A click-and-drag value input, sharing interaction semantics with `ixfx-radial-input`, but depicted as a shape outline (`rectangle` or `circle`) containing a concentric inner shape scaled to the curren
50
51
  - [slider-input](docs-user/slider-input.md): A linear SVG-based slider. Supports horizontal and vertical orientations, three value modes (single/dual/triple), three single-mode styles (range/bipolar/unipolar), triangle/needle/dot markers, and `v
51
52
  - [snackbar](docs-user/snackbar.md): A small floating toolbar anchored in visual proximity to another element on the page (the "target"), built on the native Popover and CSS Anchor Positioning APIs.
52
53
  - [snap-container](docs-user/snap-container.md): A container that positions its child element at configurable snap positions along an axis. The child can be dragged, clicked to snap, or controlled via keyboard.
@@ -0,0 +1,125 @@
1
+ # ixfx-shapefill-value
2
+
3
+ A click-and-drag value input, sharing interaction semantics with `ixfx-radial-input`, but depicted as a shape outline (`rectangle` or `circle`) containing a concentric inner shape scaled to the current value. A value of 50% draws an inner shape at half the outline's size.
4
+
5
+ ---
6
+
7
+ ## Basic Usage
8
+
9
+ ```html
10
+ <ixfx-shapefill-value min="0" max="1" value="0.5"></ixfx-shapefill-value>
11
+ ```
12
+
13
+ The component has an intrinsic size of `2em x 2em`. Set width/height via CSS to resize:
14
+
15
+ ```html
16
+ <ixfx-shapefill-value display="rectangle" min="0" max="100" value="30" style="width: 60px; height: 60px;"></ixfx-shapefill-value>
17
+ ```
18
+
19
+ To fill a container:
20
+
21
+ ```html
22
+ <div style="width: 80px; height: 80px;">
23
+ <ixfx-shapefill-value min="0" max="1" value="0.5" style="width: 100%; height: 100%;"></ixfx-shapefill-value>
24
+ </div>
25
+ ```
26
+
27
+ ---
28
+
29
+ ## Attributes & Properties
30
+
31
+ | Attribute / Property | Type | Default | Description |
32
+ |---|---|---|---|
33
+ | `display` | `"circle"` \| `"rectangle"` | `"circle"` | Outline/fill shape |
34
+ | `fill-mode` | `"centre"` \| `"left"` \| `"right"` | `"centre"` | How the inner shape grows (see [Fill Modes](#fill-modes)) |
35
+ | `value` | `number` | `0` | Current value, within `min`…`max` |
36
+ | `min` | `number` | `0` | Minimum value |
37
+ | `max` | `number` | `100` | Maximum value |
38
+ | `step` | `number` | `undefined` | Keyboard step size. If unset, defaults to 10% of range |
39
+ | `stops` | `number` | `undefined` | Quantise to N discrete positions. Also sets keyboard step size |
40
+ | `disabled` | `boolean` | `false` | Disables all interaction. Reflected as attribute |
41
+ | `lighttouch` | `boolean` | `false` | Mouse wheel adjusts on hover, without requiring focus |
42
+
43
+ ### Sizing
44
+
45
+ Size via CSS `width`/`height`.
46
+
47
+ - `display="rectangle"` stretches to fill the host, so a landscape or portrait box produces a matching landscape or portrait rectangle:
48
+
49
+ ```html
50
+ <ixfx-shapefill-value display="rectangle" style="width: 160px; height: 60px;" min="0" max="1" value="0.6"></ixfx-shapefill-value>
51
+ ```
52
+
53
+ - `display="circle"` always keeps a 1:1 aspect ratio and letterboxes within a non-square host, so it stays a true circle rather than an ellipse.
54
+
55
+ ---
56
+
57
+ ## Fill Modes
58
+
59
+ - **`centre`** (default): the inner shape stays concentric with the outline, growing from the middle in all directions. A value of 50% is half the outline's size.
60
+ - **`left`** / **`right`**: the inner shape grows from the outline's left or right edge (mirror images of each other).
61
+ - For `rectangle`, this is a classic slider-bar fill: fixed height, width scales with value.
62
+ - For `circle`, the value circle's x-origin travels from the outline's left/right perimeter (value 0%) to the outline's centre (value 100%), while its radius grows the same way as `centre` — the combined effect is a circle that inflates outward from that edge.
63
+
64
+ ```html
65
+ <ixfx-shapefill-value display="rectangle" fill-mode="left" min="0" max="1" value="0.6"></ixfx-shapefill-value>
66
+ <ixfx-shapefill-value display="rectangle" fill-mode="right" min="0" max="1" value="0.6"></ixfx-shapefill-value>
67
+ <ixfx-shapefill-value display="circle" fill-mode="left" min="0" max="1" value="0.6"></ixfx-shapefill-value>
68
+ <ixfx-shapefill-value display="circle" fill-mode="right" min="0" max="1" value="0.6"></ixfx-shapefill-value>
69
+ ```
70
+
71
+ ---
72
+
73
+ ## Styling
74
+
75
+ CSS custom properties, set on the host:
76
+
77
+ | Variable | Default | Description |
78
+ |---|---|---|
79
+ | `--shapefill-value-outline-color` | `var(--surface-4)` | Outline stroke colour |
80
+ | `--shapefill-value-outline-width` | `2` | Outline stroke width (viewBox units) |
81
+ | `--shapefill-value-fill-color` | `var(--accent)` | Inner shape fill colour |
82
+ | `--shapefill-value-fill-transition` | `none` | Transition applied to the inner shape (e.g. `fill-opacity 0.15s ease`) |
83
+ | `--shapefill-value-focus-outline` | `2px solid var(--accent)` | Focus ring |
84
+ | `--shapefill-value-focus-outline-offset` | `2px` | Focus ring offset |
85
+
86
+ ```html
87
+ <ixfx-shapefill-value
88
+ style="
89
+ --shapefill-value-outline-color: hsl(280, 30%, 40%);
90
+ --shapefill-value-fill-color: hsl(280, 100%, 60%);
91
+ "
92
+ min="0" max="1" value="0.6"
93
+ ></ixfx-shapefill-value>
94
+ ```
95
+
96
+ ---
97
+
98
+ ## Events
99
+
100
+ | Event | Detail | Fired |
101
+ |---|---|---|
102
+ | `input` | `{ value, valueRelative }` | Continuously during user interaction (drag, keyboard, wheel) |
103
+ | `change` | `{ value, valueRelative, previousValue }` | Debounced ~150ms after user interaction settles, or immediately for programmatic value changes |
104
+
105
+ ```typescript
106
+ el.addEventListener('input', (event) => {
107
+ const { value, valueRelative } = event.detail;
108
+ });
109
+ ```
110
+
111
+ ---
112
+
113
+ ## Interaction
114
+
115
+ - **Mouse/touch:** press and drag vertically — up increases, down decreases. Full min-to-max range covers 2x the element's height. Hold Shift while dragging for finer control.
116
+ - **Keyboard:** the element must have focus. Arrow Up/Right increases, Arrow Down/Left decreases, by `step` (or 10% of range if unset). Home/End jump to `min`/`max`. Hold Shift for a finer step.
117
+ - **Mouse wheel:** requires focus by default; set `lighttouch` to react on hover instead.
118
+
119
+ ---
120
+
121
+ ## Disabled state
122
+
123
+ ```html
124
+ <ixfx-shapefill-value disabled min="0" max="1" value="0.7"></ixfx-shapefill-value>
125
+ ```
package/llms.txt CHANGED
@@ -47,6 +47,7 @@ import '@ixfx/components/data-grid';
47
47
  - [range-input](docs-user/range-input.md): A label-free, fully styled range slider wrapping `<input type="range">`. Supports horizontal and vertical orientations, CSS variable theming, gradient fills, custom thumb shapes, and an optional value
48
48
  - [rating](docs-user/rating.md): A rating input that renders a row of glyphs (star / heart / square). Supports half-step increments, keyboard control, per-rank theming via CSS variables, and prominence-based hover feedback. Clicking
49
49
  - [select-horiz](docs-user/select-horiz.md): A horizontally scrolling, snap-to-center single-item picker. Children are ordinary elements you provide — the component doesn't render its own item markup, it just lays them out with scroll-snap, keep
50
+ - [shapefill-value](docs-user/shapefill-value.md): A click-and-drag value input, sharing interaction semantics with `ixfx-radial-input`, but depicted as a shape outline (`rectangle` or `circle`) containing a concentric inner shape scaled to the curren
50
51
  - [slider-input](docs-user/slider-input.md): A linear SVG-based slider. Supports horizontal and vertical orientations, three value modes (single/dual/triple), three single-mode styles (range/bipolar/unipolar), triangle/needle/dot markers, and `v
51
52
  - [snackbar](docs-user/snackbar.md): A small floating toolbar anchored in visual proximity to another element on the page (the "target"), built on the native Popover and CSS Anchor Positioning APIs.
52
53
  - [snap-container](docs-user/snap-container.md): A container that positions its child element at configurable snap positions along an axis. The child can be dragged, clicked to snap, or controlled via keyboard.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ixfx/components",
3
3
  "type": "module",
4
- "version": "0.10.4",
4
+ "version": "0.10.6",
5
5
  "packageManager": "pnpm@10.0.0",
6
6
  "description": "Web components for ixfx — Lit-based UI toolkit",
7
7
  "author": "Clint Heyer",
@@ -1 +0,0 @@
1
- {"version":3,"file":"tab-list-CGyPt07Z.d.ts","names":[],"sources":["../src/tabs/tab-list-item.ts","../src/tabs/tab-list.ts"],"mappings":";;;;;;;;;;;cAiBa,2BAA2B;EAEtC;EAGA;EAGA;EAGA;EAGA;EAGA,UAAU;UAEO;UACA;UACT;UACA;UACA;EAEC;EAgBA;EAcA,QAAQ,SAAS;UAQlB;UAUA;UAWA;UAIA;;;;;UAQA;UAiBA;EAKC;EAKA,wBAAM;UA8BP;SAWQ,sBAAM;;QAmKhB;YACI;IACR,sBAAsB;;;;;;;;;;KC3Ud;WACD;WACA;WACA;;KAGC,qBAAqB,YAAY;KAEjC;WACD;WACA;WACA;;KAGC,sBAAsB,YAAY;KAElC;cAMC,uBAAuB;;EAElC;EAGA;EAGA;EAGA;EAGA;EAGA,UAAU;EA4BD,wBAAM;EAsDN;EAUA,QAAQ,mBAAmB;EAoGpC,sBAAkB;EAwBlB,cAAc,IAAI;SAwaX,sBAAM;;QAyKP;YACI;IACR,iBAAiB"}