@adia-ai/web-components 0.4.5 → 0.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -24
- package/USAGE.md +604 -0
- package/components/accordion/accordion.d.ts +17 -0
- package/components/accordion/accordion.js +10 -117
- package/components/accordion/class.js +132 -0
- package/components/action-list/action-list.d.ts +15 -0
- package/components/action-list/action-list.js +9 -140
- package/components/action-list/class.js +156 -0
- package/components/agent-artifact/agent-artifact.d.ts +25 -0
- package/components/agent-artifact/agent-artifact.js +8 -181
- package/components/agent-artifact/class.js +200 -0
- package/components/agent-feedback-bar/agent-feedback-bar.d.ts +21 -0
- package/components/agent-feedback-bar/agent-feedback-bar.js +8 -143
- package/components/agent-feedback-bar/class.js +162 -0
- package/components/agent-questions/agent-questions.d.ts +23 -0
- package/components/agent-questions/agent-questions.js +8 -180
- package/components/agent-questions/class.js +199 -0
- package/components/agent-reasoning/agent-reasoning.d.ts +23 -0
- package/components/agent-reasoning/agent-reasoning.js +8 -494
- package/components/agent-reasoning/class.js +513 -0
- package/components/agent-suggestions/agent-suggestions.d.ts +21 -0
- package/components/agent-suggestions/agent-suggestions.js +8 -78
- package/components/agent-suggestions/class.js +97 -0
- package/components/agent-trace/agent-trace.d.ts +19 -0
- package/components/alert/alert.d.ts +29 -0
- package/components/alert/alert.js +8 -175
- package/components/alert/class.js +194 -0
- package/components/avatar/avatar.d.ts +27 -0
- package/components/avatar/avatar.js +9 -159
- package/components/avatar/class.js +173 -0
- package/components/badge/badge.d.ts +27 -0
- package/components/badge/badge.js +9 -75
- package/components/badge/class.js +93 -0
- package/components/block/block.d.ts +19 -0
- package/components/block/block.js +9 -15
- package/components/block/class.js +33 -0
- package/components/breadcrumb/breadcrumb.d.ts +23 -0
- package/components/breadcrumb/breadcrumb.js +8 -113
- package/components/breadcrumb/class.js +132 -0
- package/components/button/button.d.ts +34 -0
- package/components/button/button.js +15 -66
- package/components/button/class.js +80 -0
- package/components/calendar-picker/calendar-picker.a2ui.json +6 -1
- package/components/calendar-picker/calendar-picker.d.ts +27 -0
- package/components/calendar-picker/calendar-picker.js +8 -332
- package/components/calendar-picker/calendar-picker.yaml +51 -177
- package/components/calendar-picker/class.js +351 -0
- package/components/canvas/canvas.a2ui.json +6 -1
- package/components/canvas/canvas.d.ts +17 -0
- package/components/canvas/canvas.yaml +19 -36
- package/components/card/card.a2ui.json +3 -0
- package/components/card/card.d.ts +27 -0
- package/components/card/card.js +9 -50
- package/components/card/card.yaml +171 -433
- package/components/card/class.js +68 -0
- package/components/chart/chart.d.ts +41 -0
- package/components/chart/chart.js +8 -2131
- package/components/chart/class.js +2150 -0
- package/components/chart-legend/chart-legend.d.ts +27 -0
- package/components/chart-legend/chart-legend.js +8 -197
- package/components/chart-legend/class.js +215 -0
- package/components/chat-thread/chat-thread.d.ts +17 -0
- package/components/chat-thread/chat-thread.js +8 -157
- package/components/chat-thread/class.js +176 -0
- package/components/check/check.d.ts +30 -0
- package/components/check/check.js +11 -52
- package/components/check/class.js +68 -0
- package/components/code/class.js +501 -0
- package/components/code/code.d.ts +39 -0
- package/components/code/code.js +8 -482
- package/components/col/class.js +30 -0
- package/components/col/col.d.ts +23 -0
- package/components/col/col.js +10 -13
- package/components/color-picker/class.js +550 -0
- package/components/color-picker/color-picker.d.ts +37 -0
- package/components/color-picker/color-picker.js +8 -531
- package/components/command/class.js +364 -0
- package/components/command/command.a2ui.json +3 -0
- package/components/command/command.d.ts +19 -0
- package/components/command/command.js +8 -345
- package/components/command/command.yaml +105 -124
- package/components/demo-toggle/class.js +153 -0
- package/components/demo-toggle/demo-toggle.d.ts +23 -0
- package/components/demo-toggle/demo-toggle.js +8 -135
- package/components/description-list/class.js +86 -0
- package/components/description-list/description-list.d.ts +21 -0
- package/components/description-list/description-list.js +8 -67
- package/components/divider/class.js +57 -0
- package/components/divider/divider.d.ts +19 -0
- package/components/divider/divider.js +10 -40
- package/components/drawer/class.js +306 -0
- package/components/drawer/drawer.d.ts +25 -0
- package/components/drawer/drawer.js +8 -287
- package/components/embed/class.js +73 -0
- package/components/embed/embed.d.ts +23 -0
- package/components/embed/embed.js +9 -55
- package/components/empty-state/class.js +108 -0
- package/components/empty-state/empty-state.d.ts +21 -0
- package/components/empty-state/empty-state.js +9 -90
- package/components/feed/class.js +381 -0
- package/components/feed/feed.d.ts +19 -0
- package/components/feed/feed.js +9 -367
- package/components/field/class.js +266 -0
- package/components/field/field.d.ts +23 -0
- package/components/field/field.js +8 -247
- package/components/fields/class.js +106 -0
- package/components/fields/fields.d.ts +19 -0
- package/components/fields/fields.js +8 -87
- package/components/grid/class.js +31 -0
- package/components/grid/grid.d.ts +23 -0
- package/components/grid/grid.js +10 -14
- package/components/heatmap/class.js +305 -0
- package/components/heatmap/heatmap.d.ts +31 -0
- package/components/heatmap/heatmap.js +8 -286
- package/components/icon/class.js +54 -0
- package/components/icon/icon.d.ts +23 -0
- package/components/icon/icon.js +13 -40
- package/components/image/class.js +112 -0
- package/components/image/image.d.ts +33 -0
- package/components/image/image.js +9 -94
- package/components/index.js +1 -0
- package/components/input/class.js +773 -0
- package/components/input/input.a2ui.json +3 -0
- package/components/input/input.d.ts +61 -0
- package/components/input/input.js +8 -755
- package/components/input/input.yaml +171 -442
- package/components/inspector/class.js +142 -0
- package/components/inspector/inspector.a2ui.json +8 -1
- package/components/inspector/inspector.d.ts +17 -0
- package/components/inspector/inspector.js +8 -124
- package/components/inspector/inspector.yaml +15 -30
- package/components/kbd/class.js +34 -0
- package/components/kbd/kbd.a2ui.json +3 -0
- package/components/kbd/kbd.d.ts +17 -0
- package/components/kbd/kbd.js +10 -17
- package/components/kbd/kbd.yaml +54 -185
- package/components/link/class.js +187 -0
- package/components/link/link.d.ts +55 -0
- package/components/link/link.js +8 -168
- package/components/list/class.js +249 -0
- package/components/list/list.d.ts +23 -0
- package/components/list/list.js +9 -231
- package/components/menu/class.js +332 -0
- package/components/menu/menu.d.ts +21 -0
- package/components/menu/menu.js +11 -316
- package/components/modal/class.js +231 -0
- package/components/modal/modal.a2ui.json +5 -1
- package/components/modal/modal.d.ts +23 -0
- package/components/modal/modal.js +8 -212
- package/components/modal/modal.yaml +19 -39
- package/components/nav/class.js +150 -0
- package/components/nav/nav.d.ts +31 -0
- package/components/nav/nav.js +8 -131
- package/components/nav-group/class.js +152 -0
- package/components/nav-group/nav-group.d.ts +35 -0
- package/components/nav-group/nav-group.js +9 -134
- package/components/nav-item/class.js +86 -0
- package/components/nav-item/nav-item.d.ts +37 -0
- package/components/nav-item/nav-item.js +10 -69
- package/components/noodles/class.js +510 -0
- package/components/noodles/noodles.d.ts +33 -0
- package/components/noodles/noodles.js +9 -493
- package/components/option-card/class.js +167 -0
- package/components/option-card/option-card.d.ts +30 -0
- package/components/option-card/option-card.js +8 -149
- package/components/otp-input/class.js +180 -0
- package/components/otp-input/otp-input.a2ui.json +5 -1
- package/components/otp-input/otp-input.d.ts +25 -0
- package/components/otp-input/otp-input.js +9 -162
- package/components/otp-input/otp-input.yaml +45 -174
- package/components/page/class.js +97 -0
- package/components/page/page.d.ts +46 -0
- package/components/page/page.js +8 -79
- package/components/pagination/class.js +195 -0
- package/components/pagination/pagination.d.ts +23 -0
- package/components/pagination/pagination.js +9 -177
- package/components/pane/class.js +186 -0
- package/components/pane/pane.a2ui.json +12 -1
- package/components/pane/pane.css +10 -0
- package/components/pane/pane.d.ts +31 -0
- package/components/pane/pane.js +8 -143
- package/components/pane/pane.yaml +57 -157
- package/components/pipeline-status/class.js +189 -0
- package/components/pipeline-status/pipeline-status.a2ui.json +7 -1
- package/components/pipeline-status/pipeline-status.d.ts +21 -0
- package/components/pipeline-status/pipeline-status.js +9 -172
- package/components/pipeline-status/pipeline-status.yaml +34 -72
- package/components/popover/class.js +194 -0
- package/components/popover/popover.d.ts +23 -0
- package/components/popover/popover.js +9 -176
- package/components/progress/class.js +74 -0
- package/components/progress/progress.a2ui.json +3 -0
- package/components/progress/progress.d.ts +19 -0
- package/components/progress/progress.js +10 -57
- package/components/progress/progress.yaml +124 -287
- package/components/progress-row/class.js +110 -0
- package/components/progress-row/progress-row.d.ts +23 -0
- package/components/progress-row/progress-row.js +8 -92
- package/components/radio/class.js +83 -0
- package/components/radio/radio.d.ts +28 -0
- package/components/radio/radio.js +11 -67
- package/components/range/class.js +194 -0
- package/components/range/range.d.ts +31 -0
- package/components/range/range.js +9 -176
- package/components/rating/class.js +148 -0
- package/components/rating/rating.d.ts +33 -0
- package/components/rating/rating.js +9 -130
- package/components/richtext/class.js +87 -0
- package/components/richtext/richtext.a2ui.json +7 -1
- package/components/richtext/richtext.d.ts +19 -0
- package/components/richtext/richtext.js +8 -68
- package/components/richtext/richtext.yaml +30 -65
- package/components/row/class.js +50 -0
- package/components/row/row.d.ts +27 -0
- package/components/row/row.js +10 -33
- package/components/search/class.js +134 -0
- package/components/search/search.d.ts +35 -0
- package/components/search/search.js +10 -117
- package/components/segment/class.js +62 -0
- package/components/segment/segment.d.ts +25 -0
- package/components/segment/segment.js +10 -45
- package/components/segmented/class.js +165 -0
- package/components/segmented/segmented.a2ui.json +4 -0
- package/components/segmented/segmented.d.ts +24 -0
- package/components/segmented/segmented.js +10 -148
- package/components/segmented/segmented.yaml +41 -59
- package/components/select/class.js +408 -0
- package/components/select/select.d.ts +57 -0
- package/components/select/select.js +15 -396
- package/components/skeleton/class.js +52 -0
- package/components/skeleton/skeleton.d.ts +23 -0
- package/components/skeleton/skeleton.js +8 -34
- package/components/slider/class.js +184 -0
- package/components/slider/slider.d.ts +31 -0
- package/components/slider/slider.js +9 -166
- package/components/stack/class.js +28 -0
- package/components/stack/stack.d.ts +17 -0
- package/components/stack/stack.js +10 -11
- package/components/step-progress/class.js +98 -0
- package/components/step-progress/step-progress.d.ts +27 -0
- package/components/step-progress/step-progress.js +8 -79
- package/components/stepper/class.js +126 -0
- package/components/stepper/stepper.d.ts +19 -0
- package/components/stepper/stepper.js +9 -112
- package/components/stream/class.js +109 -0
- package/components/stream/stream.d.ts +19 -0
- package/components/stream/stream.js +8 -90
- package/components/swatch/class.js +131 -0
- package/components/swatch/swatch.d.ts +28 -0
- package/components/swatch/swatch.js +8 -112
- package/components/swiper/class.js +373 -0
- package/components/swiper/swiper.a2ui.json +4 -0
- package/components/swiper/swiper.d.ts +31 -0
- package/components/swiper/swiper.js +8 -354
- package/components/swiper/swiper.yaml +68 -212
- package/components/switch/class.js +63 -0
- package/components/switch/switch.a2ui.json +6 -1
- package/components/switch/switch.d.ts +30 -0
- package/components/switch/switch.js +11 -47
- package/components/switch/switch.yaml +70 -265
- package/components/table/class.js +1453 -0
- package/components/table/table.d.ts +37 -0
- package/components/table/table.js +8 -1435
- package/components/table-toolbar/class.js +680 -0
- package/components/table-toolbar/table-toolbar.d.ts +33 -0
- package/components/table-toolbar/table-toolbar.js +8 -689
- package/components/tabs/class.js +242 -0
- package/components/tabs/tabs.d.ts +21 -0
- package/components/tabs/tabs.js +8 -223
- package/components/tag/class.js +99 -0
- package/components/tag/tag.d.ts +27 -0
- package/components/tag/tag.js +8 -80
- package/components/text/class.js +46 -0
- package/components/text/text.d.ts +25 -0
- package/components/text/text.js +9 -28
- package/components/textarea/class.js +134 -0
- package/components/textarea/textarea.d.ts +31 -0
- package/components/textarea/textarea.js +11 -118
- package/components/timeline/class.js +176 -0
- package/components/timeline/timeline.d.ts +19 -0
- package/components/timeline/timeline.js +9 -162
- package/components/toast/class.js +92 -0
- package/components/toast/toast.d.ts +23 -0
- package/components/toast/toast.js +9 -76
- package/components/toggle-group/class.js +154 -0
- package/components/toggle-group/toggle-group.d.ts +19 -0
- package/components/toggle-group/toggle-group.js +11 -140
- package/components/toggle-scheme/class.js +286 -0
- package/components/toggle-scheme/toggle-scheme.a2ui.json +197 -0
- package/components/toggle-scheme/toggle-scheme.css +20 -0
- package/components/toggle-scheme/toggle-scheme.d.ts +41 -0
- package/components/toggle-scheme/toggle-scheme.js +17 -0
- package/components/toggle-scheme/toggle-scheme.yaml +173 -0
- package/components/toolbar/class.js +388 -0
- package/components/toolbar/toolbar.d.ts +23 -0
- package/components/toolbar/toolbar.js +10 -376
- package/components/tooltip/class.js +299 -0
- package/components/tooltip/tooltip.d.ts +27 -0
- package/components/tooltip/tooltip.js +8 -280
- package/components/tree/class.js +245 -0
- package/components/tree/tree.d.ts +15 -0
- package/components/tree/tree.js +9 -244
- package/components/upload/class.js +199 -0
- package/components/upload/upload.d.ts +27 -0
- package/components/upload/upload.js +11 -183
- package/core/element.d.ts +174 -0
- package/core/form.d.ts +108 -0
- package/core/index.d.ts +11 -0
- package/core/index.js +1 -0
- package/core/register.d.ts +25 -0
- package/core/register.js +58 -0
- package/core/signals.d.ts +94 -0
- package/core/template.d.ts +70 -0
- package/index.d.ts +315 -0
- package/package.json +25 -6
- package/traits/CATEGORIES.md +1 -1
|
@@ -1,698 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* `<table-toolbar-ui>` — auto-registers the tag on import.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* • filter / sort / columns popover buttons
|
|
7
|
-
* • search input
|
|
8
|
-
* • optional [slot="actions"] trailing region
|
|
4
|
+
* For non-side-effect class import (test isolation, tag override), use
|
|
5
|
+
* the `class` subpath:
|
|
9
6
|
*
|
|
10
|
-
*
|
|
11
|
-
* id, then dispatches state changes against it. When [for] is absent, falls
|
|
12
|
-
* back to the first table-ui sibling under the same parent.
|
|
7
|
+
* import { UITableToolbar } from '@adia-ai/web-components/components/table-toolbar/class';
|
|
13
8
|
*
|
|
14
|
-
*
|
|
15
|
-
* that menu-ui / popover-ui / toolbar-ui already use in this package.
|
|
16
|
-
*
|
|
17
|
-
* State flow:
|
|
18
|
-
* search → table.search (string property)
|
|
19
|
-
* filters → table.setFilter() (per-key)
|
|
20
|
-
* sort → simulated click on table's [data-sort-key] header
|
|
21
|
-
* column hidden → table.columns = (clone with hidden flag flipped)
|
|
9
|
+
* @see ../../USAGE.md#registration--auto-vs-explicit
|
|
22
10
|
*/
|
|
23
11
|
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
|
|
27
|
-
const SEARCH_DEBOUNCE = 200;
|
|
28
|
-
|
|
29
|
-
class UITableToolbar extends UIElement {
|
|
30
|
-
static properties = {
|
|
31
|
-
for: { type: String, default: '', reflect: true },
|
|
32
|
-
text: { type: String, default: '', reflect: false },
|
|
33
|
-
count: { type: String, default: '', reflect: false },
|
|
34
|
-
noFilter: { type: Boolean, default: false, reflect: true, attribute: 'no-filter' },
|
|
35
|
-
noSort: { type: Boolean, default: false, reflect: true, attribute: 'no-sort' },
|
|
36
|
-
noColumns: { type: Boolean, default: false, reflect: true, attribute: 'no-columns' },
|
|
37
|
-
noSearch: { type: Boolean, default: false, reflect: true, attribute: 'no-search' },
|
|
38
|
-
placeholder: { type: String, default: 'Search...', reflect: false },
|
|
39
|
-
variant: { type: String, default: 'default', reflect: true },
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
static template = () => null;
|
|
43
|
-
|
|
44
|
-
#target = null;
|
|
45
|
-
#targetListeners = [];
|
|
46
|
-
#activePopover = null; // { btn, panel, cleanup }
|
|
47
|
-
#docListenersBound = false;
|
|
48
|
-
#docListenerRaf = null;
|
|
49
|
-
#sortIndicatorRafs = new Set();
|
|
50
|
-
|
|
51
|
-
// ── Lifecycle ────────────────────────────────────────────────────────────
|
|
52
|
-
|
|
53
|
-
connected() {
|
|
54
|
-
this.setAttribute('role', 'toolbar');
|
|
55
|
-
this.#stamp();
|
|
56
|
-
this.#resolveTarget();
|
|
57
|
-
this.#syncFromTarget();
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
disconnected() {
|
|
61
|
-
if (this.#docListenerRaf != null) {
|
|
62
|
-
cancelAnimationFrame(this.#docListenerRaf);
|
|
63
|
-
this.#docListenerRaf = null;
|
|
64
|
-
}
|
|
65
|
-
for (const id of this.#sortIndicatorRafs) cancelAnimationFrame(id);
|
|
66
|
-
this.#sortIndicatorRafs.clear();
|
|
67
|
-
this.#closePopover();
|
|
68
|
-
this.#detachTarget();
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
render() {
|
|
72
|
-
// [for] / count / text changes come through here.
|
|
73
|
-
this.#resolveTarget();
|
|
74
|
-
this.#syncFromTarget();
|
|
75
|
-
this.#updateTitle();
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// ── Target resolution ────────────────────────────────────────────────────
|
|
79
|
-
|
|
80
|
-
#resolveTarget() {
|
|
81
|
-
const next = this.#findTarget();
|
|
82
|
-
if (next === this.#target) return;
|
|
83
|
-
this.#detachTarget();
|
|
84
|
-
if (!next) return;
|
|
85
|
-
this.#target = next;
|
|
86
|
-
|
|
87
|
-
const onSort = () => this.#refreshSortPanel();
|
|
88
|
-
const onFilter = () => this.#refreshFilterPanel();
|
|
89
|
-
next.addEventListener('sort', onSort);
|
|
90
|
-
next.addEventListener('filter-change', onFilter);
|
|
91
|
-
this.#targetListeners.push(['sort', onSort], ['filter-change', onFilter]);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
#findTarget() {
|
|
95
|
-
if (this.for) {
|
|
96
|
-
const root = this.getRootNode?.();
|
|
97
|
-
const byId = root?.getElementById?.(this.for) || document.getElementById(this.for);
|
|
98
|
-
if (byId && byId.tagName?.toLowerCase() === 'table-ui') return byId;
|
|
99
|
-
return null;
|
|
100
|
-
}
|
|
101
|
-
// Fallback — first sibling table-ui in the same parent.
|
|
102
|
-
const parent = this.parentElement;
|
|
103
|
-
if (!parent) return null;
|
|
104
|
-
return parent.querySelector(':scope table-ui') || null;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
#detachTarget() {
|
|
108
|
-
if (this.#target) {
|
|
109
|
-
for (const [evt, fn] of this.#targetListeners) {
|
|
110
|
-
this.#target.removeEventListener(evt, fn);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
this.#targetListeners = [];
|
|
114
|
-
this.#target = null;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// ── DOM stamp ────────────────────────────────────────────────────────────
|
|
118
|
-
|
|
119
|
-
#stamp() {
|
|
120
|
-
if (this.querySelector(':scope > [data-toolbar]')) return;
|
|
121
|
-
|
|
122
|
-
const root = document.createElement('div');
|
|
123
|
-
root.setAttribute('data-toolbar', '');
|
|
124
|
-
|
|
125
|
-
// Title cluster
|
|
126
|
-
const title = document.createElement('div');
|
|
127
|
-
title.setAttribute('data-title', '');
|
|
128
|
-
|
|
129
|
-
const heading = document.createElement('span');
|
|
130
|
-
heading.setAttribute('data-heading', '');
|
|
131
|
-
title.appendChild(heading);
|
|
132
|
-
|
|
133
|
-
const badge = document.createElement('badge-ui');
|
|
134
|
-
badge.setAttribute('data-count-badge', '');
|
|
135
|
-
badge.setAttribute('size', 'sm');
|
|
136
|
-
badge.setAttribute('variant', 'muted');
|
|
137
|
-
badge.hidden = true;
|
|
138
|
-
title.appendChild(badge);
|
|
139
|
-
|
|
140
|
-
// Controls cluster
|
|
141
|
-
const controls = document.createElement('div');
|
|
142
|
-
controls.setAttribute('data-controls', '');
|
|
143
|
-
controls.appendChild(this.#mkButton('filter', 'Filter', 'funnel-simple'));
|
|
144
|
-
controls.appendChild(this.#mkButton('sort', 'Sort', 'arrows-down-up'));
|
|
145
|
-
controls.appendChild(this.#mkButton('columns', 'Columns', 'columns'));
|
|
146
|
-
|
|
147
|
-
// Search — compose <search-ui>, which already stamps input-ui with
|
|
148
|
-
// the magnifying-glass prefix + clear suffix and debounces a `search`
|
|
149
|
-
// event. Rolling our own from input-ui would re-derive that wiring
|
|
150
|
-
// and (as discovered) hit a first-paint timing race where the icon
|
|
151
|
-
// name renders as literal text before the icon registry resolves.
|
|
152
|
-
const search = document.createElement('search-ui');
|
|
153
|
-
search.setAttribute('data-search', '');
|
|
154
|
-
search.setAttribute('placeholder', this.placeholder);
|
|
155
|
-
search.setAttribute('debounce', String(SEARCH_DEBOUNCE));
|
|
156
|
-
search.addEventListener('search', this.#onSearch);
|
|
157
|
-
|
|
158
|
-
// Actions slot passthrough — we move any pre-existing [slot="actions"] children here
|
|
159
|
-
const actionsSlot = document.createElement('div');
|
|
160
|
-
actionsSlot.setAttribute('data-actions', '');
|
|
161
|
-
for (const node of [...this.children]) {
|
|
162
|
-
if (node === root) continue;
|
|
163
|
-
if (node.getAttribute?.('slot') === 'actions') {
|
|
164
|
-
actionsSlot.appendChild(node);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
root.appendChild(title);
|
|
169
|
-
root.appendChild(controls);
|
|
170
|
-
root.appendChild(search);
|
|
171
|
-
root.appendChild(actionsSlot);
|
|
172
|
-
|
|
173
|
-
this.appendChild(root);
|
|
174
|
-
this.#updateTitle();
|
|
175
|
-
this.#updateControlVisibility();
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
#mkPopoverAction(label, onClick) {
|
|
179
|
-
const btn = document.createElement('button-ui');
|
|
180
|
-
btn.setAttribute('data-popover-action', '');
|
|
181
|
-
btn.setAttribute('text', label);
|
|
182
|
-
btn.setAttribute('variant', 'ghost');
|
|
183
|
-
btn.setAttribute('size', 'sm');
|
|
184
|
-
btn.setAttribute('stretch', '');
|
|
185
|
-
btn.addEventListener('click', onClick);
|
|
186
|
-
return btn;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
#mkButton(kind, label, icon) {
|
|
190
|
-
const btn = document.createElement('button-ui');
|
|
191
|
-
btn.setAttribute('data-toolbar-btn', kind);
|
|
192
|
-
btn.setAttribute('icon', icon);
|
|
193
|
-
btn.setAttribute('text', label);
|
|
194
|
-
btn.setAttribute('variant', 'outline');
|
|
195
|
-
btn.setAttribute('size', 'sm');
|
|
196
|
-
btn.setAttribute('aria-haspopup', 'menu');
|
|
197
|
-
btn.addEventListener('click', (e) => {
|
|
198
|
-
e.stopPropagation();
|
|
199
|
-
this.#togglePopover(kind, btn);
|
|
200
|
-
});
|
|
201
|
-
return btn;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
#updateTitle() {
|
|
205
|
-
const heading = this.querySelector(':scope [data-heading]');
|
|
206
|
-
if (!heading) return;
|
|
207
|
-
if (this.text) {
|
|
208
|
-
heading.textContent = this.text;
|
|
209
|
-
heading.hidden = false;
|
|
210
|
-
} else if (heading.textContent.trim()) {
|
|
211
|
-
heading.hidden = false;
|
|
212
|
-
} else {
|
|
213
|
-
heading.hidden = true;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const badge = this.querySelector(':scope [data-count-badge]');
|
|
217
|
-
if (!badge) return;
|
|
218
|
-
const explicit = this.count?.toString().trim();
|
|
219
|
-
const fallback = this.#target?.data?.length;
|
|
220
|
-
const value = explicit || (Number.isFinite(fallback) ? String(fallback) : '');
|
|
221
|
-
if (value) {
|
|
222
|
-
badge.setAttribute('text', value);
|
|
223
|
-
badge.hidden = false;
|
|
224
|
-
} else {
|
|
225
|
-
badge.hidden = true;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
#updateControlVisibility() {
|
|
230
|
-
const root = this.querySelector(':scope > [data-toolbar]');
|
|
231
|
-
if (!root) return;
|
|
232
|
-
|
|
233
|
-
const setHidden = (sel, hidden) => {
|
|
234
|
-
const el = root.querySelector(sel);
|
|
235
|
-
if (el) el.hidden = hidden;
|
|
236
|
-
};
|
|
237
|
-
|
|
238
|
-
setHidden('[data-toolbar-btn="filter"]', this.noFilter);
|
|
239
|
-
setHidden('[data-toolbar-btn="sort"]', this.noSort);
|
|
240
|
-
setHidden('[data-toolbar-btn="columns"]', this.noColumns);
|
|
241
|
-
setHidden('[data-search]', this.noSearch);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// Re-run on attribute changes for boolean flags.
|
|
245
|
-
attributeChanged(name) {
|
|
246
|
-
if (['no-filter', 'no-sort', 'no-columns', 'no-search'].includes(name)) {
|
|
247
|
-
this.#updateControlVisibility();
|
|
248
|
-
}
|
|
249
|
-
if (name === 'placeholder') {
|
|
250
|
-
const search = this.querySelector(':scope [data-search]');
|
|
251
|
-
search?.setAttribute('placeholder', this.placeholder);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// ── Search ───────────────────────────────────────────────────────────────
|
|
256
|
-
|
|
257
|
-
#onSearch = (e) => {
|
|
258
|
-
const value = e.detail?.value ?? '';
|
|
259
|
-
if (this.#target) this.#target.search = value;
|
|
260
|
-
this.dispatchEvent(new CustomEvent('search', {
|
|
261
|
-
bubbles: true,
|
|
262
|
-
detail: { value },
|
|
263
|
-
}));
|
|
264
|
-
};
|
|
265
|
-
|
|
266
|
-
// ── Sync from target (initial paint) ─────────────────────────────────────
|
|
267
|
-
|
|
268
|
-
#syncFromTarget() {
|
|
269
|
-
if (!this.#target) return;
|
|
270
|
-
const search = this.querySelector(':scope [data-search]');
|
|
271
|
-
if (search && this.#target.search) {
|
|
272
|
-
search.value = this.#target.search;
|
|
273
|
-
}
|
|
274
|
-
this.#updateTitle();
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// ── Popovers ─────────────────────────────────────────────────────────────
|
|
278
|
-
|
|
279
|
-
#togglePopover(kind, btn) {
|
|
280
|
-
if (this.#activePopover?.kind === kind) {
|
|
281
|
-
this.#closePopover();
|
|
282
|
-
return;
|
|
283
|
-
}
|
|
284
|
-
this.#closePopover();
|
|
285
|
-
|
|
286
|
-
const panel = document.createElement('div');
|
|
287
|
-
panel.setAttribute('data-toolbar-popover', kind);
|
|
288
|
-
panel.setAttribute('popover', 'manual');
|
|
289
|
-
panel.setAttribute('role', 'menu');
|
|
290
|
-
|
|
291
|
-
if (kind === 'filter') this.#fillFilterPanel(panel);
|
|
292
|
-
if (kind === 'sort') this.#fillSortPanel(panel);
|
|
293
|
-
if (kind === 'columns') this.#fillColumnsPanel(panel);
|
|
294
|
-
|
|
295
|
-
document.body.appendChild(panel);
|
|
296
|
-
|
|
297
|
-
try { panel.showPopover(); } catch { /* popover API unavailable */ }
|
|
298
|
-
const cleanup = anchorPopover(btn, panel, { placement: 'bottom-start', gap: 4 });
|
|
299
|
-
|
|
300
|
-
this.#activePopover = { kind, btn, panel, cleanup };
|
|
301
|
-
|
|
302
|
-
if (!this.#docListenersBound) {
|
|
303
|
-
this.#docListenersBound = true;
|
|
304
|
-
this.#docListenerRaf = requestAnimationFrame(() => {
|
|
305
|
-
this.#docListenerRaf = null;
|
|
306
|
-
if (!this.isConnected || !this.#docListenersBound) return;
|
|
307
|
-
document.addEventListener('pointerdown', this.#onDocDown, true);
|
|
308
|
-
document.addEventListener('keydown', this.#onDocKey, true);
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
#closePopover() {
|
|
314
|
-
const ap = this.#activePopover;
|
|
315
|
-
if (!ap) return;
|
|
316
|
-
ap.cleanup?.();
|
|
317
|
-
if (ap.panel?.matches?.(':popover-open')) {
|
|
318
|
-
try { ap.panel.hidePopover(); } catch { /* noop */ }
|
|
319
|
-
}
|
|
320
|
-
ap.panel?.remove();
|
|
321
|
-
this.#activePopover = null;
|
|
322
|
-
if (this.#docListenersBound) {
|
|
323
|
-
this.#docListenersBound = false;
|
|
324
|
-
document.removeEventListener('pointerdown', this.#onDocDown, true);
|
|
325
|
-
document.removeEventListener('keydown', this.#onDocKey, true);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
#onDocDown = (e) => {
|
|
330
|
-
const ap = this.#activePopover;
|
|
331
|
-
if (!ap) return;
|
|
332
|
-
if (ap.btn.contains(e.target)) return;
|
|
333
|
-
if (ap.panel.contains(e.target)) return;
|
|
334
|
-
this.#closePopover();
|
|
335
|
-
};
|
|
336
|
-
|
|
337
|
-
#onDocKey = (e) => {
|
|
338
|
-
if (e.key !== 'Escape') return;
|
|
339
|
-
e.stopPropagation();
|
|
340
|
-
this.#closePopover();
|
|
341
|
-
this.#activePopover?.btn?.focus?.({ preventScroll: true });
|
|
342
|
-
};
|
|
343
|
-
|
|
344
|
-
// ── Filter panel ─────────────────────────────────────────────────────────
|
|
345
|
-
|
|
346
|
-
#fillFilterPanel(panel) {
|
|
347
|
-
const target = this.#target;
|
|
348
|
-
if (!target?.columns?.length) {
|
|
349
|
-
panel.appendChild(emptyHint('No filterable columns'));
|
|
350
|
-
return;
|
|
351
|
-
}
|
|
352
|
-
const filters = target.filters || {};
|
|
353
|
-
|
|
354
|
-
panel.appendChild(popoverHead('Filter rows'));
|
|
355
|
-
|
|
356
|
-
const list = document.createElement('div');
|
|
357
|
-
list.setAttribute('data-popover-list', '');
|
|
358
|
-
|
|
359
|
-
const data = target.data || [];
|
|
360
|
-
for (const col of target.columns) {
|
|
361
|
-
if (col.hidden) continue;
|
|
362
|
-
// <field-ui inline label="…"><…control…></…></field-ui> — canonical
|
|
363
|
-
// label-binds-to-control pair, mints id + [for]. The CONTROL is
|
|
364
|
-
// chosen per-column from the column's filter shape:
|
|
365
|
-
// • 'select' → <select-ui multiple searchable> populated from data
|
|
366
|
-
// • 'text' → <input-ui type="text"> (contains match)
|
|
367
|
-
// Shape comes from col.filter when set, otherwise auto-detected
|
|
368
|
-
// from the data (id-like keys → text; ≤ 50 distinct values → select).
|
|
369
|
-
const row = document.createElement('field-ui');
|
|
370
|
-
row.setAttribute('data-filter-row', '');
|
|
371
|
-
row.setAttribute('inline', '');
|
|
372
|
-
row.setAttribute('label', col.label || col.key);
|
|
373
|
-
|
|
374
|
-
const shape = detectFilterShape(col, data);
|
|
375
|
-
const current = filters[col.key];
|
|
376
|
-
|
|
377
|
-
if (shape === 'select') {
|
|
378
|
-
const sel = document.createElement('select-ui');
|
|
379
|
-
sel.setAttribute('data-filter-input', '');
|
|
380
|
-
sel.setAttribute('multiple', '');
|
|
381
|
-
sel.setAttribute('placeholder', '—');
|
|
382
|
-
|
|
383
|
-
const uniqueValues = collectUniqueValues(col, data);
|
|
384
|
-
// Searchable kicks in at 12+ options — below that the listbox is
|
|
385
|
-
// fully scannable and a search field is overhead.
|
|
386
|
-
if (uniqueValues.length >= 12) sel.setAttribute('searchable', '');
|
|
387
|
-
|
|
388
|
-
for (const val of uniqueValues) {
|
|
389
|
-
const opt = document.createElement('option');
|
|
390
|
-
opt.setAttribute('value', val);
|
|
391
|
-
opt.textContent = val;
|
|
392
|
-
sel.appendChild(opt);
|
|
393
|
-
}
|
|
394
|
-
if (current?.op === 'select' && current.value) {
|
|
395
|
-
sel.value = current.value;
|
|
396
|
-
}
|
|
397
|
-
sel.addEventListener('change', () => {
|
|
398
|
-
const v = sel.value || '';
|
|
399
|
-
if (v) target.setFilter(col.key, v, 'select');
|
|
400
|
-
else target.setFilter(col.key, null);
|
|
401
|
-
this.dispatchEvent(new CustomEvent('filter-change', {
|
|
402
|
-
bubbles: true,
|
|
403
|
-
detail: { filters: target.filters },
|
|
404
|
-
}));
|
|
405
|
-
});
|
|
406
|
-
row.appendChild(sel);
|
|
407
|
-
} else {
|
|
408
|
-
const input = document.createElement('input-ui');
|
|
409
|
-
input.setAttribute('type', 'text');
|
|
410
|
-
input.setAttribute('size', 'sm');
|
|
411
|
-
input.setAttribute('data-filter-input', '');
|
|
412
|
-
input.setAttribute('placeholder', '—');
|
|
413
|
-
if (current?.op === 'contains') input.value = current.value ?? '';
|
|
414
|
-
input.addEventListener('input', () => {
|
|
415
|
-
const v = input.value;
|
|
416
|
-
if (v) target.setFilter(col.key, v, 'contains');
|
|
417
|
-
else target.setFilter(col.key, null);
|
|
418
|
-
this.dispatchEvent(new CustomEvent('filter-change', {
|
|
419
|
-
bubbles: true,
|
|
420
|
-
detail: { filters: target.filters },
|
|
421
|
-
}));
|
|
422
|
-
});
|
|
423
|
-
row.appendChild(input);
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
list.appendChild(row);
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
panel.appendChild(list);
|
|
430
|
-
|
|
431
|
-
if (Object.keys(filters).length) {
|
|
432
|
-
const clear = this.#mkPopoverAction('Clear all filters', () => {
|
|
433
|
-
target.clearFilters();
|
|
434
|
-
this.dispatchEvent(new CustomEvent('filter-change', {
|
|
435
|
-
bubbles: true,
|
|
436
|
-
detail: { filters: {} },
|
|
437
|
-
}));
|
|
438
|
-
this.#refreshFilterPanel();
|
|
439
|
-
});
|
|
440
|
-
panel.appendChild(clear);
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
#refreshFilterPanel() {
|
|
445
|
-
const ap = this.#activePopover;
|
|
446
|
-
if (!ap || ap.kind !== 'filter') return;
|
|
447
|
-
ap.panel.replaceChildren();
|
|
448
|
-
this.#fillFilterPanel(ap.panel);
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
// ── Sort panel ───────────────────────────────────────────────────────────
|
|
452
|
-
|
|
453
|
-
#fillSortPanel(panel) {
|
|
454
|
-
const target = this.#target;
|
|
455
|
-
if (!target?.columns?.length) {
|
|
456
|
-
panel.appendChild(emptyHint('No sortable columns'));
|
|
457
|
-
return;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
panel.appendChild(popoverHead('Sort by'));
|
|
461
|
-
|
|
462
|
-
const sortState = target.sortState || [];
|
|
463
|
-
const dirByKey = new Map(sortState.map((s) => [s.key, s.dir]));
|
|
464
|
-
|
|
465
|
-
const list = document.createElement('div');
|
|
466
|
-
list.setAttribute('data-popover-list', '');
|
|
467
|
-
|
|
468
|
-
for (const col of target.columns) {
|
|
469
|
-
if (col.hidden) continue;
|
|
470
|
-
if (col.sortable === false) continue;
|
|
471
|
-
|
|
472
|
-
// <menu-item-ui> is the canonical action-row primitive (role="menuitem",
|
|
473
|
-
// built-in icon + text slots, hover/focus tokens, danger variant). Append
|
|
474
|
-
// a trailing <icon-ui> after the auto-stamped [slot="text"] — its flex:1
|
|
475
|
-
// pushes any later child to the trailing edge.
|
|
476
|
-
const dir = dirByKey.get(col.key);
|
|
477
|
-
const row = document.createElement('menu-item-ui');
|
|
478
|
-
row.setAttribute('data-sort-row', '');
|
|
479
|
-
row.setAttribute('text', col.label || col.key);
|
|
480
|
-
row.dataset.key = col.key;
|
|
481
|
-
if (dir) row.dataset.active = dir;
|
|
482
|
-
|
|
483
|
-
// Trailing direction indicator — append AFTER menu-item-ui's connected()
|
|
484
|
-
// has run its #stamp() (defers via rAF so the [slot="text"] span exists).
|
|
485
|
-
const rafId = requestAnimationFrame(() => {
|
|
486
|
-
this.#sortIndicatorRafs.delete(rafId);
|
|
487
|
-
if (!this.isConnected) return;
|
|
488
|
-
const indicator = document.createElement('icon-ui');
|
|
489
|
-
indicator.setAttribute('data-sort-indicator', '');
|
|
490
|
-
indicator.setAttribute('size', 'xs');
|
|
491
|
-
indicator.setAttribute('name', dir === 'asc' ? 'arrow-up' : dir === 'desc' ? 'arrow-down' : 'caret-up-down');
|
|
492
|
-
row.appendChild(indicator);
|
|
493
|
-
});
|
|
494
|
-
this.#sortIndicatorRafs.add(rafId);
|
|
495
|
-
|
|
496
|
-
row.addEventListener('click', (e) => {
|
|
497
|
-
// Forward to the bound table by simulating a header click — the table
|
|
498
|
-
// already owns the asc / desc / clear cycle, so we don't duplicate state.
|
|
499
|
-
const headerCell = target.querySelector(`:scope > [data-header] [data-sort-key="${col.key}"]`);
|
|
500
|
-
if (!headerCell) return;
|
|
501
|
-
const evt = new MouseEvent('click', { bubbles: true, cancelable: true, shiftKey: e.shiftKey });
|
|
502
|
-
headerCell.dispatchEvent(evt);
|
|
503
|
-
this.dispatchEvent(new CustomEvent('sort-change', {
|
|
504
|
-
bubbles: true,
|
|
505
|
-
detail: { sortState: target.sortState },
|
|
506
|
-
}));
|
|
507
|
-
this.#refreshSortPanel();
|
|
508
|
-
});
|
|
509
|
-
|
|
510
|
-
list.appendChild(row);
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
panel.appendChild(list);
|
|
514
|
-
|
|
515
|
-
if (sortState.length) {
|
|
516
|
-
const clear = this.#mkPopoverAction('Clear sort', () => {
|
|
517
|
-
// Simulate cycle-through clicks until empty — but the table only stores
|
|
518
|
-
// one sort entry per key, and a non-shift click on an already-active key
|
|
519
|
-
// cycles to the opposite dir before clearing. Cleanest: clone columns and
|
|
520
|
-
// clear via a microtask using clicks on each active key until empty.
|
|
521
|
-
for (const s of [...sortState]) {
|
|
522
|
-
const headerCell = target.querySelector(`:scope > [data-header] [data-sort-key="${s.key}"]`);
|
|
523
|
-
if (!headerCell) continue;
|
|
524
|
-
// Two clicks toggle through asc → desc → clear when single-sort. With
|
|
525
|
-
// multi-sort entries we shift-click to remove individually.
|
|
526
|
-
const fire = (shift) => headerCell.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, shiftKey: shift }));
|
|
527
|
-
if (sortState.length > 1) {
|
|
528
|
-
// Shift-clicking through default→opposite→remove.
|
|
529
|
-
fire(true); fire(true);
|
|
530
|
-
} else {
|
|
531
|
-
// Single-sort: click twice non-shift to cycle through both dirs and clear.
|
|
532
|
-
fire(false); fire(false);
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
this.dispatchEvent(new CustomEvent('sort-change', {
|
|
536
|
-
bubbles: true,
|
|
537
|
-
detail: { sortState: target.sortState },
|
|
538
|
-
}));
|
|
539
|
-
this.#refreshSortPanel();
|
|
540
|
-
});
|
|
541
|
-
panel.appendChild(clear);
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
#refreshSortPanel() {
|
|
546
|
-
const ap = this.#activePopover;
|
|
547
|
-
if (!ap || ap.kind !== 'sort') return;
|
|
548
|
-
ap.panel.replaceChildren();
|
|
549
|
-
this.#fillSortPanel(ap.panel);
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
// ── Columns panel ────────────────────────────────────────────────────────
|
|
553
|
-
|
|
554
|
-
#fillColumnsPanel(panel) {
|
|
555
|
-
const target = this.#target;
|
|
556
|
-
if (!target?.columns?.length) {
|
|
557
|
-
panel.appendChild(emptyHint('No columns'));
|
|
558
|
-
return;
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
panel.appendChild(popoverHead('Visible columns'));
|
|
562
|
-
|
|
563
|
-
const list = document.createElement('div');
|
|
564
|
-
list.setAttribute('data-popover-list', '');
|
|
565
|
-
|
|
566
|
-
for (const col of target.columns) {
|
|
567
|
-
// <field-ui inline label="…"><check-ui></check-ui></field-ui> —
|
|
568
|
-
// canonical label-binds-to-control pair, mints id + [for] so clicking
|
|
569
|
-
// the label toggles the check. Same primitive used for filter rows.
|
|
570
|
-
const row = document.createElement('field-ui');
|
|
571
|
-
row.setAttribute('data-columns-row', '');
|
|
572
|
-
row.setAttribute('inline', '');
|
|
573
|
-
row.setAttribute('label', col.label || col.key);
|
|
574
|
-
|
|
575
|
-
const check = document.createElement('check-ui');
|
|
576
|
-
if (!col.hidden) check.setAttribute('checked', '');
|
|
577
|
-
check.dataset.key = col.key;
|
|
578
|
-
check.addEventListener('change', () => {
|
|
579
|
-
const next = target.columns.map((c) => (
|
|
580
|
-
c.key === col.key ? { ...c, hidden: !check.hasAttribute('checked') } : { ...c }
|
|
581
|
-
));
|
|
582
|
-
target.columns = next;
|
|
583
|
-
this.dispatchEvent(new CustomEvent('columns-change', {
|
|
584
|
-
bubbles: true,
|
|
585
|
-
detail: { hiddenColumns: next.filter((c) => c.hidden).map((c) => c.key) },
|
|
586
|
-
}));
|
|
587
|
-
});
|
|
588
|
-
row.appendChild(check);
|
|
589
|
-
|
|
590
|
-
list.appendChild(row);
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
panel.appendChild(list);
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
function emptyHint(text) {
|
|
598
|
-
const el = document.createElement('text-ui');
|
|
599
|
-
el.setAttribute('data-popover-empty', '');
|
|
600
|
-
el.setAttribute('color', 'subtle');
|
|
601
|
-
el.setAttribute('variant', 'caption');
|
|
602
|
-
el.textContent = text;
|
|
603
|
-
return el;
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
function popoverHead(text) {
|
|
607
|
-
// <text-ui variant="kicker"> renders the uppercase + tracked + muted-color
|
|
608
|
-
// section-marker typography role — exactly what a popover head needs. No
|
|
609
|
-
// bespoke styling required, no [data-popover-head] CSS rule.
|
|
610
|
-
const el = document.createElement('text-ui');
|
|
611
|
-
el.setAttribute('data-popover-head', '');
|
|
612
|
-
el.setAttribute('variant', 'kicker');
|
|
613
|
-
el.setAttribute('color', 'subtle');
|
|
614
|
-
el.textContent = text;
|
|
615
|
-
return el;
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
// ── Filter shape detection ─────────────────────────────────────────────────
|
|
619
|
-
|
|
620
|
-
const SELECT_THRESHOLD = 50; // ≤ N distinct values → multi-select
|
|
621
|
-
const SAMPLE_LIMIT = 500; // cap data scan for cardinality detection
|
|
622
|
-
|
|
623
|
-
/**
|
|
624
|
-
* Resolve a cell value for a column. Mirrors table.js getCellValue —
|
|
625
|
-
* supports col.accessor (function) and dot-notation col.key paths.
|
|
626
|
-
*/
|
|
627
|
-
function getCellValue(row, col) {
|
|
628
|
-
if (typeof col?.accessor === 'function') return col.accessor(row);
|
|
629
|
-
const path = col?.key;
|
|
630
|
-
if (!path || row == null) return undefined;
|
|
631
|
-
const parts = String(path).split('.');
|
|
632
|
-
let cur = row;
|
|
633
|
-
for (const p of parts) {
|
|
634
|
-
if (cur == null) return undefined;
|
|
635
|
-
cur = cur[p];
|
|
636
|
-
}
|
|
637
|
-
return cur;
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
/**
|
|
641
|
-
* Heuristic: id-like keys (`id`, `userId`, `worker_id`, `uuid`, `guid`)
|
|
642
|
-
* are searched as free text — picking from a 10k-entry listbox is worse
|
|
643
|
-
* UX than typing a known value.
|
|
644
|
-
*/
|
|
645
|
-
function looksLikeIdKey(key) {
|
|
646
|
-
if (!key) return false;
|
|
647
|
-
const k = String(key);
|
|
648
|
-
if (k === 'id' || k === 'ID' || k === 'uuid' || k === 'guid') return true;
|
|
649
|
-
if (/[a-z](Id|ID)$/.test(k)) return true; // camelCase: workerId, userId
|
|
650
|
-
if (/_id$/i.test(k)) return true; // snake_case: worker_id
|
|
651
|
-
return false;
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
/**
|
|
655
|
-
* Choose the right filter primitive for a column.
|
|
656
|
-
* col.filter = 'select' | 'text' — explicit override (table-ui contract)
|
|
657
|
-
* col.type = 'number' | 'currency' | 'percent' → 'text' (numeric range
|
|
658
|
-
* support is a future addition; falls back to contains for now)
|
|
659
|
-
* id-like key → 'text'
|
|
660
|
-
* ≤ SELECT_THRESHOLD distinct values in the sample → 'select'
|
|
661
|
-
* otherwise → 'text'
|
|
662
|
-
*/
|
|
663
|
-
function detectFilterShape(col, data) {
|
|
664
|
-
if (col?.filter === 'select' || col?.filter === 'text') return col.filter;
|
|
665
|
-
if (looksLikeIdKey(col?.key)) return 'text';
|
|
666
|
-
|
|
667
|
-
const values = [];
|
|
668
|
-
const limit = Math.min(data?.length ?? 0, SAMPLE_LIMIT);
|
|
669
|
-
const unique = new Set();
|
|
670
|
-
for (let i = 0; i < limit; i++) {
|
|
671
|
-
const v = getCellValue(data[i], col);
|
|
672
|
-
if (v == null || v === '') continue;
|
|
673
|
-
values.push(v);
|
|
674
|
-
unique.add(String(v));
|
|
675
|
-
if (unique.size > SELECT_THRESHOLD) return 'text';
|
|
676
|
-
}
|
|
677
|
-
if (!values.length) return 'text';
|
|
678
|
-
return 'select';
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
/**
|
|
682
|
-
* Collect distinct stringified values for the column from data, sorted.
|
|
683
|
-
* Used to populate select-ui options when filter shape === 'select'.
|
|
684
|
-
*/
|
|
685
|
-
function collectUniqueValues(col, data) {
|
|
686
|
-
const set = new Set();
|
|
687
|
-
const limit = Math.min(data?.length ?? 0, SAMPLE_LIMIT);
|
|
688
|
-
for (let i = 0; i < limit; i++) {
|
|
689
|
-
const v = getCellValue(data[i], col);
|
|
690
|
-
if (v == null || v === '') continue;
|
|
691
|
-
set.add(String(v));
|
|
692
|
-
}
|
|
693
|
-
return [...set].sort((a, b) => a.localeCompare(b, undefined, { numeric: true }));
|
|
694
|
-
}
|
|
12
|
+
import { defineIfFree } from '../../core/register.js';
|
|
13
|
+
import { UITableToolbar } from './class.js';
|
|
695
14
|
|
|
696
|
-
|
|
15
|
+
defineIfFree('table-toolbar-ui', UITableToolbar);
|
|
697
16
|
|
|
698
17
|
export { UITableToolbar };
|