@ultimat3/ui 16.0.0 → 18.0.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ultimat3/ui",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "18.0.0",
|
|
4
4
|
"description": "SolidJS design system: semantic design tokens, dark/RTL-ready SCSS modules, a11y primitives",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"LICENSE"
|
|
36
36
|
],
|
|
37
37
|
"engines": {
|
|
38
|
-
"bun": ">=1.
|
|
38
|
+
"bun": ">=1.4.0"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
41
|
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
@@ -44,10 +44,10 @@
|
|
|
44
44
|
"icons": "bun run src/icons/build-icons.ts"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@ultimat3/core": "
|
|
48
|
-
"@ultimat3/i18n": "
|
|
49
|
-
"@ultimat3/money": "
|
|
50
|
-
"@ultimat3/time": "
|
|
47
|
+
"@ultimat3/core": "18.0.0",
|
|
48
|
+
"@ultimat3/i18n": "18.0.0",
|
|
49
|
+
"@ultimat3/money": "18.0.0",
|
|
50
|
+
"@ultimat3/time": "18.0.0"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
53
|
"solid-js": "^1.9.0"
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// real list always has (loading, error, empty, data). The error state renders an
|
|
3
3
|
// UltimateError with the same code/cause/fix strings the terminal prints.
|
|
4
4
|
|
|
5
|
+
import { finiteCount } from '@ultimat3/core';
|
|
5
6
|
import type { JSX } from 'solid-js';
|
|
6
7
|
import { ariaBool } from '../a11y';
|
|
7
8
|
import { cx } from '../cx';
|
|
@@ -61,7 +62,13 @@ export function DataTable<Row>(props: DataTableProps<Row>): JSX.Element {
|
|
|
61
62
|
|
|
62
63
|
const body = (): JSX.Element => {
|
|
63
64
|
if (props.loading === true) {
|
|
64
|
-
|
|
65
|
+
// `Array.from({ length: NaN })` is `[]`, so a busy tbody with no placeholders in it is the
|
|
66
|
+
// collapsed layout this state exists to prevent, reported as healthy; `Infinity` asks for
|
|
67
|
+
// 2^53 - 1 elements and dies with a bare, uncoded `RangeError` out of a render. `??` reaches
|
|
68
|
+
// neither, because `NaN` is not nullish. 0 stays legal: a loading table with no placeholders
|
|
69
|
+
// is a choice, and it is a visible one.
|
|
70
|
+
const placeholders = finiteCount('DataTable', 'skeletonRows', props.skeletonRows ?? 5, 0);
|
|
71
|
+
return Array.from({ length: placeholders }, () => (
|
|
65
72
|
<tr>
|
|
66
73
|
{props.columns.map(() => (
|
|
67
74
|
<td>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Loading placeholder. Sized by the caller so the real content lands in the same
|
|
2
2
|
// box — a skeleton that changes size on load is just a slower layout shift.
|
|
3
3
|
|
|
4
|
+
import { finiteCount } from '@ultimat3/core';
|
|
4
5
|
import type { JSX } from 'solid-js';
|
|
5
6
|
import { cx } from '../cx';
|
|
6
7
|
import styles from './Skeleton.module.scss';
|
|
@@ -16,6 +17,12 @@ export interface SkeletonProps {
|
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
export function Skeleton(props: SkeletonProps): JSX.Element {
|
|
20
|
+
// `Math.max` IS NOT A VALIDATOR — it propagates `NaN`, so `Math.max(1, NaN)` is `NaN` and
|
|
21
|
+
// `Array.from({ length: NaN })` is `[]`: the placeholder that holds the box while content loads
|
|
22
|
+
// rendered nothing at all, which is the layout shift it exists to prevent. The clamp stays, and
|
|
23
|
+
// it is the screened value it clamps — `lines: 0` still means one line, exactly as before.
|
|
24
|
+
const lines = (): number => Math.max(1, finiteCount('Skeleton', 'lines', props.lines ?? 1, 0));
|
|
25
|
+
|
|
19
26
|
const one = (index: number): JSX.Element => (
|
|
20
27
|
<span
|
|
21
28
|
class={cx(styles['skeleton'], styles[`shape-${props.shape ?? 'text'}`])}
|
|
@@ -28,7 +35,7 @@ export function Skeleton(props: SkeletonProps): JSX.Element {
|
|
|
28
35
|
|
|
29
36
|
return (
|
|
30
37
|
<span aria-hidden="true" class={cx(styles['group'], props.class)}>
|
|
31
|
-
{Array.from({ length:
|
|
38
|
+
{Array.from({ length: lines() }, (_unused, index) => one(index))}
|
|
32
39
|
</span>
|
|
33
40
|
);
|
|
34
41
|
}
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
// every debounced keystroke, so a no-JS form round-trip and a live filter narrow the list the
|
|
3
3
|
// same way — two matchers would eventually disagree, and the user would see the list flicker.
|
|
4
4
|
|
|
5
|
+
import { finiteCount } from '@ultimat3/core';
|
|
6
|
+
|
|
5
7
|
export interface ComboboxOption {
|
|
6
8
|
/** The text the field takes when the suggestion is picked — and what the form submits. */
|
|
7
9
|
value: string;
|
|
@@ -36,8 +38,14 @@ export function filterOptions(
|
|
|
36
38
|
query = '',
|
|
37
39
|
limit: number = COMBOBOX_LIMIT,
|
|
38
40
|
): readonly ComboboxOption[] {
|
|
41
|
+
// A BARE PARAMETER DEFAULT, which `??` never guards and `scripts/finite-bounds.ts` cannot see:
|
|
42
|
+
// `slice(0, NaN)` is `[]`, so every suggestion vanishes and `<Combobox>` renders "no results" for
|
|
43
|
+
// a list that matched. `Infinity` is the mirror — every option rendered, out of the function
|
|
44
|
+
// whose own doc says long lists are a scroll, not an answer. 0 stays legal: it renders nothing,
|
|
45
|
+
// on purpose and visibly.
|
|
46
|
+
const cap = finiteCount('filterOptions', 'limit', limit, 0);
|
|
39
47
|
const needle = normalizeQuery(query);
|
|
40
|
-
if (needle === '') return options.slice(0,
|
|
48
|
+
if (needle === '') return options.slice(0, cap);
|
|
41
49
|
|
|
42
50
|
const prefix: ComboboxOption[] = [];
|
|
43
51
|
const contains: ComboboxOption[] = [];
|
|
@@ -46,5 +54,5 @@ export function filterOptions(
|
|
|
46
54
|
if (text.startsWith(needle)) prefix.push(option);
|
|
47
55
|
else if (text.includes(needle)) contains.push(option);
|
|
48
56
|
}
|
|
49
|
-
return [...prefix, ...contains].slice(0,
|
|
57
|
+
return [...prefix, ...contains].slice(0, cap);
|
|
50
58
|
}
|