basalt-ui 1.26.0 → 1.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +8 -0
- package/MIGRATING.md +299 -33
- package/agent/rules/basalt-controls.md +17 -12
- package/agent/rules/basalt-state.md +5 -0
- package/configs/oxlint-plugin.js +251 -66
- package/configs/oxlint-plugin.test.ts +243 -0
- package/configs/oxlint.json +5 -5
- package/dist/controls/enum-filter.d.ts +11 -2
- package/dist/controls/enum-filter.d.ts.map +1 -1
- package/dist/controls/enum-filter.js +4 -2
- package/dist/controls/filter-context.d.ts +7 -2
- package/dist/controls/filter-context.d.ts.map +1 -1
- package/dist/controls/index.d.ts +1 -0
- package/dist/controls/index.d.ts.map +1 -1
- package/dist/controls/index.js +2 -0
- package/dist/controls/multi-select-filter.js +2 -2
- package/dist/controls/number-filter.d.ts +26 -0
- package/dist/controls/number-filter.d.ts.map +1 -0
- package/dist/controls/number-filter.js +143 -0
- package/dist/controls/range-filter.d.ts +19 -5
- package/dist/controls/range-filter.d.ts.map +1 -1
- package/dist/controls/range-filter.js +22 -7
- package/dist/controls/range-filter.type-guard.test.d.ts +2 -0
- package/dist/controls/range-filter.type-guard.test.d.ts.map +1 -0
- package/dist/controls/search-filter.js +1 -1
- package/dist/controls/toggle-filter.js +1 -1
- package/dist/dashboard/index.d.ts +1 -1
- package/dist/dashboard/index.d.ts.map +1 -1
- package/dist/dashboard/index.js +3 -1
- package/dist/dashboard/stat-card.d.ts +52 -1
- package/dist/dashboard/stat-card.d.ts.map +1 -1
- package/dist/dashboard/stat-card.js +42 -15
- package/dist/dashboard/stat-card.module.css +71 -0
- package/dist/dashboard/stat-card.module.css.d.ts +4 -0
- package/dist/data/data-table.d.ts.map +1 -1
- package/dist/data/data-table.js +8 -0
- package/dist/guard/grace.test.d.ts.map +1 -1
- package/dist/guard/index.d.ts +1 -1
- package/dist/guard/index.d.ts.map +1 -1
- package/dist/guard/index.js +6 -10
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/router-tanstack/field.d.ts +1 -1
- package/dist/router-tanstack/field.d.ts.map +1 -1
- package/dist/router-tanstack/index.d.ts +1 -1
- package/dist/router-tanstack/index.d.ts.map +1 -1
- package/dist/router-tanstack/search-store.d.ts +4 -1
- package/dist/router-tanstack/search-store.d.ts.map +1 -1
- package/dist/router-tanstack/search-store.js +91 -7
- package/dist/shell/page-bar.d.ts +1 -1
- package/dist/state/fields.d.ts +209 -36
- package/dist/state/fields.d.ts.map +1 -1
- package/dist/state/fields.js +68 -32
- package/dist/state/persisted.d.ts +12 -0
- package/dist/state/persisted.d.ts.map +1 -1
- package/dist/state/persisted.js +36 -18
- package/dist/state/persisted.test.d.ts +2 -0
- package/dist/state/persisted.test.d.ts.map +1 -0
- package/dist/state.d.ts +1 -1
- package/dist/state.d.ts.map +1 -1
- package/dist/surfaces.d.ts +1 -1
- package/dist/surfaces.d.ts.map +1 -1
- package/dist/surfaces.js +4 -3
- package/dist/widget-header/widget-header.d.ts +31 -1
- package/dist/widget-header/widget-header.d.ts.map +1 -1
- package/dist/widget-header/widget-header.js +13 -1
- package/dist/widget-header/widget-header.module.css +15 -0
- package/dist/widget-header/widget-header.module.css.d.ts +1 -0
- package/llms.txt +3 -3
- package/package.json +1 -1
- package/src/controls/controls.router.test.tsx +416 -0
- package/src/controls/enum-filter.tsx +14 -3
- package/src/controls/filter-context.tsx +7 -2
- package/src/controls/index.ts +1 -0
- package/src/controls/multi-select-filter.tsx +2 -2
- package/src/controls/number-filter.tsx +291 -0
- package/src/controls/props-contract.test.tsx +31 -0
- package/src/controls/range-filter.tsx +64 -11
- package/src/controls/range-filter.type-guard.test.tsx +122 -0
- package/src/controls/search-filter.tsx +1 -1
- package/src/controls/toggle-filter.tsx +1 -1
- package/src/dashboard/index.ts +6 -1
- package/src/dashboard/stat-card.module.css +71 -0
- package/src/dashboard/stat-card.module.css.d.ts +4 -0
- package/src/dashboard/stat-card.test.tsx +231 -0
- package/src/dashboard/stat-card.tsx +81 -0
- package/src/data/data-table.tsx +8 -0
- package/src/guard/check-source.test.ts +35 -2
- package/src/guard/grace.test.ts +14 -5
- package/src/guard/index.ts +30 -17
- package/src/index.ts +5 -0
- package/src/router-tanstack/field.ts +4 -0
- package/src/router-tanstack/index.ts +4 -0
- package/src/router-tanstack/search-store.router.test.tsx +232 -11
- package/src/router-tanstack/search-store.test.ts +160 -0
- package/src/router-tanstack/search-store.ts +177 -11
- package/src/shell/page-bar.tsx +1 -1
- package/src/state/fields.test.tsx +254 -0
- package/src/state/fields.ts +328 -70
- package/src/state/persisted.test.ts +91 -0
- package/src/state/persisted.ts +76 -26
- package/src/state.ts +6 -0
- package/src/surfaces.ts +4 -3
- package/src/widget-header/widget-header.module.css +15 -0
- package/src/widget-header/widget-header.module.css.d.ts +1 -0
- package/src/widget-header/widget-header.tsx +46 -1
package/dist/state/fields.js
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import { useSyncExternalStore } from "react";
|
|
2
|
-
import { createPersistedState, readPersistedValue } from "./persisted.js";
|
|
2
|
+
import { createPersistedState, readPersistedValue, writePersistedValue } from "./persisted.js";
|
|
3
|
+
function rangeField(o, lane) {
|
|
4
|
+
const { fallback } = o;
|
|
5
|
+
return {
|
|
6
|
+
kind: "range",
|
|
7
|
+
presets: o.presets,
|
|
8
|
+
// A thunk fallback stays a thunk — wrapped so the lazy value is read at the same moment every
|
|
9
|
+
// other lane reads one, not once here at definition.
|
|
10
|
+
fallback: typeof fallback === "function" ? () => ({ preset: fallback() }) : { preset: fallback },
|
|
11
|
+
custom: o.custom ?? false,
|
|
12
|
+
params: o.params ?? {},
|
|
13
|
+
window: o.window ?? {},
|
|
14
|
+
lane: resolveLane(lane)
|
|
15
|
+
};
|
|
16
|
+
}
|
|
3
17
|
const field = {
|
|
4
18
|
/** A closed string enum — a tab, a compare mode, a single-select filter. */
|
|
5
19
|
enum(values, fallback, lane) {
|
|
@@ -9,29 +23,7 @@ const field = {
|
|
|
9
23
|
multi(values, fallback = [], lane) {
|
|
10
24
|
return { kind: "multi", values, fallback, lane: resolveLane(lane) };
|
|
11
25
|
},
|
|
12
|
-
|
|
13
|
-
* A time window: THREE URL params (preset + `from` + `to`, each renamable through `params`), so a
|
|
14
|
-
* consumer's existing deep links and loaders keep their shape. `custom: true` additionally
|
|
15
|
-
* allows the preset `'custom'` with two ISO dates.
|
|
16
|
-
*
|
|
17
|
-
* A second `field.range` in the same store — or a second range store composed into the same
|
|
18
|
-
* route's `validateSearch` — MUST rename `from`/`to` via `params`: every range defaults to the
|
|
19
|
-
* literal param names `'from'`/`'to'`, and two fields silently sharing them makes the later one's
|
|
20
|
-
* `toSearch` overwrite the earlier one's dates. `createStoreCore` throws at definition for the
|
|
21
|
-
* in-store case; the cross-store case is not detectable by either store alone.
|
|
22
|
-
*/
|
|
23
|
-
range(o, lane) {
|
|
24
|
-
return {
|
|
25
|
-
kind: "range",
|
|
26
|
-
presets: o.presets,
|
|
27
|
-
fallback: { preset: o.fallback },
|
|
28
|
-
// The only cast left, and it is narrow: `C` is inferred from this very literal, so the
|
|
29
|
-
// runtime boolean IS `C` — `?? false` is what the compiler cannot see through.
|
|
30
|
-
custom: o.custom ?? false,
|
|
31
|
-
params: o.params ?? {},
|
|
32
|
-
lane: resolveLane(lane)
|
|
33
|
-
};
|
|
34
|
-
},
|
|
26
|
+
range: rangeField,
|
|
35
27
|
/** A number — pagination, a threshold. Out-of-range input clamps to `min`/`max`. */
|
|
36
28
|
number(o, lane) {
|
|
37
29
|
return {
|
|
@@ -64,6 +56,9 @@ function resolveLane(lane) {
|
|
|
64
56
|
history: lane?.history === "push" ? "push" : "replace"
|
|
65
57
|
};
|
|
66
58
|
}
|
|
59
|
+
function resolveFallback(raw) {
|
|
60
|
+
return typeof raw === "function" ? raw() : raw;
|
|
61
|
+
}
|
|
67
62
|
const ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}(?:T[\d:.]+(?:Z|[+-]\d{2}:\d{2})?)?$/;
|
|
68
63
|
function isIsoDate(raw) {
|
|
69
64
|
return typeof raw === "string" && ISO_DATE_RE.test(raw) && !Number.isNaN(Date.parse(raw));
|
|
@@ -126,7 +121,10 @@ function resolveFieldCodec(name, f, opts) {
|
|
|
126
121
|
lane: f.lane,
|
|
127
122
|
params: [presetParam, fromParam, toParam],
|
|
128
123
|
primary: presetParam,
|
|
129
|
-
fallback
|
|
124
|
+
get fallback() {
|
|
125
|
+
return resolveFallback(f.fallback);
|
|
126
|
+
},
|
|
127
|
+
windows: f.window,
|
|
130
128
|
decode,
|
|
131
129
|
fromSearch: (search) => decode({
|
|
132
130
|
preset: search[presetParam],
|
|
@@ -160,7 +158,7 @@ function resolveFieldCodec(name, f, opts) {
|
|
|
160
158
|
if (f.max !== void 0 && value > f.max) value = f.max;
|
|
161
159
|
return value;
|
|
162
160
|
};
|
|
163
|
-
return single(name, f, decode);
|
|
161
|
+
return single(name, f, decode, { bounds: { min: f.min, max: f.max, int: f.int } });
|
|
164
162
|
}
|
|
165
163
|
case "boolean": {
|
|
166
164
|
return single(name, f, decodeBoolean);
|
|
@@ -184,7 +182,10 @@ function single(name, f, decode, extra) {
|
|
|
184
182
|
lane: f.lane,
|
|
185
183
|
params: [name],
|
|
186
184
|
primary: name,
|
|
187
|
-
fallback
|
|
185
|
+
get fallback() {
|
|
186
|
+
return resolveFallback(f.fallback);
|
|
187
|
+
},
|
|
188
|
+
...extra?.bounds !== void 0 && { bounds: extra.bounds },
|
|
188
189
|
decode,
|
|
189
190
|
fromSearch: (search) => decodeUrl(search[name]),
|
|
190
191
|
toSearch: (value) => ({ [name]: value }),
|
|
@@ -235,6 +236,10 @@ function createMemoryLane() {
|
|
|
235
236
|
values.set(name, value);
|
|
236
237
|
bump();
|
|
237
238
|
},
|
|
239
|
+
remove: (name) => {
|
|
240
|
+
if (!values.delete(name)) return;
|
|
241
|
+
bump();
|
|
242
|
+
},
|
|
238
243
|
clear: () => {
|
|
239
244
|
if (values.size === 0) return;
|
|
240
245
|
values.clear();
|
|
@@ -279,24 +284,51 @@ function createStoreCore(o) {
|
|
|
279
284
|
const map = labels.get(entry.name);
|
|
280
285
|
return entry.codec.optionValues.map((value) => ({ value, label: map?.[value] ?? value }));
|
|
281
286
|
};
|
|
287
|
+
const clearField = (name) => {
|
|
288
|
+
const record = readRecord();
|
|
289
|
+
if (!Object.hasOwn(record, name)) return;
|
|
290
|
+
const next = { ...record };
|
|
291
|
+
delete next[name];
|
|
292
|
+
writePersistedValue(o.key, version, fromRecord(next));
|
|
293
|
+
};
|
|
282
294
|
const writeField = (setRecord, name, value) => {
|
|
283
295
|
setRecord({ ...readRecord(), [name]: value });
|
|
284
296
|
};
|
|
285
|
-
const handle = (entry, use) => {
|
|
297
|
+
const handle = (entry, use, clear) => {
|
|
286
298
|
const { codec } = entry;
|
|
287
299
|
const built = {
|
|
288
300
|
kind: codec.kind,
|
|
289
|
-
fallback: codec.fallback,
|
|
290
301
|
use,
|
|
302
|
+
clear,
|
|
291
303
|
isDefault: (v) => codec.equals(v, codec.fallback)
|
|
292
304
|
};
|
|
293
305
|
if (codec.kind === "range") {
|
|
294
|
-
built["toWindow"] = (v) =>
|
|
306
|
+
built["toWindow"] = (v) => {
|
|
307
|
+
if (v.preset === "custom" && v.from !== void 0 && v.to !== void 0) {
|
|
308
|
+
return { from: v.from, to: v.to };
|
|
309
|
+
}
|
|
310
|
+
const windows = codec.windows;
|
|
311
|
+
if (windows !== void 0 && Object.hasOwn(windows, v.preset)) {
|
|
312
|
+
const resolver = windows[v.preset];
|
|
313
|
+
if (resolver !== void 0) return resolver(/* @__PURE__ */ new Date());
|
|
314
|
+
}
|
|
315
|
+
return { window: v.preset };
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
if (codec.kind === "number") {
|
|
319
|
+
const bounds = codec.bounds;
|
|
320
|
+
built["min"] = bounds?.min;
|
|
321
|
+
built["max"] = bounds?.max;
|
|
322
|
+
built["int"] = bounds?.int ?? false;
|
|
295
323
|
}
|
|
296
324
|
Object.defineProperty(built, "options", {
|
|
297
325
|
enumerable: true,
|
|
298
326
|
get: () => optionsFor(entry)
|
|
299
327
|
});
|
|
328
|
+
Object.defineProperty(built, "fallback", {
|
|
329
|
+
enumerable: true,
|
|
330
|
+
get: () => codec.fallback
|
|
331
|
+
});
|
|
300
332
|
return built;
|
|
301
333
|
};
|
|
302
334
|
return {
|
|
@@ -317,6 +349,8 @@ function createStoreCore(o) {
|
|
|
317
349
|
readMemoryValue: (entry) => memory.resolve(entry),
|
|
318
350
|
useMemoryVersion: () => useSyncExternalStore(memory.subscribe, memory.getVersion, memory.getVersion),
|
|
319
351
|
resetMemory: () => memory.clear(),
|
|
352
|
+
clearField,
|
|
353
|
+
clearMemory: (name) => memory.remove(name),
|
|
320
354
|
setLabels(map) {
|
|
321
355
|
for (const [name, entry] of Object.entries(map)) {
|
|
322
356
|
if (entry !== void 0) labels.set(name, entry);
|
|
@@ -348,8 +382,10 @@ function createLocalStore(o) {
|
|
|
348
382
|
};
|
|
349
383
|
const field2 = {};
|
|
350
384
|
for (const entry of core.entries) {
|
|
351
|
-
const
|
|
352
|
-
|
|
385
|
+
const persisted = entry.codec.lane.persist;
|
|
386
|
+
const use = persisted ? persistedUse(entry) : core.memoryUse(entry);
|
|
387
|
+
const clear = persisted ? () => core.clearField(entry.name) : () => core.clearMemory(entry.name);
|
|
388
|
+
field2[entry.name] = core.handle(entry, use, clear);
|
|
353
389
|
}
|
|
354
390
|
const store = {
|
|
355
391
|
field: field2,
|
|
@@ -45,4 +45,16 @@ export declare function createPersistedState<T>(opts: PersistedStateOptions<T>):
|
|
|
45
45
|
* // stored is '1d' | '7d' | '30d' | null
|
|
46
46
|
*/
|
|
47
47
|
export declare function readPersistedValue(key: string, version?: number): unknown | null;
|
|
48
|
+
/**
|
|
49
|
+
* Write a value under the namespaced key `basalt:<key>` in the same versioned envelope
|
|
50
|
+
* `createPersistedState` writes, and wake every hook on that key — the write half of
|
|
51
|
+
* `readPersistedValue`, and plain in the same way: no React required.
|
|
52
|
+
*
|
|
53
|
+
* The one call site is a store field's `clear()`, which has to DELETE its key from the store's
|
|
54
|
+
* record from an event handler, where the hook's setter is not in reach. Not exported from the
|
|
55
|
+
* `basalt-ui/state` barrel: the two store factories are the API, this is their seam.
|
|
56
|
+
*
|
|
57
|
+
* @internal
|
|
58
|
+
*/
|
|
59
|
+
export declare function writePersistedValue(key: string, version: number, value: unknown): void;
|
|
48
60
|
//# sourceMappingURL=persisted.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"persisted.d.ts","sourceRoot":"","sources":["../../src/state/persisted.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAOnD,MAAM,MAAM,qBAAqB,CAAC,CAAC,IAAI;IACrC,+DAA+D;IAC/D,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,2DAA2D;IAC3D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,gFAAgF;IAChF,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAA;IACnB,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,KAAK,CAAC,CAAA;IACjE;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,gBAAgB,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;CAC/C,CAAA;
|
|
1
|
+
{"version":3,"file":"persisted.d.ts","sourceRoot":"","sources":["../../src/state/persisted.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAOnD,MAAM,MAAM,qBAAqB,CAAC,CAAC,IAAI;IACrC,+DAA+D;IAC/D,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,2DAA2D;IAC3D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,gFAAgF;IAChF,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAA;IACnB,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,KAAK,CAAC,CAAA;IACjE;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,gBAAgB,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;CAC/C,CAAA;AA4FD;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EACpC,IAAI,EAAE,qBAAqB,CAAC,CAAC,CAAC,GAC7B,MAAM,SAAS,CAAC,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC,CAwEvC;AAID;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAYhF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAItF"}
|
package/dist/state/persisted.js
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import { useSyncExternalStore } from "react";
|
|
2
2
|
const noopSubscribe = (_cb) => () => {
|
|
3
3
|
};
|
|
4
|
+
const channels = /* @__PURE__ */ new Map();
|
|
5
|
+
function notify(storageKey) {
|
|
6
|
+
const channel = channels.get(storageKey);
|
|
7
|
+
if (channel === void 0) return;
|
|
8
|
+
for (const listener of channel.listeners) listener();
|
|
9
|
+
}
|
|
10
|
+
function channelFor(storageKey) {
|
|
11
|
+
const existing = channels.get(storageKey);
|
|
12
|
+
if (existing !== void 0) return existing;
|
|
13
|
+
const created = { listeners: /* @__PURE__ */ new Set(), handler: null };
|
|
14
|
+
channels.set(storageKey, created);
|
|
15
|
+
return created;
|
|
16
|
+
}
|
|
4
17
|
function isEnvelope(raw) {
|
|
5
18
|
return typeof raw === "object" && raw !== null && "v" in raw && typeof raw["v"] === "number" && "value" in raw;
|
|
6
19
|
}
|
|
@@ -24,33 +37,32 @@ function parseStorage(raw, opts) {
|
|
|
24
37
|
return opts.initial;
|
|
25
38
|
}
|
|
26
39
|
}
|
|
27
|
-
function
|
|
28
|
-
const storageKey = `basalt:${opts.key}`;
|
|
40
|
+
function writeEnvelope(storageKey, version, value) {
|
|
29
41
|
try {
|
|
30
|
-
const envelope = { v:
|
|
42
|
+
const envelope = { v: version, value };
|
|
31
43
|
window.localStorage.setItem(storageKey, JSON.stringify(envelope));
|
|
32
44
|
} catch {
|
|
33
45
|
}
|
|
34
46
|
}
|
|
47
|
+
function writeStorage(opts, next) {
|
|
48
|
+
writeEnvelope(`basalt:${opts.key}`, opts.version, next);
|
|
49
|
+
}
|
|
35
50
|
function createPersistedState(opts) {
|
|
36
51
|
const storageKey = `basalt:${opts.key}`;
|
|
37
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
38
|
-
let storageHandler = null;
|
|
39
52
|
const subscribe = (cb) => {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
53
|
+
const channel = channelFor(storageKey);
|
|
54
|
+
channel.listeners.add(cb);
|
|
55
|
+
if (channel.handler === null) {
|
|
56
|
+
channel.handler = (e) => {
|
|
57
|
+
if (e.key === storageKey) notify(storageKey);
|
|
46
58
|
};
|
|
47
|
-
window.addEventListener("storage",
|
|
59
|
+
window.addEventListener("storage", channel.handler);
|
|
48
60
|
}
|
|
49
61
|
return () => {
|
|
50
|
-
listeners.delete(cb);
|
|
51
|
-
if (listeners.size === 0 &&
|
|
52
|
-
window.removeEventListener("storage",
|
|
53
|
-
|
|
62
|
+
channel.listeners.delete(cb);
|
|
63
|
+
if (channel.listeners.size === 0 && channel.handler !== null) {
|
|
64
|
+
window.removeEventListener("storage", channel.handler);
|
|
65
|
+
channel.handler = null;
|
|
54
66
|
}
|
|
55
67
|
};
|
|
56
68
|
};
|
|
@@ -73,7 +85,7 @@ function createPersistedState(opts) {
|
|
|
73
85
|
const getServerSnapshot = () => opts.initial;
|
|
74
86
|
const setState = (next) => {
|
|
75
87
|
writeStorage(opts, next);
|
|
76
|
-
|
|
88
|
+
notify(storageKey);
|
|
77
89
|
};
|
|
78
90
|
const isServer = typeof window === "undefined";
|
|
79
91
|
return function usePersistedState() {
|
|
@@ -99,7 +111,13 @@ function readPersistedValue(key, version) {
|
|
|
99
111
|
return null;
|
|
100
112
|
}
|
|
101
113
|
}
|
|
114
|
+
function writePersistedValue(key, version, value) {
|
|
115
|
+
const storageKey = `basalt:${key}`;
|
|
116
|
+
writeEnvelope(storageKey, version, value);
|
|
117
|
+
notify(storageKey);
|
|
118
|
+
}
|
|
102
119
|
export {
|
|
103
120
|
createPersistedState,
|
|
104
|
-
readPersistedValue
|
|
121
|
+
readPersistedValue,
|
|
122
|
+
writePersistedValue
|
|
105
123
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persisted.test.d.ts","sourceRoot":"","sources":["../../src/state/persisted.test.ts"],"names":[],"mappings":""}
|
package/dist/state.d.ts
CHANGED
|
@@ -13,5 +13,5 @@
|
|
|
13
13
|
* `./state` export resolves to, and its named exports are pinned by `scripts/export-surface.json`.
|
|
14
14
|
*/
|
|
15
15
|
export { createPersistedState, type PersistedStateOptions, readPersistedValue, } from './state/persisted.js';
|
|
16
|
-
export { field, createLocalStore, type LocalStore, type FieldLane, type ResolvedLane, type ResolveLane, type RangeValue, type RangeParams, type RangePresets, type EnumField, type MultiField, type RangeField, type NumberField, type BooleanField, type StringField, type AnyField, type FieldValue, type FieldOption, type FieldHandle, type SearchValues, type StoredValues, type FieldCodec, type StoreEntry, type StoreCoreOptions, type StoreCore, resolveFieldCodec, createStoreCore, } from './state/fields.js';
|
|
16
|
+
export { field, createLocalStore, type LocalStore, type FieldLane, type ResolvedLane, type ResolveLane, type RangeValue, type RangeParams, type RangePresets, type RangeWindow, type RangeWindows, type FieldFallback, type FieldSetOptions, type EnumField, type MultiField, type RangeField, type NumberField, type BooleanField, type StringField, type AnyField, type FieldValue, type FieldOption, type FieldHandle, type SearchValues, type StoredValues, type FieldCodec, type StoreEntry, type StoreCoreOptions, type StoreCore, type FieldUse, type FieldWrite, resolveFieldCodec, createStoreCore, } from './state/fields.js';
|
|
17
17
|
//# sourceMappingURL=state.d.ts.map
|
package/dist/state.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EACL,oBAAoB,EACpB,KAAK,qBAAqB,EAC1B,kBAAkB,GACnB,MAAM,mBAAmB,CAAA;AAE1B,OAAO,EACL,KAAK,EACL,gBAAgB,EAChB,KAAK,UAAU,EAEf,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,QAAQ,EACb,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,YAAY,EAEjB,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACd,iBAAiB,EACjB,eAAe,GAChB,MAAM,gBAAgB,CAAA"}
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EACL,oBAAoB,EACpB,KAAK,qBAAqB,EAC1B,kBAAkB,GACnB,MAAM,mBAAmB,CAAA;AAE1B,OAAO,EACL,KAAK,EACL,gBAAgB,EAChB,KAAK,UAAU,EAEf,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,QAAQ,EACb,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,YAAY,EAEjB,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,UAAU,EACf,iBAAiB,EACjB,eAAe,GAChB,MAAM,gBAAgB,CAAA"}
|
package/dist/surfaces.d.ts
CHANGED
|
@@ -375,7 +375,7 @@ export declare const SURFACES: {
|
|
|
375
375
|
readonly guardKinds: readonly ["raw-selection-control"];
|
|
376
376
|
readonly pluginRules: readonly ["hand-rolled-filter", "control-outside-home", "control-size-literal", "responsive-twin"];
|
|
377
377
|
readonly advisoryLaws: readonly ["C1 as a cross-file law (a control placed in one file, its home declared in another)", "hand-rolled section headings (argo writes `<Text fw={600} size=\"sm\">` + children, which no AST heuristic matches without false positives)", "C11 — a table/list stating its count when it is not a BasaltDataTable", "C12 — one shape for refresh/sync (SyncButton); only the alias table sees a renamed copy"];
|
|
378
|
-
readonly description: "The control tier (docs/CONTROLS-SPEC.md §3): FilterSet (nowrap row + measured +N fold + the mobile Filters (n) sheet), RangeFilter/CompareFilter/SelectFilter/MultiSelectFilter/SearchFilter/ToggleFilter (each bound to a FieldHandle — no value/onChange/size, law C2/C5), ViewTabs, and the action/sync family (ActionGroup, OverflowMenu, SyncButton, BarAction/GlobalAction). Every control owns its own desktop/mobile swap in CSS (C9) and renders size=\"ctl\" internally. Resolves and renders with NO @mantine/dates installed — the custom date picker is injected through RangeFilter.customPicker from ./controls-dates.";
|
|
378
|
+
readonly description: "The control tier (docs/CONTROLS-SPEC.md §3): FilterSet (nowrap row + measured +N fold + the mobile Filters (n) sheet), RangeFilter/CompareFilter/SelectFilter/MultiSelectFilter/NumberFilter/SearchFilter/ToggleFilter (each bound to a FieldHandle — no value/onChange/size, law C2/C5; NumberFilter is the field.number lane, a radio list over `options` or a stepper without), ViewTabs, and the action/sync family (ActionGroup, OverflowMenu, SyncButton, BarAction/GlobalAction). Every control owns its own desktop/mobile swap in CSS (C9) and renders size=\"ctl\" internally. Resolves and renders with NO @mantine/dates installed — the custom date picker is injected through RangeFilter.customPicker from ./controls-dates.";
|
|
379
379
|
readonly optionalPeers: readonly [];
|
|
380
380
|
readonly forbiddenImports: readonly [];
|
|
381
381
|
};
|
package/dist/surfaces.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"surfaces.d.ts","sourceRoot":"","sources":["../src/surfaces.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"surfaces.d.ts","sourceRoot":"","sources":["../src/surfaces.ts"],"names":[],"mappings":"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"}
|
package/dist/surfaces.js
CHANGED
|
@@ -346,8 +346,9 @@ const SURFACES = {
|
|
|
346
346
|
// The text lane of law C1 — the plugin's `control-outside-home` seen through a 12-line host-tag
|
|
347
347
|
// window, since a regex scan has no ancestry. Same law, same wave, same promotion.
|
|
348
348
|
guardKinds: ["raw-selection-control"],
|
|
349
|
-
// The control tier itself (laws C1/C3/C5/C9). `hand-rolled-filter
|
|
350
|
-
//
|
|
349
|
+
// The control tier itself (laws C1/C3/C5/C9). `hand-rolled-filter`, `control-size-literal` and
|
|
350
|
+
// `responsive-twin` all ship `error`; `control-outside-home` is the one grace entry left in
|
|
351
|
+
// `PLUGIN_RULE_GRACE`, re-dated to 1.28.0 against the wave-7 measurement (see its `why`).
|
|
351
352
|
// The budget rules live on `.` with `PageBar` itself.
|
|
352
353
|
pluginRules: [
|
|
353
354
|
"hand-rolled-filter",
|
|
@@ -363,7 +364,7 @@ const SURFACES = {
|
|
|
363
364
|
"C11 \u2014 a table/list stating its count when it is not a BasaltDataTable",
|
|
364
365
|
"C12 \u2014 one shape for refresh/sync (SyncButton); only the alias table sees a renamed copy"
|
|
365
366
|
],
|
|
366
|
-
description: 'The control tier (docs/CONTROLS-SPEC.md \xA73): FilterSet (nowrap row + measured +N fold + the mobile Filters (n) sheet), RangeFilter/CompareFilter/SelectFilter/MultiSelectFilter/SearchFilter/ToggleFilter (each bound to a FieldHandle \u2014 no value/onChange/size, law C2/C5), ViewTabs, and the action/sync family (ActionGroup, OverflowMenu, SyncButton, BarAction/GlobalAction). Every control owns its own desktop/mobile swap in CSS (C9) and renders size="ctl" internally. Resolves and renders with NO @mantine/dates installed \u2014 the custom date picker is injected through RangeFilter.customPicker from ./controls-dates.',
|
|
367
|
+
description: 'The control tier (docs/CONTROLS-SPEC.md \xA73): FilterSet (nowrap row + measured +N fold + the mobile Filters (n) sheet), RangeFilter/CompareFilter/SelectFilter/MultiSelectFilter/NumberFilter/SearchFilter/ToggleFilter (each bound to a FieldHandle \u2014 no value/onChange/size, law C2/C5; NumberFilter is the field.number lane, a radio list over `options` or a stepper without), ViewTabs, and the action/sync family (ActionGroup, OverflowMenu, SyncButton, BarAction/GlobalAction). Every control owns its own desktop/mobile swap in CSS (C9) and renders size="ctl" internally. Resolves and renders with NO @mantine/dates installed \u2014 the custom date picker is injected through RangeFilter.customPicker from ./controls-dates.',
|
|
367
368
|
optionalPeers: [],
|
|
368
369
|
forbiddenImports: []
|
|
369
370
|
},
|
|
@@ -20,10 +20,40 @@ export type WidgetHeaderProps = {
|
|
|
20
20
|
/** Pre-formatted metric value (mono, hero size). Renders on its own row with `delta`, directly
|
|
21
21
|
* under the title row — never inline with the title. */
|
|
22
22
|
value?: string;
|
|
23
|
+
/**
|
|
24
|
+
* The value's unit, rendered immediately after it on the hero row: muted, mono, `--vx-text-sm`.
|
|
25
|
+
*
|
|
26
|
+
* It exists because `value` is a pre-formatted `string` and a KPI's unit is not part of the
|
|
27
|
+
* number: `412` and `TSS` are two facts at two weights, and jamming them into one string paints
|
|
28
|
+
* the unit at the hero's 24px — which reads as a second numeral. `subtitle` was the only place a
|
|
29
|
+
* unit could go before this, and a unit on its own line under the value is a different claim (the
|
|
30
|
+
* BASIS: `7-day rolling`), so cards using both had to pick one.
|
|
31
|
+
*
|
|
32
|
+
* Never a substitute for the number's own formatting: a thousands separator, a currency symbol or
|
|
33
|
+
* a `%` belong in `value`, where they are part of how the numeral reads.
|
|
34
|
+
*/
|
|
35
|
+
unit?: string;
|
|
23
36
|
/** Signed delta rendered via `DeltaBadge`, on the same hero-metric row as `value`. */
|
|
24
37
|
delta?: number;
|
|
25
38
|
/** Comparison timeframe forwarded to `DeltaBadge` (e.g. `MoM`). */
|
|
26
39
|
deltaPeriod?: string;
|
|
40
|
+
/**
|
|
41
|
+
* Formats `delta` into the chip's label — forwarded to `DeltaBadge`'s `format`. Defaults to
|
|
42
|
+
* `${Math.abs(delta).toFixed(1)}%`.
|
|
43
|
+
*
|
|
44
|
+
* It exists because not every delta is a percentage: a pace card's trend is `0:12 /km` and a
|
|
45
|
+
* throughput card's is `0.3 km/h`, and the default printed both as a percent — a wrong unit on a
|
|
46
|
+
* KPI, which is worse than no chip. A FUNCTION rather than a label string, so the number stays the
|
|
47
|
+
* number: `delta` still drives the tone and the glyph, and there is exactly one place the sign is
|
|
48
|
+
* decided.
|
|
49
|
+
*
|
|
50
|
+
* The value arrives SIGNED, so a formatter that wants to print the sign itself (`−0:12 /km`) can —
|
|
51
|
+
* pair it with `deltaGlyph={false}` so the ▼ does not say the same thing twice.
|
|
52
|
+
*/
|
|
53
|
+
deltaFormat?: (delta: number) => string;
|
|
54
|
+
/** Render `DeltaBadge`'s ▲/▼ glyph. Defaults to `true`; a zero delta never shows one. Pass
|
|
55
|
+
* `false` when `deltaFormat` prints the sign itself. */
|
|
56
|
+
deltaGlyph?: boolean;
|
|
27
57
|
/** Trend visual slot, rendered below the metric row. Carries `data-placement="right"` so a
|
|
28
58
|
* wave-3 composer's `bleed` layout can select it. */
|
|
29
59
|
sparkline?: ReactNode;
|
|
@@ -33,5 +63,5 @@ export type WidgetHeaderProps = {
|
|
|
33
63
|
/** Right-aligned slot (`margin-inline-start: auto`) — the C1/C5 home for header actions. */
|
|
34
64
|
actions?: ReactNode;
|
|
35
65
|
};
|
|
36
|
-
export declare function WidgetHeader({ tier, title, icon, subtitle, info, value, delta, deltaPeriod, sparkline, count, actions, }: WidgetHeaderProps): import("react").JSX.Element;
|
|
66
|
+
export declare function WidgetHeader({ tier, title, icon, subtitle, info, value, unit, delta, deltaPeriod, deltaFormat, deltaGlyph, sparkline, count, actions, }: WidgetHeaderProps): import("react").JSX.Element;
|
|
37
67
|
//# sourceMappingURL=widget-header.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"widget-header.d.ts","sourceRoot":"","sources":["../../src/widget-header/widget-header.tsx"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAKtC,MAAM,MAAM,iBAAiB,GAAG;IAC9B;iDAC6C;IAC7C,IAAI,EAAE,SAAS,GAAG,QAAQ,CAAA;IAC1B,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAA;IACb,iGAAiG;IACjG,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,wDAAwD;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;4DACwD;IACxD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,sFAAsF;IACtF,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,mEAAmE;IACnE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;yDACqD;IACrD,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB;2EACuE;IACvE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,4FAA4F;IAC5F,OAAO,CAAC,EAAE,SAAS,CAAA;CACpB,CAAA;AAyDD,wBAAgB,YAAY,CAAC,EAC3B,IAAI,EACJ,KAAK,EACL,IAAI,EACJ,QAAQ,EACR,IAAI,EACJ,KAAK,EACL,KAAK,EACL,WAAW,EACX,SAAS,EACT,KAAK,EACL,OAAO,GACR,EAAE,iBAAiB,+
|
|
1
|
+
{"version":3,"file":"widget-header.d.ts","sourceRoot":"","sources":["../../src/widget-header/widget-header.tsx"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAKtC,MAAM,MAAM,iBAAiB,GAAG;IAC9B;iDAC6C;IAC7C,IAAI,EAAE,SAAS,GAAG,QAAQ,CAAA;IAC1B,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAA;IACb,iGAAiG;IACjG,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,wDAAwD;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;4DACwD;IACxD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;;;;;;;;;OAWG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,sFAAsF;IACtF,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,mEAAmE;IACnE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;;;;;;;;;OAYG;IACH,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IACvC;4DACwD;IACxD,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;yDACqD;IACrD,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB;2EACuE;IACvE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,4FAA4F;IAC5F,OAAO,CAAC,EAAE,SAAS,CAAA;CACpB,CAAA;AAyDD,wBAAgB,YAAY,CAAC,EAC3B,IAAI,EACJ,KAAK,EACL,IAAI,EACJ,QAAQ,EACR,IAAI,EACJ,KAAK,EACL,IAAI,EACJ,KAAK,EACL,WAAW,EACX,WAAW,EACX,UAAU,EACV,SAAS,EACT,KAAK,EACL,OAAO,GACR,EAAE,iBAAiB,+BA6CnB"}
|
|
@@ -47,8 +47,11 @@ function WidgetHeader({
|
|
|
47
47
|
subtitle,
|
|
48
48
|
info,
|
|
49
49
|
value,
|
|
50
|
+
unit,
|
|
50
51
|
delta,
|
|
51
52
|
deltaPeriod,
|
|
53
|
+
deltaFormat,
|
|
54
|
+
deltaGlyph,
|
|
52
55
|
sparkline,
|
|
53
56
|
count,
|
|
54
57
|
actions
|
|
@@ -66,7 +69,16 @@ function WidgetHeader({
|
|
|
66
69
|
] }),
|
|
67
70
|
(value !== void 0 || delta !== void 0) && /* @__PURE__ */ jsxs("div", { className: classes.metrics, children: [
|
|
68
71
|
value !== void 0 && /* @__PURE__ */ jsx("span", { className: classes.value, children: value }),
|
|
69
|
-
|
|
72
|
+
value !== void 0 && unit !== void 0 && /* @__PURE__ */ jsx("span", { className: classes.unit, children: unit }),
|
|
73
|
+
delta !== void 0 && /* @__PURE__ */ jsx(
|
|
74
|
+
DeltaBadge,
|
|
75
|
+
{
|
|
76
|
+
value: delta,
|
|
77
|
+
period: deltaPeriod,
|
|
78
|
+
...deltaFormat !== void 0 && { format: deltaFormat },
|
|
79
|
+
...deltaGlyph !== void 0 && { withGlyph: deltaGlyph }
|
|
80
|
+
}
|
|
81
|
+
)
|
|
70
82
|
] }),
|
|
71
83
|
subtitle !== void 0 && /* @__PURE__ */ jsx("span", { className: classes.subtitle, children: subtitle }),
|
|
72
84
|
sparkline !== void 0 && /* @__PURE__ */ jsx("div", { className: classes.sparkline, "data-placement": "right", children: sparkline })
|
|
@@ -199,6 +199,21 @@
|
|
|
199
199
|
color: var(--vx-ink);
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
+
/*
|
|
203
|
+
* The unit beside the hero value. Mono like the number it qualifies (so `412 TSS` sits on one
|
|
204
|
+
* baseline grid), `--vx-text-sm` and `--vx-muted` so it never competes with the numeral — the whole
|
|
205
|
+
* point is that a unit is the SMALL half of the pair. `flex-shrink: 0`: the value is the row's
|
|
206
|
+
* shrinkable half (see `.value`), and a unit that ellipsized to `TS…` would state nothing.
|
|
207
|
+
*/
|
|
208
|
+
.unit {
|
|
209
|
+
flex-shrink: 0;
|
|
210
|
+
align-self: baseline;
|
|
211
|
+
font-family: var(--basalt-font-mono);
|
|
212
|
+
font-size: var(--vx-text-sm);
|
|
213
|
+
font-weight: 500;
|
|
214
|
+
color: var(--vx-muted);
|
|
215
|
+
}
|
|
216
|
+
|
|
202
217
|
/* The C1/C5 home for header actions — always pinned right, regardless of what else is present. */
|
|
203
218
|
.actions {
|
|
204
219
|
display: inline-flex;
|
package/llms.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# basalt-ui v1.
|
|
1
|
+
# basalt-ui v1.27.0
|
|
2
2
|
# Opinionated framework for Mantine-based React apps — theme, app shell, visx charts, toolchain presets
|
|
3
3
|
#
|
|
4
4
|
# basalt-ui is an opinionated framework for Mantine-based React apps.
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
# ./tokens.css subpath. Guide: docs/FRAMEWORK-FREE.md in the repo.
|
|
20
20
|
|
|
21
21
|
Package: basalt-ui
|
|
22
|
-
Version: 1.
|
|
22
|
+
Version: 1.27.0
|
|
23
23
|
|
|
24
24
|
## basalt-ui
|
|
25
25
|
Import: import { ... } from 'basalt-ui'
|
|
@@ -135,7 +135,7 @@ OptionalPeers: shiki@^4.3.0, @shikijs/langs@^4.3.1, @shikijs/themes@^4.3.1, beau
|
|
|
135
135
|
|
|
136
136
|
## basalt-ui/controls
|
|
137
137
|
Import: import { ... } from 'basalt-ui/controls'
|
|
138
|
-
Description: The control tier (docs/CONTROLS-SPEC.md §3): FilterSet (nowrap row + measured +N fold + the mobile Filters (n) sheet), RangeFilter/CompareFilter/SelectFilter/MultiSelectFilter/SearchFilter/ToggleFilter (each bound to a FieldHandle — no value/onChange/size, law C2/C5), ViewTabs, and the action/sync family (ActionGroup, OverflowMenu, SyncButton, BarAction/GlobalAction). Every control owns its own desktop/mobile swap in CSS (C9) and renders size="ctl" internally. Resolves and renders with NO @mantine/dates installed — the custom date picker is injected through RangeFilter.customPicker from ./controls-dates.
|
|
138
|
+
Description: The control tier (docs/CONTROLS-SPEC.md §3): FilterSet (nowrap row + measured +N fold + the mobile Filters (n) sheet), RangeFilter/CompareFilter/SelectFilter/MultiSelectFilter/NumberFilter/SearchFilter/ToggleFilter (each bound to a FieldHandle — no value/onChange/size, law C2/C5; NumberFilter is the field.number lane, a radio list over `options` or a stepper without), ViewTabs, and the action/sync family (ActionGroup, OverflowMenu, SyncButton, BarAction/GlobalAction). Every control owns its own desktop/mobile swap in CSS (C9) and renders size="ctl" internally. Resolves and renders with NO @mantine/dates installed — the custom date picker is injected through RangeFilter.customPicker from ./controls-dates.
|
|
139
139
|
Layer: mantine-coupled
|
|
140
140
|
Rule: basalt-controls
|
|
141
141
|
|