@urbicon-ui/blocks 6.26.3 → 6.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/dist/primitives/Card/card.variants.js +8 -1
- package/dist/primitives/Combobox/Combobox.svelte +24 -3
- package/dist/primitives/Combobox/index.d.ts +13 -4
- package/dist/primitives/Pagination/Pagination.svelte +27 -1
- package/dist/primitives/Pagination/index.d.ts +9 -1
- package/dist/primitives/Select/Select.svelte +12 -2
- package/package.json +3 -3
|
@@ -89,7 +89,14 @@ export const cardVariants = tv({
|
|
|
89
89
|
},
|
|
90
90
|
elementType: {
|
|
91
91
|
button: {
|
|
92
|
-
|
|
92
|
+
// `[font:inherit]` (arbitrary property), NOT `font-inherit`: Tailwind
|
|
93
|
+
// v4 has no `font-inherit` utility (no `--font-inherit` theme key), so
|
|
94
|
+
// that class emitted no rule and a clickable Card kept the UA button
|
|
95
|
+
// font instead of inheriting the surrounding type. Same utility gap as
|
|
96
|
+
// Button's `[gap:inherit]` (Codeberg #21); found ground-truthing the
|
|
97
|
+
// variants-lint theme-existence guard (`font-` itself stays unguarded —
|
|
98
|
+
// family keys are legitimately consumer-supplied).
|
|
99
|
+
base: 'border-none [font:inherit] text-left cursor-pointer'
|
|
93
100
|
},
|
|
94
101
|
a: {
|
|
95
102
|
base: 'no-underline text-inherit'
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
queryFn,
|
|
36
36
|
debounceMs = 250,
|
|
37
37
|
loadingText = 'Loading…',
|
|
38
|
+
onError,
|
|
38
39
|
tier,
|
|
39
40
|
variant = 'outlined',
|
|
40
41
|
size = 'md',
|
|
@@ -154,12 +155,21 @@
|
|
|
154
155
|
// The dev-warn is gated to *sync* mode: with `queryFn`, a pre-bound value that
|
|
155
156
|
// isn't in the current result set is expected, not a bug (there is no API to
|
|
156
157
|
// seed labels for it), so warning there would cry wolf on a legitimate pattern.
|
|
157
|
-
// See technical-debt (async pre-selected labels
|
|
158
|
+
// See technical-debt (async pre-selected labels).
|
|
159
|
+
//
|
|
160
|
+
// Warn dedup: `selectedTags` recomputes on every fresh `options` array a
|
|
161
|
+
// parent re-render passes in (the common `options={items.map(…)}` idiom), so
|
|
162
|
+
// an unguarded warn would re-fire per recompute. A plain Set — deliberately
|
|
163
|
+
// outside the reactive graph, so adding to it never invalidates the derived —
|
|
164
|
+
// makes it one warn per orphan value for the instance's lifetime (mirrors
|
|
165
|
+
// Select).
|
|
166
|
+
const warnedOrphanValues = new Set<T>();
|
|
158
167
|
const selectedTags = $derived.by<ComboboxOption<T>[]>(() =>
|
|
159
168
|
selectedValues.map((v) => {
|
|
160
169
|
const found = allOptions.find((o) => o.value === v) ?? tagCache.get(v);
|
|
161
170
|
if (found) return found;
|
|
162
|
-
if (!queryFn && import.meta.env?.DEV) {
|
|
171
|
+
if (!queryFn && import.meta.env?.DEV && !warnedOrphanValues.has(v)) {
|
|
172
|
+
warnedOrphanValues.add(v);
|
|
163
173
|
console.warn(
|
|
164
174
|
`[Combobox] value ${JSON.stringify(v)} has no matching option — tag falls back to its raw value.`
|
|
165
175
|
);
|
|
@@ -261,7 +271,18 @@
|
|
|
261
271
|
.catch((err: unknown) => {
|
|
262
272
|
if (controller.signal.aborted || (err as { name?: string })?.name === 'AbortError')
|
|
263
273
|
return;
|
|
264
|
-
|
|
274
|
+
// Failure contract: the loading state ends (`finally`) and the
|
|
275
|
+
// previous — now stale — options stay in place (no result-set
|
|
276
|
+
// clobber; a UI error slot remains an open debt). The rejection is
|
|
277
|
+
// handed to `onError`; without one it is surfaced DEV-only instead
|
|
278
|
+
// of vanishing silently (ConfirmDialog onConfirm precedent). A
|
|
279
|
+
// throwing `onError` is a consumer bug and deliberately escapes
|
|
280
|
+
// (fail-loud, mirrors createCronRunner).
|
|
281
|
+
if (onError) {
|
|
282
|
+
onError(err);
|
|
283
|
+
} else if (import.meta.env?.DEV) {
|
|
284
|
+
console.warn('[Combobox] queryFn rejected:', err);
|
|
285
|
+
}
|
|
265
286
|
})
|
|
266
287
|
.finally(() => {
|
|
267
288
|
if (!controller.signal.aborted) loading = false;
|
|
@@ -56,16 +56,25 @@ interface ComboboxBaseProps<T extends SelectValue = string> extends ComboboxVari
|
|
|
56
56
|
* by {@link debounceMs} — on each query change, replacing the option list with
|
|
57
57
|
* the resolved result. The `AbortSignal` is aborted when a newer query
|
|
58
58
|
* supersedes an in-flight request, so a slow stale response never clobbers a
|
|
59
|
-
* fresh one. Aborted rejections are swallowed; other rejections
|
|
60
|
-
* previous options in place
|
|
61
|
-
* in this mode. The selected
|
|
62
|
-
* sets that no longer
|
|
59
|
+
* fresh one. Aborted rejections are swallowed; other rejections end the
|
|
60
|
+
* loading state, leave the previous options in place, and are reported via
|
|
61
|
+
* `onError`. `options`/`groups` are ignored in this mode. The selected
|
|
62
|
+
* option's label is cached so it survives result sets that no longer
|
|
63
|
+
* contain it.
|
|
63
64
|
*/
|
|
64
65
|
queryFn?: (query: string, signal: AbortSignal) => Promise<ComboboxOption<T>[]>;
|
|
65
66
|
/** Debounce applied to `queryFn` in milliseconds. @default 250 */
|
|
66
67
|
debounceMs?: number;
|
|
67
68
|
/** Text shown in the listbox while an async `queryFn` request is in flight. @default 'Loading…' */
|
|
68
69
|
loadingText?: string;
|
|
70
|
+
/**
|
|
71
|
+
* Fired when `queryFn` rejects (aborted / superseded requests are ignored).
|
|
72
|
+
* The loading state ends and the previous option list stays in place — use
|
|
73
|
+
* this to surface the failure (toast, inline message). Without a handler
|
|
74
|
+
* the rejection is logged DEV-only (`console.warn`) and swallowed in
|
|
75
|
+
* production; it never escapes as an unhandled promise rejection.
|
|
76
|
+
*/
|
|
77
|
+
onError?: (error: unknown) => void;
|
|
69
78
|
/** Show a clear button when a value is selected. Click or press Escape to reset. @default false */
|
|
70
79
|
clearable?: boolean;
|
|
71
80
|
/** Disable the entire combobox. @default false */
|
|
@@ -20,7 +20,10 @@
|
|
|
20
20
|
intent = 'primary',
|
|
21
21
|
tier,
|
|
22
22
|
visiblePages = 7,
|
|
23
|
-
|
|
23
|
+
// Undefaulted so the DEV no-op warning below can tell an explicit
|
|
24
|
+
// `showFirstLast` from the default; `showFirstLast` itself (derived) keeps
|
|
25
|
+
// the documented `true` default for every render-path consumer.
|
|
26
|
+
showFirstLast: showFirstLastProp,
|
|
24
27
|
showPreviousNext = true,
|
|
25
28
|
showNumbers = true,
|
|
26
29
|
showInfo = false,
|
|
@@ -52,6 +55,29 @@
|
|
|
52
55
|
const blocksConfig = getBlocksConfig();
|
|
53
56
|
const unstyled = $derived(unstyledProp || blocksConfig?.unstyled || false);
|
|
54
57
|
|
|
58
|
+
const showFirstLast = $derived(showFirstLastProp ?? true);
|
|
59
|
+
|
|
60
|
+
// DEV fail-loud: First/Last are redundancy-gated to the number window (they
|
|
61
|
+
// render only beside a start/end ellipsis), so with `showNumbers={false}` an
|
|
62
|
+
// explicitly-set `showFirstLast` is a silent no-op. Surface that once per
|
|
63
|
+
// instance — the coupling itself is a settled decision (see the
|
|
64
|
+
// `showFirstLast` JSDoc), only its silence was the bug. Plain flag, not
|
|
65
|
+
// `$state`: the warn must not feed back into the reactive graph.
|
|
66
|
+
let warnedFirstLastWithoutNumbers = false;
|
|
67
|
+
$effect(() => {
|
|
68
|
+
if (
|
|
69
|
+
import.meta.env?.DEV &&
|
|
70
|
+
!warnedFirstLastWithoutNumbers &&
|
|
71
|
+
showFirstLastProp === true &&
|
|
72
|
+
!showNumbers
|
|
73
|
+
) {
|
|
74
|
+
warnedFirstLastWithoutNumbers = true;
|
|
75
|
+
console.warn(
|
|
76
|
+
'[Pagination] showFirstLast has no effect while showNumbers is false — First/Last only render beside the number window’s ellipsis. Drop showFirstLast or re-enable showNumbers.'
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
55
81
|
const variantProps: PaginationVariants = $derived({
|
|
56
82
|
layout,
|
|
57
83
|
size,
|
|
@@ -68,7 +68,15 @@ export interface PaginationProps extends Omit<PaginationVariants, 'disabled' | '
|
|
|
68
68
|
tier?: InteractiveTier;
|
|
69
69
|
/** Maximum number of page buttons shown between the ellipsis indicators. */
|
|
70
70
|
visiblePages?: number;
|
|
71
|
-
/**
|
|
71
|
+
/**
|
|
72
|
+
* Show "First" / "Last" boundary buttons when the current page is far from
|
|
73
|
+
* the edges. Deliberately redundancy-gated to the number window: the buttons
|
|
74
|
+
* render only beside a start/end ellipsis, so without `showNumbers` there is
|
|
75
|
+
* no number window, no ellipsis — and no First/Last buttons. That coupling is
|
|
76
|
+
* intentional (a compact prev/next-only bar stays compact), not a bug.
|
|
77
|
+
* Setting `showFirstLast` explicitly while `showNumbers` is `false` warns
|
|
78
|
+
* once per instance in dev. @default true
|
|
79
|
+
*/
|
|
72
80
|
showFirstLast?: boolean;
|
|
73
81
|
/** Show "Previous" / "Next" navigation buttons. */
|
|
74
82
|
showPreviousNext?: boolean;
|
|
@@ -177,14 +177,23 @@
|
|
|
177
177
|
* dev so the consumer notices the orphan. The orphan is also filtered out
|
|
178
178
|
* of the hidden form input, so the submitted form stays consistent with
|
|
179
179
|
* what the trigger shows.
|
|
180
|
+
*
|
|
181
|
+
* Warn dedup: `selectedOptions` recomputes on every fresh `options` array a
|
|
182
|
+
* parent re-render passes in (the common `options={items.map(…)}` idiom), so
|
|
183
|
+
* an unguarded warn would re-fire per recompute. A plain Set — deliberately
|
|
184
|
+
* outside the reactive graph, so adding to it never invalidates the derived —
|
|
185
|
+
* makes it one warn per orphan value for the instance's lifetime (mirrors
|
|
186
|
+
* Combobox).
|
|
180
187
|
*/
|
|
188
|
+
const warnedOrphanValues = new Set<unknown>();
|
|
181
189
|
const selectedOptions = $derived.by((): SelectOption<T>[] => {
|
|
182
190
|
if (multiple) {
|
|
183
191
|
const values = Array.isArray(value) ? value : [];
|
|
184
192
|
return values
|
|
185
193
|
.map((v) => {
|
|
186
194
|
const found = allOptions.find((o) => o.value === v);
|
|
187
|
-
if (!found && import.meta.env?.DEV) {
|
|
195
|
+
if (!found && import.meta.env?.DEV && !warnedOrphanValues.has(v)) {
|
|
196
|
+
warnedOrphanValues.add(v);
|
|
188
197
|
console.warn(
|
|
189
198
|
`[Select] value ${JSON.stringify(v)} has no matching option — dropped from selection.`
|
|
190
199
|
);
|
|
@@ -195,7 +204,8 @@
|
|
|
195
204
|
}
|
|
196
205
|
if (value === null || value === undefined) return [];
|
|
197
206
|
const found = allOptions.find((o) => o.value === value);
|
|
198
|
-
if (!found && import.meta.env?.DEV) {
|
|
207
|
+
if (!found && import.meta.env?.DEV && !warnedOrphanValues.has(value)) {
|
|
208
|
+
warnedOrphanValues.add(value);
|
|
199
209
|
console.warn(
|
|
200
210
|
`[Select] value ${JSON.stringify(value)} has no matching option — trigger will fall back to placeholder.`
|
|
201
211
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@urbicon-ui/blocks",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.27.0",
|
|
4
4
|
"description": "Svelte 5 UI component library with Tailwind CSS 4, OKLCH design tokens and zero runtime dependencies",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -92,8 +92,8 @@
|
|
|
92
92
|
"@sveltejs/package": "^2.5.8",
|
|
93
93
|
"@sveltejs/vite-plugin-svelte": "^7.0.0",
|
|
94
94
|
"@tailwindcss/vite": "^4.3.1",
|
|
95
|
-
"@urbicon-ui/i18n": "6.
|
|
96
|
-
"@urbicon-ui/shared-types": "6.
|
|
95
|
+
"@urbicon-ui/i18n": "6.27.0",
|
|
96
|
+
"@urbicon-ui/shared-types": "6.27.0",
|
|
97
97
|
"prettier": "^3.8.4",
|
|
98
98
|
"prettier-plugin-svelte": "^4.1.1",
|
|
99
99
|
"prettier-plugin-tailwindcss": "^0.8.0",
|