@urbicon-ui/blocks 6.31.0 → 6.33.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.
@@ -62,14 +62,18 @@
62
62
  const agendaKey = $derived(`${ctx.displayedYear}-${ctx.displayedMonth}`);
63
63
 
64
64
  function handleKeydown(e: KeyboardEvent) {
65
+ // Direction-gated like the swipes and header arrows (the DateGridScaffold
66
+ // pattern): an arrow key at the bound is inert — no navDirection flip, no
67
+ // clamped no-op onMonthChange emit. Disabled calendars ignore keys entirely.
68
+ if (ctx.disabled) return;
65
69
  switch (e.key) {
66
70
  case 'ArrowLeft':
67
71
  e.preventDefault();
68
- ctx.navigate(-1);
72
+ if (ctx.canGoBack) ctx.navigate(-1);
69
73
  break;
70
74
  case 'ArrowRight':
71
75
  e.preventDefault();
72
- ctx.navigate(1);
76
+ if (ctx.canGoForward) ctx.navigate(1);
73
77
  break;
74
78
  }
75
79
  }
@@ -33,14 +33,18 @@
33
33
  const totalEventCount = $derived(eventsWithInfo.length);
34
34
 
35
35
  function handleKeydown(e: KeyboardEvent) {
36
+ // Direction-gated like the swipes and header arrows (the DateGridScaffold
37
+ // pattern): an arrow key at the bound is inert — no navDirection flip, no
38
+ // clamped no-op onDayChange emit. Disabled calendars ignore keys entirely.
39
+ if (ctx.disabled) return;
36
40
  switch (e.key) {
37
41
  case 'ArrowLeft':
38
42
  e.preventDefault();
39
- ctx.navigate(-1);
43
+ if (ctx.canGoBack) ctx.navigate(-1);
40
44
  break;
41
45
  case 'ArrowRight':
42
46
  e.preventDefault();
43
- ctx.navigate(1);
47
+ if (ctx.canGoForward) ctx.navigate(1);
44
48
  break;
45
49
  }
46
50
  }
@@ -296,9 +296,11 @@ export class DateGridController {
296
296
  break;
297
297
  }
298
298
  // Week/day steps carry no month bounds of their own, so clamp the shifted
299
- // reference to [minDate, maxDate] too otherwise a swipe or a day-view arrow
300
- // key (neither gated by canGoBack/canGoForward the way the header arrows are)
301
- // could step the anchor onto an all-disabled week/day past the boundary.
299
+ // reference to [minDate, maxDate] too. Swipes and the day/agenda arrow keys
300
+ // are canGoBack/canGoForward-gated like the header arrows these days, but the
301
+ // custom-header API (goTo/navigateWeek/navigateDay) is not this clamp stays
302
+ // as the backstop so no caller can step the anchor onto an all-disabled
303
+ // week/day past the boundary.
302
304
  case 'week':
303
305
  next = clampDate(addDays(referenceDate, delta * 7), minDate, maxDate);
304
306
  break;
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { buttonGroupVariants, type ButtonGroupVariants } from '..';
3
3
  import { getBlocksConfig, resolveSlotClasses } from '../../provider';
4
+ import { composeHandlers } from '../../utils/compose-handlers';
4
5
  import { edgeEnabledIndex, nextEnabledIndex } from '../../utils';
5
6
  import { getTierContext, setTierContext } from '../../utils/tier-context';
6
7
  import type { ButtonGroupContext, ButtonGroupProps } from './index';
@@ -25,6 +26,12 @@
25
26
  preset,
26
27
  ariaLabel,
27
28
  ariaLabelledBy,
29
+ // Pulled out of restProps so the `{...restProps}`-first spread (internal
30
+ // attributes win — see docs/COMPONENT-API-CONVENTIONS.md) can't clobber
31
+ // it: the roving keyboard navigation below is composed with a consumer's
32
+ // own handler (internal first, consumer second) instead of either side
33
+ // silently replacing the other.
34
+ onkeydown: onkeydownProp,
28
35
  ...restProps
29
36
  }: ButtonGroupProps = $props();
30
37
 
@@ -271,18 +278,35 @@
271
278
  const ariaOrientation = $derived(ariaRole === 'radiogroup' ? orientation : undefined);
272
279
  </script>
273
280
 
281
+ <!--
282
+ restProps spreads FIRST so component-owned state wins (COMPONENT-API-CONVENTIONS
283
+ §restProps ordering) — a consumer role/tabindex through restProps must not
284
+ defeat the radiogroup semantics. The attributes after the spread are
285
+ conditional merges, not plain overrides, because an explicit `undefined`
286
+ after a spread REMOVES the attribute:
287
+ - role: always internally computed (radiogroup/group) — internal wins outright.
288
+ - aria-label / aria-labelledby: the dedicated props win, a consumer's own
289
+ `aria-label`/`aria-labelledby` through restProps is the fallback.
290
+ - aria-orientation: internal on the radiogroup arm; on the `group` arm it is
291
+ actively removed even against restProps — ARIA disallows aria-orientation
292
+ on role=group (aria-allowed-attr), mirroring Button's aria-pressed force-off.
293
+ - aria-disabled: internal `true` is unoverridable, idle falls back to the
294
+ consumer value; the default DOM output stays byte-identical (attr absent).
295
+ - onkeydown is destructured (never in restProps) and composed: the roving
296
+ keyboard nav always runs, a consumer handler runs after it.
297
+ -->
274
298
  <div
275
299
  bind:this={containerElement}
300
+ {...restProps}
276
301
  role={ariaRole}
277
302
  class={unstyled
278
303
  ? [slotClasses?.base, className].filter(Boolean).join(' ')
279
304
  : styles.base({ class: [slotClasses?.base, className] })}
280
- aria-label={ariaLabel}
281
- aria-labelledby={ariaLabelledBy}
305
+ aria-label={ariaLabel ?? restProps['aria-label']}
306
+ aria-labelledby={ariaLabelledBy ?? restProps['aria-labelledby']}
282
307
  aria-orientation={ariaOrientation}
283
- aria-disabled={disabled || undefined}
284
- onkeydown={handleKeyDown}
285
- {...restProps}
308
+ aria-disabled={disabled || restProps['aria-disabled'] || undefined}
309
+ onkeydown={composeHandlers(handleKeyDown, onkeydownProp)}
286
310
  >
287
311
  {@render children?.()}
288
312
  </div>
@@ -39,14 +39,24 @@
39
39
  );
40
40
  </script>
41
41
 
42
+ <!--
43
+ restProps spreads FIRST so component-owned state wins (COMPONENT-API-CONVENTIONS
44
+ §restProps ordering) — a consumer role through restProps must not defeat the
45
+ toolbar semantics. No conditional merges are needed here (unlike
46
+ Button/ButtonGroup), because every attribute after the spread is always
47
+ defined: role is static, `orientation` has a default, and `aria-label` is a
48
+ required prop destructured out of restProps. `aria-orientation` is valid on
49
+ role=toolbar on both arms, so there is no removal case; `aria-labelledby`
50
+ and other native/data-* attributes pass through the spread untouched.
51
+ -->
42
52
  <div
53
+ {...restProps}
43
54
  class={unstyled
44
55
  ? [slotClasses?.base, className].filter(Boolean).join(' ')
45
56
  : styles.base({ class: [slotClasses?.base, className] })}
46
57
  role="toolbar"
47
58
  aria-orientation={orientation}
48
59
  aria-label={ariaLabel}
49
- {...restProps}
50
60
  >
51
61
  {@render children()}
52
62
  </div>
@@ -58,10 +58,12 @@ its disclosure sibling `Collapsible`.)
58
58
 
59
59
  ## 3. Table chrome
60
60
 
61
- The Table no longer renders a frame by default. The new `appearance` prop
62
- controls the container chrome:
61
+ The Table no longer renders a frame by default. The new `variant` prop
62
+ controls the container chrome. (The prop was named `appearance` from v5.0
63
+ to v6.32; it now shares the single style-axis name `variant` with every
64
+ other component.)
63
65
 
64
- | `appearance` | Effect |
66
+ | `variant` | Effect |
65
67
  | ------------ | ------------------------------------------------------------ |
66
68
  | `flush` (default) | No frame; the table sits inline in the reading flow. |
67
69
  | `surface` | Quiet tinted zone — `bg-surface-quiet`. |
@@ -73,7 +75,7 @@ The dead `tableVariants` export is removed (was unused in any consumer).
73
75
  <!-- v4 — implicit frame -->
74
76
  <Table data={rows} columns={cols} />
75
77
  <!-- v5 — explicit equivalent -->
76
- <Table data={rows} columns={cols} appearance="framed" />
78
+ <Table data={rows} columns={cols} variant="framed" />
77
79
  <!-- v5 — new editorial default -->
78
80
  <Table data={rows} columns={cols} />
79
81
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@urbicon-ui/blocks",
3
- "version": "6.31.0",
3
+ "version": "6.33.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": {
@@ -31,7 +31,8 @@
31
31
  "format": "biome format --write . && prettier --write \"**/*.svelte\"",
32
32
  "lint": "biome check . && svelte-check --tsconfig ./tsconfig.json",
33
33
  "test": "vitest",
34
- "test:run": "vitest run"
34
+ "test:run": "vitest run",
35
+ "size": "bun scripts/bundle-size.ts"
35
36
  },
36
37
  "files": [
37
38
  "LICENSE",
@@ -94,8 +95,8 @@
94
95
  "@sveltejs/package": "^2.5.8",
95
96
  "@sveltejs/vite-plugin-svelte": "^7.0.0",
96
97
  "@tailwindcss/vite": "^4.3.1",
97
- "@urbicon-ui/i18n": "6.31.0",
98
- "@urbicon-ui/shared-types": "6.31.0",
98
+ "@urbicon-ui/i18n": "6.33.0",
99
+ "@urbicon-ui/shared-types": "6.33.0",
99
100
  "prettier": "^3.8.4",
100
101
  "prettier-plugin-svelte": "^4.1.1",
101
102
  "prettier-plugin-tailwindcss": "^0.8.0",