@serkonda7/solid-components 0.0.4 → 0.0.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 CHANGED
@@ -8,7 +8,7 @@ Reusable components for [SolidJS](https://www.solidjs.com/).
8
8
 
9
9
  ## 📦 Installation
10
10
  ```sh
11
- bun install @serkonda7/solid-components solid-js
11
+ bun install @serkonda7/solid-components solid-js @tabler/icons-solidjs
12
12
  ```
13
13
 
14
14
 
@@ -26,6 +26,7 @@ import '@serkonda7/solid-components/styles.css'
26
26
 
27
27
  These components are available:
28
28
  - `DataTable` - generic table with optional sorting, row selection, actions, loading, empty states, and column customizer.
29
+ - `Combobox` - searchable, accessible dropdown with keyboard navigation, option icons, a detail column, and an optional "Add …" entry.
29
30
 
30
31
  For the full API, examples, and agent notes, see [docs.md](docs.md).
31
32
 
@@ -0,0 +1,48 @@
1
+ import type { Component, JSX } from 'solid-js';
2
+ /** Icon component rendered before an option label, e.g. one from `@tabler/icons-solidjs`. */
3
+ export type ComboboxIcon = Component<{
4
+ size?: number;
5
+ }>;
6
+ /** One `Combobox` entry. */
7
+ export interface ComboboxOption {
8
+ value: number | string;
9
+ label: string;
10
+ /** Secondary text (e.g. the manufacturer), shown muted in a column after the labels. */
11
+ detail?: string;
12
+ /** Glyph shown before the label, in the list and in the closed input. */
13
+ icon?: ComboboxIcon;
14
+ }
15
+ export interface ComboboxProps {
16
+ /** Input id, for pairing with an external `<label for>`. Generated when omitted. */
17
+ id?: string;
18
+ /** Selected option value as a string; `''` means nothing (or the empty entry) is selected. */
19
+ value: string;
20
+ /** Omitted on read-only comboboxes. */
21
+ onChange?: (value: string) => void;
22
+ options: ComboboxOption[];
23
+ /** Label of the empty (`''`) first entry; omit for no empty choice. */
24
+ emptyLabel?: string;
25
+ required?: boolean;
26
+ disabled?: boolean;
27
+ autofocus?: boolean;
28
+ ariaLabel?: string;
29
+ describedBy?: string;
30
+ /** Called whenever the list opens, e.g. to refetch the options. */
31
+ onOpen?: () => unknown;
32
+ /** Pins an "Add …" entry at the end of the list; receives the trimmed search text. */
33
+ onAdd?: (query: string) => void;
34
+ /** Text of the pinned add entry. Defaults to `Add`. */
35
+ addLabel?: string;
36
+ /** Placeholder while open and nothing is selected. Defaults to `Search`. */
37
+ searchLabel?: string;
38
+ /** Shown when no option matches the search. Defaults to `No matching objects`. */
39
+ noMatchesLabel?: string;
40
+ class?: string;
41
+ }
42
+ /**
43
+ * Searchable dropdown of `options`, with an optional leading empty choice.
44
+ * A text combobox: typing filters the options by label and detail, arrow keys
45
+ * move the highlight (wrapping around), Enter picks it, Escape closes. The
46
+ * input shows the selected label while closed; the value sits in `data-value`.
47
+ */
48
+ export declare function Combobox(props: ComboboxProps): JSX.Element;
package/dist/index.d.ts CHANGED
@@ -4,4 +4,5 @@
4
4
  * Add components here as they are implemented so consumers can import them
5
5
  * from the package root.
6
6
  */
7
+ export { Combobox, type ComboboxIcon, type ComboboxOption, type ComboboxProps, } from './combobox';
7
8
  export { DataTable, type DataTableColumn, type DataTableProps, type DataTableSort, type DataTableSortDirection, } from './data-table';
package/dist/index.js CHANGED
@@ -1,222 +1,389 @@
1
- import { className as e, createComponent as t, delegateEvents as n, effect as r, insert as i, memo as a, setAttribute as o, template as s, use as c } from "solid-js/web";
2
- import { For as l, Show as u, createEffect as d, createMemo as f, createSignal as p, createUniqueId as m, onCleanup as h } from "solid-js";
1
+ import { Dynamic as e, className as t, createComponent as n, delegateEvents as r, effect as i, insert as a, memo as o, setAttribute as s, template as c, use as l } from "solid-js/web";
2
+ import { IconChevronDown as u, IconPlus as d } from "@tabler/icons-solidjs";
3
+ import { For as f, Show as p, createEffect as m, createMemo as h, createSignal as g, createUniqueId as _, onCleanup as v, onMount as y } from "solid-js";
4
+ //#region src/combobox.tsx
5
+ var b = /*#__PURE__*/ c("<span class=combobox-value aria-hidden=true><span class=combobox-option-label></span><span class=combobox-detail>"), x = /*#__PURE__*/ c("<div role=option class=\"combobox-option combobox-option-add\">"), S = /*#__PURE__*/ c("<div class=combobox-list role=listbox>"), C = /*#__PURE__*/ c("<div><input class=combobox-input role=combobox aria-autocomplete=list autocomplete=off>"), w = /*#__PURE__*/ c("<span class=combobox-icon aria-hidden=true>"), T = /*#__PURE__*/ c("<div class=combobox-empty>"), E = /*#__PURE__*/ c("<span class=combobox-detail>"), D = /*#__PURE__*/ c("<div role=option class=combobox-option><span class=combobox-option-label>"), ee = /*#__PURE__*/ c("<span class=combobox-option-icon aria-hidden=true>");
6
+ function O(r) {
7
+ let c, v, O = _(), k = () => r.id ?? O, A = () => `${k()}-listbox`, j = (e) => `${k()}-option-${e}`, [M, N] = g(!1), [P, F] = g(""), [I, L] = g(0), R = h(() => r.emptyLabel === void 0 ? r.options : [{
8
+ value: "",
9
+ label: r.emptyLabel
10
+ }, ...r.options]), z = h(() => r.value === "" ? void 0 : R().find((e) => String(e.value) === r.value)), B = h(() => {
11
+ let e = P().trim().toLowerCase();
12
+ return e === "" ? R() : R().filter((t) => t.label.toLowerCase().includes(e) || (t.detail?.toLowerCase().includes(e) ?? !1));
13
+ }), V = () => r.options.some((e) => e.detail !== void 0), H = () => B().length, U = () => B().length + +!!r.onAdd, W = () => !r.disabled && r.onChange !== void 0;
14
+ y(() => {
15
+ r.autofocus === !0 && c?.focus();
16
+ }), m(() => {
17
+ M() && v?.querySelector(`[data-index="${I()}"]`)?.scrollIntoView({ block: "nearest" });
18
+ });
19
+ function G() {
20
+ if (M() || !W()) return;
21
+ F("");
22
+ let e = R().findIndex((e) => String(e.value) === r.value);
23
+ L(Math.max(e, 0)), N(!0), r.onOpen?.();
24
+ }
25
+ function K(e) {
26
+ if (N(!1), r.onAdd && e === H()) {
27
+ r.onAdd(P().trim());
28
+ return;
29
+ }
30
+ let t = B()[e];
31
+ t && String(t.value) !== r.value && r.onChange?.(String(t.value));
32
+ }
33
+ function q(e) {
34
+ let t = U();
35
+ M() ? t > 0 && L((I() + e + t) % t) : G();
36
+ }
37
+ function J(e) {
38
+ if (e.key === "ArrowDown" || e.key === "ArrowUp") {
39
+ e.preventDefault(), q(e.key === "ArrowDown" ? 1 : -1);
40
+ return;
41
+ }
42
+ M() && (e.key === "Enter" ? (e.preventDefault(), K(I())) : e.key === "Escape" && (e.preventDefault(), e.stopPropagation(), N(!1)));
43
+ }
44
+ let te = () => M() ? z()?.label ?? r.emptyLabel ?? r.searchLabel ?? "Search" : r.emptyLabel;
45
+ return (() => {
46
+ var m = C(), h = m.firstChild;
47
+ h.addEventListener("blur", () => N(!1)), h.$$keydown = J, h.$$input = (e) => {
48
+ let t = e.currentTarget.value;
49
+ G(), F(t), L(0);
50
+ }, h.$$click = G;
51
+ var g = c;
52
+ return typeof g == "function" ? l(g, h) : c = h, a(m, n(p, {
53
+ get when() {
54
+ return o(() => !M())() && z()?.icon;
55
+ },
56
+ children: (t) => (() => {
57
+ var r = w();
58
+ return a(r, n(e, {
59
+ get component() {
60
+ return t();
61
+ },
62
+ size: 14
63
+ })), r;
64
+ })()
65
+ }), null), a(m, n(p, {
66
+ get when() {
67
+ return o(() => !M())() && z()?.detail;
68
+ },
69
+ get children() {
70
+ var e = b(), t = e.firstChild, n = t.nextSibling;
71
+ return a(t, () => z()?.label), a(n, () => z()?.detail), e;
72
+ }
73
+ }), null), a(m, n(u, {
74
+ class: "combobox-chevron",
75
+ size: 16,
76
+ "aria-hidden": "true"
77
+ }), null), a(m, n(p, {
78
+ get when() {
79
+ return M();
80
+ },
81
+ get children() {
82
+ var t = S(), o = v;
83
+ return typeof o == "function" ? l(o, t) : v = t, a(t, n(f, {
84
+ get each() {
85
+ return B();
86
+ },
87
+ get fallback() {
88
+ return (() => {
89
+ var e = T();
90
+ return a(e, () => r.noMatchesLabel ?? "No matching objects"), e;
91
+ })();
92
+ },
93
+ children: (t, o) => (() => {
94
+ var c = D(), l = c.firstChild;
95
+ return c.$$click = () => K(o()), c.$$mousemove = () => L(o()), c.$$mousedown = (e) => e.preventDefault(), a(l, n(p, {
96
+ get when() {
97
+ return t.icon;
98
+ },
99
+ children: (t) => (() => {
100
+ var r = ee();
101
+ return a(r, n(e, {
102
+ get component() {
103
+ return t();
104
+ },
105
+ size: 14
106
+ })), r;
107
+ })()
108
+ }), null), a(l, () => t.label, null), a(c, n(p, {
109
+ get when() {
110
+ return t.detail;
111
+ },
112
+ get children() {
113
+ var e = E();
114
+ return a(e, () => t.detail), e;
115
+ }
116
+ }), null), i((e) => {
117
+ var n = j(o()), i = o(), a = String(t.value) === r.value, l = o() === I(), u = t.value === "";
118
+ return n !== e.e && s(c, "id", e.e = n), i !== e.t && s(c, "data-index", e.t = i), a !== e.a && s(c, "aria-selected", e.a = a), l !== e.o && c.classList.toggle("combobox-option-active", e.o = l), u !== e.i && c.classList.toggle("combobox-option-empty", e.i = u), e;
119
+ }, {
120
+ e: void 0,
121
+ t: void 0,
122
+ a: void 0,
123
+ o: void 0,
124
+ i: void 0
125
+ }), c;
126
+ })()
127
+ }), null), a(t, n(p, {
128
+ get when() {
129
+ return r.onAdd;
130
+ },
131
+ get children() {
132
+ var e = x();
133
+ return e.$$click = () => K(H()), e.$$mousemove = () => L(H()), e.$$mousedown = (e) => e.preventDefault(), s(e, "aria-selected", !1), a(e, n(d, {
134
+ size: 14,
135
+ "aria-hidden": "true"
136
+ }), null), a(e, () => r.addLabel ?? "Add", null), i((t) => {
137
+ var n = j(H()), r = H(), i = H() === I();
138
+ return n !== t.e && s(e, "id", t.e = n), r !== t.t && s(e, "data-index", t.t = r), i !== t.a && e.classList.toggle("combobox-option-active", t.a = i), t;
139
+ }, {
140
+ e: void 0,
141
+ t: void 0,
142
+ a: void 0
143
+ }), e;
144
+ }
145
+ }), null), i((e) => {
146
+ var n = !!V(), r = A();
147
+ return n !== e.e && t.classList.toggle("combobox-list-detail", e.e = n), r !== e.t && s(t, "id", e.t = r), e;
148
+ }, {
149
+ e: void 0,
150
+ t: void 0
151
+ }), t;
152
+ }
153
+ }), null), i((e) => {
154
+ var n = r.class ? `combobox ${r.class}` : "combobox", i = !!M(), a = !(M() || !z()?.detail), o = !(M() || z()?.icon === void 0), c = k(), l = M(), u = A(), d = M() && U() > 0 ? j(I()) : void 0, f = r.ariaLabel, p = r.describedBy, g = r.required, _ = r.disabled, v = r.onChange === void 0, y = r.value, b = te();
155
+ return n !== e.e && t(m, e.e = n), i !== e.t && m.classList.toggle("combobox-open", e.t = i), a !== e.a && m.classList.toggle("combobox-with-detail", e.a = a), o !== e.o && m.classList.toggle("combobox-with-icon", e.o = o), c !== e.i && s(h, "id", e.i = c), l !== e.n && s(h, "aria-expanded", e.n = l), u !== e.s && s(h, "aria-controls", e.s = u), d !== e.h && s(h, "aria-activedescendant", e.h = d), f !== e.r && s(h, "aria-label", e.r = f), p !== e.d && s(h, "aria-describedby", e.d = p), g !== e.l && (h.required = e.l = g), _ !== e.u && (h.disabled = e.u = _), v !== e.c && (h.readOnly = e.c = v), y !== e.w && s(h, "data-value", e.w = y), b !== e.m && s(h, "placeholder", e.m = b), e;
156
+ }, {
157
+ e: void 0,
158
+ t: void 0,
159
+ a: void 0,
160
+ o: void 0,
161
+ i: void 0,
162
+ n: void 0,
163
+ s: void 0,
164
+ h: void 0,
165
+ r: void 0,
166
+ d: void 0,
167
+ l: void 0,
168
+ u: void 0,
169
+ c: void 0,
170
+ w: void 0,
171
+ m: void 0
172
+ }), i(() => h.value = M() ? P() : z()?.label ?? ""), m;
173
+ })();
174
+ }
175
+ r([
176
+ "click",
177
+ "input",
178
+ "keydown",
179
+ "mousedown",
180
+ "mousemove"
181
+ ]);
182
+ //#endregion
3
183
  //#region src/data-table.tsx
4
- var g = /*#__PURE__*/ s("<div role=dialog class=data-table-customizer-panel><p class=data-table-customizer-title></p><ul class=data-table-customizer-list></ul><div class=data-table-customizer-actions><button type=button></button><button type=button>"), ee = /*#__PURE__*/ s("<div class=data-table-toolbar><div class=data-table-customizer><button type=button class=data-table-customizer-button aria-haspopup=dialog><span aria-hidden=true>⚙</span> "), _ = /*#__PURE__*/ s("<th class=data-table-checkbox><span class=data-table-visually-hidden>Select rows</span><input type=checkbox>"), te = /*#__PURE__*/ s("<th>Actions"), v = /*#__PURE__*/ s("<div><div class=data-table-scroll><table class=data-table><thead><tr></tr></thead><tbody>"), y = /*#__PURE__*/ s("<li><label class=data-table-customizer-option><input type=checkbox>"), b = /*#__PURE__*/ s("<button type=button class=data-table-sort>"), ne = /*#__PURE__*/ s("<button type=button class=data-table-sort-clear aria-label=\"Clear sorting\"title=\"Clear sorting\">×"), re = /*#__PURE__*/ s("<th>"), ie = /*#__PURE__*/ s("<td class=data-table-checkbox><input type=checkbox>"), x = /*#__PURE__*/ s("<td>"), ae = /*#__PURE__*/ s("<tr>"), S = /*#__PURE__*/ s("<p class=data-table-status>Loading…"), C = /*#__PURE__*/ s("<p class=data-table-status>No results.");
5
- function w(e) {
184
+ var k = /*#__PURE__*/ c("<div role=dialog class=data-table-customizer-panel><p class=data-table-customizer-title></p><ul class=data-table-customizer-list></ul><div class=data-table-customizer-actions><button type=button></button><button type=button>"), A = /*#__PURE__*/ c("<div class=data-table-toolbar><div class=data-table-customizer><button type=button class=data-table-customizer-button aria-haspopup=dialog><span aria-hidden=true>⚙</span> "), j = /*#__PURE__*/ c("<th class=data-table-checkbox><span class=data-table-visually-hidden>Select rows</span><input type=checkbox>"), M = /*#__PURE__*/ c("<th>Actions"), N = /*#__PURE__*/ c("<div><div class=data-table-scroll><table class=data-table><thead><tr></tr></thead><tbody>"), P = /*#__PURE__*/ c("<li><label class=data-table-customizer-option><input type=checkbox>"), F = /*#__PURE__*/ c("<button type=button class=data-table-sort><span class=data-table-sort-indicator aria-hidden=true>"), I = /*#__PURE__*/ c("<th>"), L = /*#__PURE__*/ c("<td class=data-table-checkbox><input type=checkbox>"), R = /*#__PURE__*/ c("<td>"), z = /*#__PURE__*/ c("<tr>"), B = /*#__PURE__*/ c("<p class=data-table-status>Loading…"), V = /*#__PURE__*/ c("<p class=data-table-status>No results.");
185
+ function H(e) {
6
186
  return typeof e == "function" ? e() : e;
7
187
  }
8
- function T(n) {
9
- let s = f(() => w(n.rows)), T = () => w(n.loading ?? !1), E = () => w(n.selected ?? []), D = n.sortKey !== void 0 || n.sortDirection !== void 0, [O, k] = p(n.defaultSort), A = () => D ? w(n.sortKey ?? void 0) : O()?.key, j = () => D ? w(n.sortDirection ?? "asc") : O()?.direction ?? "asc", M = () => w(n.showColumnCustomizer ?? !1), N = () => n.selected !== void 0 && n.onSelectionChange !== void 0, P = () => {
10
- let e = E();
11
- return s().length > 0 && s().every((t) => e.includes(n.getRowId(t)));
12
- }, F = f(() => n.defaultVisibleColumns ?? n.columns.filter((e) => e.defaultVisible !== !1).map((e) => e.key)), [I, L] = p(void 0), R = () => n.visibleColumns === void 0 ? I() ?? F() : w(n.visibleColumns), z = f(() => new Set(R())), B = f(() => n.columns.filter((e) => e.toggleable === !1 || z().has(e.key))), V = f(() => n.columns.filter((e) => e.toggleable !== !1)), H = f(() => V().map((e) => e.key));
13
- function U(e, t) {
188
+ function U(e) {
189
+ let r = h(() => H(e.rows)), c = () => H(e.loading ?? !1), u = () => H(e.selected ?? []), d = e.sortKey !== void 0 || e.sortDirection !== void 0, [y, b] = g(e.defaultSort), x = () => d ? H(e.sortKey ?? void 0) : y()?.key, S = () => d ? H(e.sortDirection ?? "asc") : y()?.direction ?? "asc", C = () => H(e.showColumnCustomizer ?? !1), w = () => e.selected !== void 0 && e.onSelectionChange !== void 0, T = () => {
190
+ let t = u();
191
+ return r().length > 0 && r().every((n) => t.includes(e.getRowId(n)));
192
+ }, E = h(() => e.defaultVisibleColumns ?? e.columns.filter((e) => e.defaultVisible !== !1).map((e) => e.key)), [D, ee] = g(void 0), O = () => e.visibleColumns === void 0 ? D() ?? E() : H(e.visibleColumns), U = h(() => new Set(O())), W = h(() => e.columns.filter((e) => e.toggleable === !1 || U().has(e.key))), G = h(() => e.columns.filter((e) => e.toggleable !== !1)), K = h(() => G().map((e) => e.key));
193
+ function q(e, t) {
14
194
  return t.sortValue ? t.sortValue(e) : e[t.key];
15
195
  }
16
- function W(e, t) {
196
+ function J(e, t) {
17
197
  return e === t ? 0 : e == null ? -1 : t == null ? 1 : typeof e == "number" && typeof t == "number" ? e - t : String(e).localeCompare(String(t), void 0, { numeric: !0 });
18
198
  }
19
- let G = f(() => {
20
- let e = s(), t = A();
21
- if (D || !t) return e;
22
- let r = n.columns.find((e) => e.key === t);
23
- if (!r) return e;
24
- let i = j() === "asc" ? 1 : -1;
25
- return [...e].sort((e, t) => i * W(U(e, r), U(t, r)));
199
+ let te = h(() => {
200
+ let t = r(), n = x();
201
+ if (d || !n) return t;
202
+ let i = e.columns.find((e) => e.key === n);
203
+ if (!i) return t;
204
+ let a = S() === "asc" ? 1 : -1;
205
+ return [...t].sort((e, t) => a * J(q(e, i), q(t, i)));
26
206
  });
27
- function K(e) {
28
- let t = {
29
- key: e,
30
- direction: A() === e && j() === "asc" ? "desc" : "asc"
207
+ function ne(t) {
208
+ let n = x() === t, r = n && S() === "desc" ? void 0 : {
209
+ key: t,
210
+ direction: n ? "desc" : "asc"
31
211
  };
32
- D || k(t), n.onSort?.(e), n.onSortChange?.(t);
33
- }
34
- function q() {
35
- D || k(void 0), n.onSortClear?.(), n.onSortChange?.(void 0);
212
+ d || b(r), e.onSort?.(t), r || e.onSortClear?.(), e.onSortChange?.(r);
36
213
  }
37
- function oe(e) {
38
- let t = new Map(n.columns.map((e, t) => [e.key, t]));
39
- return [...e].sort((e, n) => (t.get(e) ?? 0) - (t.get(n) ?? 0));
214
+ function re(t) {
215
+ let n = new Map(e.columns.map((e, t) => [e.key, t]));
216
+ return [...t].sort((e, t) => (n.get(e) ?? 0) - (n.get(t) ?? 0));
40
217
  }
41
- function J(e) {
42
- let t = oe(e);
43
- n.visibleColumns === void 0 && L(t), n.onVisibleColumnsChange?.(t);
218
+ function Y(t) {
219
+ let n = re(t);
220
+ e.visibleColumns === void 0 && ee(n), e.onVisibleColumnsChange?.(n);
44
221
  }
45
- function se(e, t) {
46
- let n = new Set(R());
47
- t ? n.add(e) : n.delete(e), J(H().filter((e) => n.has(e)));
222
+ function ie(e, t) {
223
+ let n = new Set(O());
224
+ t ? n.add(e) : n.delete(e), Y(K().filter((e) => n.has(e)));
48
225
  }
49
- function ce() {
50
- J(H());
226
+ function ae() {
227
+ Y(K());
51
228
  }
52
- function le() {
53
- J(F());
229
+ function oe() {
230
+ Y(E());
54
231
  }
55
- let Y = `${m()}-panel`, [X, Z] = p(!1), Q;
56
- d(() => {
57
- if (!X() || !M()) return;
232
+ let se = `${_()}-panel`, [X, Z] = g(!1), Q;
233
+ m(() => {
234
+ if (!X() || !C()) return;
58
235
  function e(e) {
59
236
  Q && !Q.contains(e.target) && Z(!1);
60
237
  }
61
238
  function t(e) {
62
239
  e.key === "Escape" && Z(!1);
63
240
  }
64
- document.addEventListener("pointerdown", e), document.addEventListener("keydown", t), h(() => {
241
+ document.addEventListener("pointerdown", e), document.addEventListener("keydown", t), v(() => {
65
242
  document.removeEventListener("pointerdown", e), document.removeEventListener("keydown", t);
66
243
  });
67
244
  });
68
245
  let $;
69
- d(() => {
70
- $ && ($.indeterminate = E().length > 0 && !P());
246
+ m(() => {
247
+ $ && ($.indeterminate = u().length > 0 && !T());
71
248
  });
72
- function ue(e) {
73
- n.onSelectionChange?.(e ? s().map(n.getRowId) : []);
249
+ function ce(t) {
250
+ e.onSelectionChange?.(t ? r().map(e.getRowId) : []);
74
251
  }
75
- function de(e, t) {
76
- let r = n.getRowId(e), i = E(), a = t ? [...i, r] : i.filter((e) => e !== r);
77
- n.onSelectionChange?.(a);
252
+ function le(t, n) {
253
+ let r = e.getRowId(t), i = u(), a = n ? [...i, r] : i.filter((e) => e !== r);
254
+ e.onSelectionChange?.(a);
78
255
  }
79
- function fe(e) {
80
- return E().includes(n.getRowId(e));
256
+ function ue(t) {
257
+ return u().includes(e.getRowId(t));
81
258
  }
82
- function pe(e) {
83
- return e.toggleable === !1 || z().has(e.key);
259
+ function de(e) {
260
+ return e.toggleable === !1 || U().has(e.key);
84
261
  }
85
- function me(e, t) {
86
- return n.getCell?.(e, t) ?? t.getValue?.(e) ?? null;
262
+ function fe(t, n) {
263
+ return e.getCell?.(t, n) ?? n.getValue?.(t) ?? null;
87
264
  }
88
265
  return (() => {
89
- var d = v(), f = d.firstChild, p = f.firstChild.firstChild, m = p.firstChild, h = p.nextSibling;
90
- return i(d, t(u, {
266
+ var u = N(), m = u.firstChild, h = m.firstChild.firstChild, g = h.firstChild, _ = h.nextSibling;
267
+ return a(u, n(p, {
91
268
  get when() {
92
- return M();
269
+ return C();
93
270
  },
94
271
  get children() {
95
- var e = ee(), a = e.firstChild, s = a.firstChild;
96
- s.firstChild.nextSibling;
97
- var d = Q;
98
- return typeof d == "function" ? c(d, a) : Q = a, s.$$click = () => Z((e) => !e), o(s, "aria-controls", Y), i(s, () => n.columnCustomizerLabel ?? "Columns", null), i(a, t(u, {
272
+ var t = A(), r = t.firstChild, o = r.firstChild;
273
+ o.firstChild.nextSibling;
274
+ var c = Q;
275
+ return typeof c == "function" ? l(c, r) : Q = r, o.$$click = () => Z((e) => !e), s(o, "aria-controls", se), a(o, () => e.columnCustomizerLabel ?? "Columns", null), a(r, n(p, {
99
276
  get when() {
100
277
  return X();
101
278
  },
102
279
  get children() {
103
- var e = g(), a = e.firstChild, s = a.nextSibling, c = s.nextSibling.firstChild, u = c.nextSibling;
104
- return o(e, "id", Y), i(a, () => n.columnCustomizerTitle ?? "Shown columns"), i(s, t(l, {
280
+ var t = k(), r = t.firstChild, o = r.nextSibling, c = o.nextSibling.firstChild, l = c.nextSibling;
281
+ return s(t, "id", se), a(r, () => e.columnCustomizerTitle ?? "Shown columns"), a(o, n(f, {
105
282
  get each() {
106
- return n.columns;
283
+ return e.columns;
107
284
  },
108
285
  children: (e) => (() => {
109
- var t = y(), n = t.firstChild, a = n.firstChild;
110
- return a.addEventListener("change", (t) => se(e.key, t.currentTarget.checked)), i(n, () => e.ariaLabel ?? e.label, null), r(() => a.disabled = e.toggleable === !1), r(() => a.checked = pe(e)), t;
286
+ var t = P(), n = t.firstChild, r = n.firstChild;
287
+ return r.addEventListener("change", (t) => ie(e.key, t.currentTarget.checked)), a(n, () => e.ariaLabel ?? e.label, null), i(() => r.disabled = e.toggleable === !1), i(() => r.checked = de(e)), t;
111
288
  })()
112
- })), c.$$click = ce, i(c, () => n.columnCustomizerShowAllLabel ?? "Show all"), u.$$click = le, i(u, () => n.columnCustomizerResetLabel ?? "Reset"), r(() => o(e, "aria-label", n.columnCustomizerTitle ?? "Customize columns")), e;
289
+ })), c.$$click = ae, a(c, () => e.columnCustomizerShowAllLabel ?? "Show all"), l.$$click = oe, a(l, () => e.columnCustomizerResetLabel ?? "Reset"), i(() => s(t, "aria-label", e.columnCustomizerTitle ?? "Customize columns")), t;
113
290
  }
114
- }), null), r(() => o(s, "aria-expanded", X())), e;
291
+ }), null), i(() => s(o, "aria-expanded", X())), t;
115
292
  }
116
- }), f), i(m, t(u, {
293
+ }), m), a(g, n(p, {
117
294
  get when() {
118
- return N();
295
+ return w();
119
296
  },
120
297
  get children() {
121
- var e = _(), t = e.firstChild.nextSibling;
122
- t.addEventListener("change", (e) => ue(e.currentTarget.checked));
123
- var i = $;
124
- return typeof i == "function" ? c(i, t) : $ = t, r(() => o(t, "aria-label", n.selectionLabel ?? "Select all rows")), r(() => t.checked = P()), e;
298
+ var t = j(), n = t.firstChild.nextSibling;
299
+ n.addEventListener("change", (e) => ce(e.currentTarget.checked));
300
+ var r = $;
301
+ return typeof r == "function" ? l(r, n) : $ = n, i(() => s(n, "aria-label", e.selectionLabel ?? "Select all rows")), i(() => n.checked = T()), t;
125
302
  }
126
- }), null), i(m, t(l, {
303
+ }), null), a(g, n(f, {
127
304
  get each() {
128
- return B();
305
+ return W();
129
306
  },
130
- children: (s) => (() => {
131
- var c = re();
132
- return i(c, t(u, {
307
+ children: (r) => (() => {
308
+ var c = I();
309
+ return a(c, n(p, {
133
310
  get when() {
134
- return a(() => !!s.sortable)() && n.onSort;
311
+ return o(() => !!r.sortable)() && (!d || e.onSort || e.onSortChange);
135
312
  },
136
313
  get fallback() {
137
- return s.label;
314
+ return r.label;
138
315
  },
139
316
  get children() {
140
- return [(() => {
141
- var e = b();
142
- return e.$$click = () => K(s.key), i(e, () => s.label, null), i(e, (() => {
143
- var e = a(() => A() === s.key);
144
- return () => e() ? j() === "asc" ? " ▲" : " ▼" : "";
145
- })(), null), e;
146
- })(), t(u, {
147
- get when() {
148
- return a(() => A() === s.key)() && (!D || n.onSortClear || n.onSortChange);
149
- },
150
- get children() {
151
- var e = ne();
152
- return e.$$click = q, e;
153
- }
154
- })];
317
+ var e = F(), t = e.firstChild;
318
+ return e.$$click = () => ne(r.key), a(e, () => r.label, t), a(t, (() => {
319
+ var e = o(() => x() === r.key);
320
+ return () => e() ? S() === "asc" ? "▲" : "▼" : "";
321
+ })()), e;
155
322
  }
156
- })), r((t) => {
157
- var n = s.class, r = s.sortable ? A() === s.key ? j() === "asc" ? "ascending" : "descending" : "none" : void 0;
158
- return n !== t.e && e(c, t.e = n), r !== t.t && o(c, "aria-sort", t.t = r), t;
323
+ })), i((e) => {
324
+ var n = r.class, i = r.sortable ? x() === r.key ? S() === "asc" ? "ascending" : "descending" : "none" : void 0;
325
+ return n !== e.e && t(c, e.e = n), i !== e.t && s(c, "aria-sort", e.t = i), e;
159
326
  }, {
160
327
  e: void 0,
161
328
  t: void 0
162
329
  }), c;
163
330
  })()
164
- }), null), i(m, t(u, {
331
+ }), null), a(g, n(p, {
165
332
  get when() {
166
- return n.rowActions;
333
+ return e.rowActions;
167
334
  },
168
335
  get children() {
169
- return te();
336
+ return M();
170
337
  }
171
- }), null), i(h, t(l, {
338
+ }), null), a(_, n(f, {
172
339
  get each() {
173
- return G();
340
+ return te();
174
341
  },
175
- children: (a) => (() => {
176
- var s = ae();
177
- return i(s, t(u, {
342
+ children: (r) => (() => {
343
+ var o = z();
344
+ return a(o, n(p, {
178
345
  get when() {
179
- return N();
346
+ return w();
180
347
  },
181
348
  get children() {
182
- var e = ie(), t = e.firstChild;
183
- return t.addEventListener("change", (e) => de(a, e.currentTarget.checked)), r(() => o(t, "aria-label", `Select row ${n.getRowId(a)}`)), r(() => t.checked = fe(a)), e;
349
+ var t = L(), n = t.firstChild;
350
+ return n.addEventListener("change", (e) => le(r, e.currentTarget.checked)), i(() => s(n, "aria-label", `Select row ${e.getRowId(r)}`)), i(() => n.checked = ue(r)), t;
184
351
  }
185
- }), null), i(s, t(l, {
352
+ }), null), a(o, n(f, {
186
353
  get each() {
187
- return B();
354
+ return W();
188
355
  },
189
- children: (t) => (() => {
190
- var n = x();
191
- return i(n, () => me(a, t)), r(() => e(n, t.class)), n;
356
+ children: (e) => (() => {
357
+ var n = R();
358
+ return a(n, () => fe(r, e)), i(() => t(n, e.class)), n;
192
359
  })()
193
- }), null), i(s, t(u, {
360
+ }), null), a(o, n(p, {
194
361
  get when() {
195
- return n.rowActions;
362
+ return e.rowActions;
196
363
  },
197
364
  get children() {
198
- var e = x();
199
- return i(e, () => n.rowActions?.(a)), e;
365
+ var t = R();
366
+ return a(t, () => e.rowActions?.(r)), t;
200
367
  }
201
- }), null), s;
368
+ }), null), o;
202
369
  })()
203
- })), i(d, t(u, {
370
+ })), a(u, n(p, {
204
371
  get when() {
205
- return T();
372
+ return c();
206
373
  },
207
374
  get children() {
208
- return n.loadingContent ?? S();
375
+ return e.loadingContent ?? B();
209
376
  }
210
- }), null), i(d, t(u, {
377
+ }), null), a(u, n(p, {
211
378
  get when() {
212
- return a(() => !T())() && (n.empty === void 0 ? s().length === 0 : w(n.empty));
379
+ return o(() => !c())() && (e.empty === void 0 ? r().length === 0 : H(e.empty));
213
380
  },
214
381
  get children() {
215
- return n.emptyContent ?? C();
382
+ return e.emptyContent ?? V();
216
383
  }
217
- }), null), r(() => e(d, `data-table-wrap${n.class ? ` ${n.class}` : ""}`)), d;
384
+ }), null), i(() => t(u, `data-table-wrap${e.class ? ` ${e.class}` : ""}`)), u;
218
385
  })();
219
386
  }
220
- n(["click"]);
387
+ r(["click"]);
221
388
  //#endregion
222
- export { T as DataTable };
389
+ export { O as Combobox, U as DataTable };
package/dist/styles.css CHANGED
@@ -118,20 +118,11 @@
118
118
  text-decoration: underline;
119
119
  }
120
120
 
121
- .data-table-sort-clear {
122
- margin-left: 0.25rem;
123
- padding: 0;
124
- border: none;
125
- background: transparent;
126
- color: inherit;
127
- font: inherit;
128
- line-height: 1;
129
- cursor: pointer;
130
- }
131
-
132
- .data-table-sort-clear:hover {
133
- color: var(--data-table-sort-clear-hover, currentColor);
134
- opacity: 0.7;
121
+ .data-table-sort-indicator {
122
+ display: inline-block;
123
+ width: 1em;
124
+ margin-left: 0.25em;
125
+ text-align: center;
135
126
  }
136
127
 
137
128
  .data-table-status {
@@ -146,3 +137,221 @@
146
137
  clip: rect(0 0 0 0);
147
138
  white-space: nowrap;
148
139
  }
140
+
141
+ /* Combobox: text input plus an absolutely positioned listbox below it. */
142
+ .combobox {
143
+ position: relative;
144
+ min-width: 0;
145
+ }
146
+
147
+ .combobox-input {
148
+ box-sizing: border-box;
149
+ width: 100%;
150
+ padding: 0.375rem 1.8rem 0.375rem 0.5rem;
151
+ border: 1px solid
152
+ var(--combobox-border, var(--border, light-dark(#d0d0d0, #3a3e45)));
153
+ border-radius: 0.375rem;
154
+ background: var(
155
+ --combobox-bg,
156
+ var(--surface, light-dark(#ffffff, #1d2024))
157
+ );
158
+ color: inherit;
159
+ font: inherit;
160
+ text-overflow: ellipsis;
161
+ cursor: pointer;
162
+ }
163
+
164
+ .combobox-open .combobox-input {
165
+ cursor: text;
166
+ }
167
+
168
+ .combobox-input:disabled {
169
+ cursor: not-allowed;
170
+ opacity: 0.6;
171
+ }
172
+
173
+ .combobox-with-detail .combobox-input {
174
+ /* The `.combobox-value` overlay renders the label alongside its detail. */
175
+ color: transparent;
176
+ }
177
+
178
+ .combobox-with-icon .combobox-input {
179
+ padding-left: 1.7rem;
180
+ }
181
+
182
+ /* Closed-state glyph of the selected option, inside the input's left padding. */
183
+ .combobox-icon {
184
+ position: absolute;
185
+ top: 50%;
186
+ left: 0.55rem;
187
+ display: inline-flex;
188
+ transform: translateY(-50%);
189
+ color: var(
190
+ --combobox-muted,
191
+ var(--text-secondary, light-dark(#616161, #9b9da5))
192
+ );
193
+ pointer-events: none;
194
+ }
195
+
196
+ .combobox-option-icon {
197
+ display: inline-flex;
198
+ margin-right: 0.4rem;
199
+ vertical-align: -0.15em;
200
+ color: var(
201
+ --combobox-muted,
202
+ var(--text-secondary, light-dark(#616161, #9b9da5))
203
+ );
204
+ }
205
+
206
+ .combobox-chevron {
207
+ position: absolute;
208
+ top: 50%;
209
+ right: 0.5rem;
210
+ transform: translateY(-50%);
211
+ color: var(
212
+ --combobox-muted,
213
+ var(--text-secondary, light-dark(#616161, #9b9da5))
214
+ );
215
+ pointer-events: none;
216
+ }
217
+
218
+ .combobox-list {
219
+ position: absolute;
220
+ top: calc(100% + 0.2rem);
221
+ left: 0;
222
+ right: 0;
223
+ z-index: 20;
224
+ max-height: 16rem;
225
+ overflow-y: auto;
226
+ margin: 0;
227
+ padding: 0.2rem;
228
+ border: 1px solid
229
+ var(--combobox-border, var(--border, light-dark(#d0d0d0, #3a3e45)));
230
+ border-radius: 0.375rem;
231
+ background: var(
232
+ --combobox-panel-bg,
233
+ var(--surface, light-dark(#ffffff, #1d2024))
234
+ );
235
+ box-shadow: 0 8px 24px rgb(0 0 0 / 12%);
236
+ }
237
+
238
+ .combobox-option,
239
+ .combobox-empty {
240
+ padding: 0.3rem 0.5rem;
241
+ border-radius: 0.25rem;
242
+ }
243
+
244
+ .combobox-option {
245
+ cursor: pointer;
246
+ }
247
+
248
+ .combobox-option-label,
249
+ .combobox-detail {
250
+ overflow: hidden;
251
+ text-overflow: ellipsis;
252
+ white-space: nowrap;
253
+ }
254
+
255
+ .combobox-detail {
256
+ color: var(
257
+ --combobox-muted,
258
+ var(--text-secondary, light-dark(#616161, #9b9da5))
259
+ );
260
+ font-weight: 400;
261
+ font-size: 0.875em;
262
+ }
263
+
264
+ /* Closed-state value with detail, overlaid on the (transparent) input text. */
265
+ .combobox-value {
266
+ position: absolute;
267
+ top: 50%;
268
+ left: calc(0.5rem + 1px);
269
+ right: 1.8rem;
270
+ transform: translateY(-50%);
271
+ display: flex;
272
+ align-items: baseline;
273
+ gap: 0.6rem;
274
+ min-width: 0;
275
+ pointer-events: none;
276
+ }
277
+
278
+ .combobox-with-icon .combobox-value {
279
+ left: calc(1.7rem + 1px);
280
+ }
281
+
282
+ .combobox-value > .combobox-option-label {
283
+ flex: 0 1 auto;
284
+ min-width: 3rem;
285
+ }
286
+
287
+ .combobox-value > .combobox-detail {
288
+ flex: 0 1 auto;
289
+ min-width: 0;
290
+ }
291
+
292
+ .combobox-option-active {
293
+ background: var(
294
+ --combobox-hover-bg,
295
+ var(--surface-hover, light-dark(#f5f5f5, #292d33))
296
+ );
297
+ }
298
+
299
+ .combobox-option[aria-selected="true"] {
300
+ font-weight: 600;
301
+ }
302
+
303
+ /* Pinned "Add …" entry below the options, kept in view while scrolling. */
304
+ .combobox-option-add {
305
+ position: sticky;
306
+ bottom: -0.2rem;
307
+ display: flex;
308
+ align-items: center;
309
+ gap: 0.4rem;
310
+ margin-top: 0.2rem;
311
+ border-top: 1px solid
312
+ var(--combobox-border, var(--border, light-dark(#d0d0d0, #3a3e45)));
313
+ border-radius: 0 0 0.25rem 0.25rem;
314
+ background: var(
315
+ --combobox-panel-bg,
316
+ var(--surface, light-dark(#ffffff, #1d2024))
317
+ );
318
+ color: var(--combobox-accent, var(--primary, light-dark(#1a66d6, #6ea8fe)));
319
+ font-weight: 600;
320
+ }
321
+
322
+ .combobox-option-add.combobox-option-active {
323
+ background: var(
324
+ --combobox-hover-bg,
325
+ var(--surface-hover, light-dark(#f5f5f5, #292d33))
326
+ );
327
+ }
328
+
329
+ .combobox-option-empty,
330
+ .combobox-empty {
331
+ color: var(
332
+ --combobox-muted,
333
+ var(--text-secondary, light-dark(#616161, #9b9da5))
334
+ );
335
+ }
336
+
337
+ /* Options with a detail line up in two columns: the detail starts right
338
+ after the longest label. */
339
+ .combobox-list-detail {
340
+ display: grid;
341
+ grid-template-columns: minmax(0, max-content) minmax(0, 1fr);
342
+ column-gap: 1rem;
343
+ align-content: start;
344
+ }
345
+
346
+ .combobox-list-detail > .combobox-option,
347
+ .combobox-list-detail > .combobox-empty {
348
+ display: grid;
349
+ grid-column: 1 / -1;
350
+ grid-template-columns: subgrid;
351
+ align-items: baseline;
352
+ }
353
+
354
+ .combobox-list-detail > .combobox-option-add {
355
+ display: flex;
356
+ align-items: center;
357
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serkonda7/solid-components",
3
- "version": "0.0.4",
3
+ "version": "0.0.7",
4
4
  "description": "Reusable components for SolidJS",
5
5
  "license": "MIT",
6
6
  "author": "serkonda7",
@@ -28,6 +28,7 @@
28
28
  "lint": "biome check --write",
29
29
  "lint:ci": "biome ci",
30
30
  "check": "tsc --noEmit",
31
+ "test": "vitest run",
31
32
  "examples": "vite --config examples/vite.config.ts",
32
33
  "build:dev": "bun run scripts/build.ts --dev",
33
34
  "build": "bun run check && bun run scripts/build.ts --prod",
@@ -38,14 +39,19 @@
38
39
  },
39
40
  "packageManager": "bun@1.4.2",
40
41
  "peerDependencies": {
42
+ "@tabler/icons-solidjs": ">=3.0.0",
41
43
  "solid-js": ">=1.9.0"
42
44
  },
43
45
  "devDependencies": {
44
46
  "@biomejs/biome": "2.5.14",
47
+ "@tabler/icons-solidjs": "~3.48.0",
45
48
  "@types/bun": "1.4.2",
49
+ "@vitest/browser-playwright": "^5.0.2",
50
+ "playwright": "~1.63.0",
46
51
  "solid-js": "~1.9.15",
47
52
  "typescript": "~7.0.2",
48
53
  "vite": "~8.3.0",
49
- "vite-plugin-solid": "~2.11.14"
54
+ "vite-plugin-solid": "~2.11.14",
55
+ "vitest": "~5.0.2"
50
56
  }
51
57
  }