@urbicon-ui/blocks 6.34.0 → 6.36.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.
Files changed (127) hide show
  1. package/README.md +11 -1
  2. package/dist/components/Calendar/CalendarHeader.svelte +32 -26
  3. package/dist/components/Calendar/CalendarMiniMonth.svelte +10 -10
  4. package/dist/components/Calendar/calendar.variants.js +19 -5
  5. package/dist/components/Chat/Chat/Chat.svelte +53 -0
  6. package/dist/components/Chat/Chat/Chat.svelte.d.ts +4 -0
  7. package/dist/components/Chat/Chat/chat.variants.d.ts +20 -0
  8. package/dist/components/Chat/Chat/chat.variants.js +15 -0
  9. package/dist/components/Chat/Chat/index.d.ts +47 -0
  10. package/dist/components/Chat/Chat/index.js +2 -0
  11. package/dist/components/Chat/ChatMessage/ChatMessage.svelte +342 -0
  12. package/dist/components/Chat/ChatMessage/ChatMessage.svelte.d.ts +4 -0
  13. package/dist/components/Chat/ChatMessage/chat-message.variants.d.ts +138 -0
  14. package/dist/components/Chat/ChatMessage/chat-message.variants.js +118 -0
  15. package/dist/components/Chat/ChatMessage/index.d.ts +102 -0
  16. package/dist/components/Chat/ChatMessage/index.js +2 -0
  17. package/dist/components/Chat/ChatMessageList/ChatMessageList.svelte +301 -0
  18. package/dist/components/Chat/ChatMessageList/ChatMessageList.svelte.d.ts +6 -0
  19. package/dist/components/Chat/ChatMessageList/chat-message-list.variants.d.ts +23 -0
  20. package/dist/components/Chat/ChatMessageList/chat-message-list.variants.js +32 -0
  21. package/dist/components/Chat/ChatMessageList/chat-scroll.d.ts +45 -0
  22. package/dist/components/Chat/ChatMessageList/chat-scroll.js +52 -0
  23. package/dist/components/Chat/ChatMessageList/index.d.ts +93 -0
  24. package/dist/components/Chat/ChatMessageList/index.js +3 -0
  25. package/dist/components/Chat/CitationChip/CitationChip.svelte +107 -0
  26. package/dist/components/Chat/CitationChip/CitationChip.svelte.d.ts +4 -0
  27. package/dist/components/Chat/CitationChip/citation-chip.variants.d.ts +40 -0
  28. package/dist/components/Chat/CitationChip/citation-chip.variants.js +42 -0
  29. package/dist/components/Chat/CitationChip/index.d.ts +69 -0
  30. package/dist/components/Chat/CitationChip/index.js +2 -0
  31. package/dist/components/Chat/CodeBlock/CodeBlock.svelte +120 -0
  32. package/dist/components/Chat/CodeBlock/CodeBlock.svelte.d.ts +4 -0
  33. package/dist/components/Chat/CodeBlock/code-block.variants.d.ts +40 -0
  34. package/dist/components/Chat/CodeBlock/code-block.variants.js +51 -0
  35. package/dist/components/Chat/CodeBlock/index.d.ts +55 -0
  36. package/dist/components/Chat/CodeBlock/index.js +2 -0
  37. package/dist/components/Chat/PromptInput/PromptInput.svelte +432 -0
  38. package/dist/components/Chat/PromptInput/PromptInput.svelte.d.ts +4 -0
  39. package/dist/components/Chat/PromptInput/index.d.ts +165 -0
  40. package/dist/components/Chat/PromptInput/index.js +2 -0
  41. package/dist/components/Chat/PromptInput/prompt-input.variants.d.ts +90 -0
  42. package/dist/components/Chat/PromptInput/prompt-input.variants.js +102 -0
  43. package/dist/components/Chat/ReasoningDisclosure/ReasoningDisclosure.svelte +107 -0
  44. package/dist/components/Chat/ReasoningDisclosure/ReasoningDisclosure.svelte.d.ts +4 -0
  45. package/dist/components/Chat/ReasoningDisclosure/index.d.ts +85 -0
  46. package/dist/components/Chat/ReasoningDisclosure/index.js +2 -0
  47. package/dist/components/Chat/ReasoningDisclosure/reasoning-disclosure.variants.d.ts +20 -0
  48. package/dist/components/Chat/ReasoningDisclosure/reasoning-disclosure.variants.js +31 -0
  49. package/dist/components/Chat/StreamingMarkdown/MdBlock.svelte +99 -0
  50. package/dist/components/Chat/StreamingMarkdown/MdBlock.svelte.d.ts +9 -0
  51. package/dist/components/Chat/StreamingMarkdown/MdInline.svelte +82 -0
  52. package/dist/components/Chat/StreamingMarkdown/MdInline.svelte.d.ts +9 -0
  53. package/dist/components/Chat/StreamingMarkdown/StreamingMarkdown.svelte +118 -0
  54. package/dist/components/Chat/StreamingMarkdown/StreamingMarkdown.svelte.d.ts +4 -0
  55. package/dist/components/Chat/StreamingMarkdown/index.d.ts +86 -0
  56. package/dist/components/Chat/StreamingMarkdown/index.js +2 -0
  57. package/dist/components/Chat/StreamingMarkdown/md-context.d.ts +58 -0
  58. package/dist/components/Chat/StreamingMarkdown/md-context.js +1 -0
  59. package/dist/components/Chat/StreamingMarkdown/streaming-markdown.variants.d.ts +145 -0
  60. package/dist/components/Chat/StreamingMarkdown/streaming-markdown.variants.js +73 -0
  61. package/dist/components/Chat/ToolCallCard/ToolCallCard.svelte +174 -0
  62. package/dist/components/Chat/ToolCallCard/ToolCallCard.svelte.d.ts +4 -0
  63. package/dist/components/Chat/ToolCallCard/index.d.ts +84 -0
  64. package/dist/components/Chat/ToolCallCard/index.js +2 -0
  65. package/dist/components/Chat/ToolCallCard/tool-call-card.variants.d.ts +38 -0
  66. package/dist/components/Chat/ToolCallCard/tool-call-card.variants.js +38 -0
  67. package/dist/components/Chat/chat.types.d.ts +82 -0
  68. package/dist/components/Chat/chat.types.js +7 -0
  69. package/dist/components/Chat/index.d.ts +22 -0
  70. package/dist/components/Chat/index.js +14 -0
  71. package/dist/components/Chat/markdown/blocks.d.ts +54 -0
  72. package/dist/components/Chat/markdown/blocks.js +462 -0
  73. package/dist/components/Chat/markdown/inline.d.ts +31 -0
  74. package/dist/components/Chat/markdown/inline.js +446 -0
  75. package/dist/components/Chat/markdown/repair.d.ts +23 -0
  76. package/dist/components/Chat/markdown/repair.js +337 -0
  77. package/dist/components/Chat/markdown/streaming-harness.d.ts +23 -0
  78. package/dist/components/Chat/markdown/streaming-harness.js +62 -0
  79. package/dist/components/Chat/markdown/types.d.ts +139 -0
  80. package/dist/components/Chat/markdown/types.js +13 -0
  81. package/dist/components/Chat/markdown/url-policy.d.ts +33 -0
  82. package/dist/components/Chat/markdown/url-policy.js +109 -0
  83. package/dist/components/FileUpload/FileUpload.svelte +52 -125
  84. package/dist/components/FileUpload/FileUpload.svelte.d.ts +1 -1
  85. package/dist/components/FileUpload/index.d.ts +2 -28
  86. package/dist/components/Planner/PlannerHeader.svelte +16 -13
  87. package/dist/components/Planner/planner.variants.js +14 -3
  88. package/dist/components/index.d.ts +1 -0
  89. package/dist/components/index.js +1 -0
  90. package/dist/i18n/index.d.ts +268 -382
  91. package/dist/i18n/index.js +83 -9
  92. package/dist/internal/core/CoreIconButton.svelte +66 -0
  93. package/dist/internal/core/CoreIconButton.svelte.d.ts +13 -0
  94. package/dist/internal/core/CoreSpinner.svelte +49 -0
  95. package/dist/internal/core/CoreSpinner.svelte.d.ts +8 -0
  96. package/dist/internal/core/spinner-geometry.d.ts +6 -0
  97. package/dist/internal/core/spinner-geometry.js +6 -0
  98. package/dist/mint/registry.d.ts +20 -2
  99. package/dist/mint/registry.js +20 -2
  100. package/dist/primitives/Badge/Badge.svelte +5 -5
  101. package/dist/primitives/Badge/badge.variants.js +17 -1
  102. package/dist/primitives/Button/Button.svelte +4 -2
  103. package/dist/primitives/Collapsible/Collapsible.svelte +5 -0
  104. package/dist/primitives/Dialog/Dialog.svelte +7 -5
  105. package/dist/primitives/Dialog/dialog.variants.d.ts +7 -0
  106. package/dist/primitives/Dialog/dialog.variants.js +17 -0
  107. package/dist/primitives/Drawer/Drawer.svelte +7 -5
  108. package/dist/primitives/Drawer/drawer.variants.d.ts +8 -0
  109. package/dist/primitives/Drawer/drawer.variants.js +17 -0
  110. package/dist/primitives/Spinner/Spinner.svelte +3 -1
  111. package/dist/primitives/SplitPane/SplitPane.svelte +293 -0
  112. package/dist/primitives/SplitPane/SplitPane.svelte.d.ts +4 -0
  113. package/dist/primitives/SplitPane/index.d.ts +88 -0
  114. package/dist/primitives/SplitPane/index.js +2 -0
  115. package/dist/primitives/SplitPane/split-pane.utils.d.ts +73 -0
  116. package/dist/primitives/SplitPane/split-pane.utils.js +94 -0
  117. package/dist/primitives/SplitPane/split-pane.variants.d.ts +40 -0
  118. package/dist/primitives/SplitPane/split-pane.variants.js +56 -0
  119. package/dist/primitives/Toast/Toaster.svelte +17 -2
  120. package/dist/primitives/index.d.ts +2 -0
  121. package/dist/primitives/index.js +1 -0
  122. package/dist/utils/file-intake.d.ts +78 -0
  123. package/dist/utils/file-intake.js +144 -0
  124. package/dist/utils/index.d.ts +1 -0
  125. package/dist/utils/index.js +1 -0
  126. package/dist/utils/variants.js +44 -10
  127. package/package.json +4 -6
@@ -1,13 +1,62 @@
1
1
  import { createPackageI18n } from '@urbicon-ui/i18n';
2
- import deTranslations from '../translations/de.js';
3
2
  import enTranslations from '../translations/en.js';
4
- // Create package translations for blocks
5
- const blocksTranslations = {
6
- en: enTranslations,
7
- de: deTranslations
8
- };
9
- // Create the package i18n integration (registers lazily on first useTranslate()/t()).
10
- export const blocksI18n = createPackageI18n('blocks', blocksTranslations);
3
+ /**
4
+ * blocks package i18n.
5
+ *
6
+ * **Only the English base bundle is eager.** German (`de`) is registered as a
7
+ * lazy dynamic-import loader, so an English-only app never statically bundles the
8
+ * `de` catalog (~4 KB min) Vite/Rollup splits it into its own chunk that loads
9
+ * only when the `de` locale is activated. `en` is the base/fallback locale.
10
+ *
11
+ * The eager base is `{ en }` (not `blocksTranslations`, which is retained below as
12
+ * an `{ en }` back-compat export). Registration is lazy on first
13
+ * `useBlocksI18n()`/`t()` — see `createPackageI18n`.
14
+ *
15
+ * ## Before the `de` chunk loads: resolution falls back to `en`
16
+ *
17
+ * A package-scoped lookup for a `de` key that isn't loaded yet resolves through
18
+ * the package fallback locale, i.e. it returns the **English** string (verified:
19
+ * `@urbicon-ui/i18n` `registry.translate` package path + `lazy-load.test.ts`). It
20
+ * never renders the raw key. The provider (`<I18nProvider>`) triggers the `de`
21
+ * load in a **client-only** `$effect` on mount, so a `de` app renders English on
22
+ * the server and the first client paint, then re-resolves to German once the
23
+ * chunk lands — a brief text flash and a possible hydration text mismatch.
24
+ * Two consequences of that window worth knowing:
25
+ *
26
+ * - The "falls back to English" guarantee assumes the provider's `fallbackLocale`
27
+ * stays at its default (`en`). Pointing `fallbackLocale` at a lazy locale that
28
+ * has not loaded yet leaves both lookup paths empty — such a key then renders
29
+ * raw and fires `onMissingKey`. Eager-register any locale you use as fallback.
30
+ * - `LocaleSwitcher` disables its trigger while a locale chunk load is in flight
31
+ * (`registry.isLoading`), so without the eager register a `de` app briefly
32
+ * shows the switcher disabled on first mount.
33
+ *
34
+ * The SSR recipe below removes both, along with the flash itself.
35
+ *
36
+ * ## SSR recipe for German (and other non-base) apps
37
+ *
38
+ * German SvelteKit SSR apps should register the `de` bundle eagerly **once at
39
+ * app start** so the very first server render is already German (no flash, no
40
+ * hydration mismatch). The registry is module-global and holds only static,
41
+ * request-identical translation data, so a single startup registration is
42
+ * SSR-safe — it carries no per-request state:
43
+ *
44
+ * ```ts
45
+ * // src/hooks.server.ts (or any module evaluated once at server start)
46
+ * import { registerBlocksLocale } from '@urbicon-ui/blocks';
47
+ * import de from '@urbicon-ui/blocks/i18n/de';
48
+ *
49
+ * registerBlocksLocale('de', de);
50
+ * ```
51
+ *
52
+ * This keeps the `de` catalog out of English-only client bundles (the whole point
53
+ * of the split) while making it eagerly present wherever it is actually rendered.
54
+ */
55
+ export const blocksI18n = createPackageI18n('blocks', { en: enTranslations }, {
56
+ loaders: {
57
+ de: () => import('../translations/de.js').then((m) => m.default)
58
+ }
59
+ });
11
60
  /**
12
61
  * Context-scoped translation hook for the blocks package. Call during component
13
62
  * initialisation, then use the returned `t` (conventionally aliased `bt`):
@@ -18,10 +67,35 @@ export const blocksI18n = createPackageI18n('blocks', blocksTranslations);
18
67
  * ```
19
68
  *
20
69
  * Resolves against the nearest `<I18nProvider>`'s locale (or the base locale
21
- * when none is mounted) and re-renders reactively on locale change.
70
+ * when none is mounted) and re-renders reactively on locale change. For a lazy
71
+ * locale (`de`) that isn't loaded yet, resolution falls back to the base locale
72
+ * (`en`); see the module docblock and `registerBlocksLocale` for the SSR path.
22
73
  */
23
74
  export const useBlocksI18n = blocksI18n.useTranslate;
75
+ /**
76
+ * Eagerly register the blocks bundle for a lazy locale (currently `de`) so it is
77
+ * present for SSR / the first render, instead of loading client-side on mount.
78
+ * Call once at app/server start, paired with the public locale subpath import:
79
+ *
80
+ * ```ts
81
+ * import { registerBlocksLocale } from '@urbicon-ui/blocks';
82
+ * import de from '@urbicon-ui/blocks/i18n/de';
83
+ * registerBlocksLocale('de', de);
84
+ * ```
85
+ *
86
+ * Additive (does not drop the eager `en` base) and strict (throws on an
87
+ * unsupported locale or a non-object bundle).
88
+ */
89
+ export const registerBlocksLocale = (locale, bundle) => blocksI18n.registerLocale(locale, bundle);
24
90
  // Introspection helpers (locale list / key existence).
25
91
  export const { exists: hasBlocksTranslation, getLocales: getBlocksLocales } = blocksI18n;
92
+ /**
93
+ * The eager base bundle only (`{ en }`). `de` is lazy and no longer part of this
94
+ * object; import it explicitly via `@urbicon-ui/blocks/i18n/de` (e.g. for a parity
95
+ * test or the SSR eager-register recipe above).
96
+ */
97
+ const blocksTranslations = {
98
+ en: enTranslations
99
+ };
26
100
  // Re-export for external use
27
101
  export { blocksTranslations };
@@ -0,0 +1,66 @@
1
+ <!--
2
+ CoreIconButton — behaviour-only internal core for the small icon-only controls
3
+ embedded inside other public components (Badge's remove ×, Dialog/Drawer close
4
+ ×). It renders a native `<button type="button">` with just the interaction
5
+ baseline (flex-centred content, pointer/select affordance, focus-visible reset,
6
+ disabled inertness). Visual identity — size, radius, colour, ring, shadow — comes
7
+ entirely from the call-site's `class` (a `*.variants.ts` slot), NOT from a variant
8
+ engine pass here: this core deliberately does not touch tv()/mint/tokens, which is
9
+ what lets a public component embed a control without importing another public
10
+ component (keeps the public-to-public import graph clean, see internal/core/).
11
+
12
+ The base classes are structural plumbing, deliberately NOT tw-merged against the
13
+ incoming `class` (no engine here): a consumer slot class targeting one of these
14
+ buckets (display/cursor/select/disabled-opacity) is resolved by stylesheet order,
15
+ not by the override ladder. That is accepted: overrides belong on the call-site's
16
+ variants slot (which merges correctly bucket-by-bucket); the plumbing is not an
17
+ override surface. Under `unstyled` the plumbing also stays — stripping
18
+ `disabled:pointer-events-none` & co. would break behaviour, not styling.
19
+
20
+ INTERNAL — never exported from the package barrel, no docs/MCP entry. For a full
21
+ themed button use the public `Button`.
22
+
23
+ restProps-first spread (COMPONENT-API-CONVENTIONS §restProps ordering): the
24
+ explicit attributes come AFTER `{...restProps}` so component-owned wiring wins —
25
+ `aria-label` is required (an icon-only button has no text accessible name) and
26
+ can never be clobbered, and `type="button"` stays a submit-safe default.
27
+ -->
28
+ <script lang="ts">
29
+ import type { Snippet } from 'svelte';
30
+ import type { HTMLButtonAttributes } from 'svelte/elements';
31
+
32
+ let {
33
+ 'aria-label': ariaLabel,
34
+ class: className = '',
35
+ disabled = false,
36
+ onclick,
37
+ children,
38
+ ...restProps
39
+ }: {
40
+ /** Required — an icon-only control has no text to name it. */
41
+ 'aria-label': string;
42
+ class?: string;
43
+ disabled?: boolean;
44
+ onclick?: (event: MouseEvent) => void;
45
+ children: Snippet;
46
+ } & Omit<
47
+ HTMLButtonAttributes,
48
+ 'aria-label' | 'class' | 'disabled' | 'onclick' | 'children'
49
+ > = $props();
50
+ </script>
51
+
52
+ <button
53
+ {...restProps}
54
+ type="button"
55
+ {disabled}
56
+ {onclick}
57
+ aria-label={ariaLabel}
58
+ class={[
59
+ 'inline-flex items-center justify-center cursor-pointer select-none',
60
+ 'focus-visible:outline-none',
61
+ 'disabled:opacity-50 disabled:cursor-not-allowed disabled:pointer-events-none',
62
+ className
63
+ ]}
64
+ >
65
+ {@render children()}
66
+ </button>
@@ -0,0 +1,13 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLButtonAttributes } from 'svelte/elements';
3
+ type $$ComponentProps = {
4
+ /** Required — an icon-only control has no text to name it. */
5
+ 'aria-label': string;
6
+ class?: string;
7
+ disabled?: boolean;
8
+ onclick?: (event: MouseEvent) => void;
9
+ children: Snippet;
10
+ } & Omit<HTMLButtonAttributes, 'aria-label' | 'class' | 'disabled' | 'onclick' | 'children'>;
11
+ declare const CoreIconButton: import("svelte").Component<$$ComponentProps, {}, "">;
12
+ type CoreIconButton = ReturnType<typeof CoreIconButton>;
13
+ export default CoreIconButton;
@@ -0,0 +1,49 @@
1
+ <!--
2
+ CoreSpinner — the bare rotating-arc SVG shared by embedding contexts (loading
3
+ Button, and any internal surface that needs an inline busy indicator) WITHOUT
4
+ pulling in the public Spinner.
5
+
6
+ INTERNAL — never exported from the package barrel, no docs/MCP entry. For a
7
+ configurable spinner (variants, speed, intent palette, sr-only label, unstyled
8
+ / slotClasses) use the public `Spinner`; this core is deliberately fixed to the
9
+ default variant at 1s.
10
+
11
+ No `role` / `aria-*` / sr-only label here on purpose: the embedding context
12
+ owns the semantics. Button wraps this in an `aria-hidden` span and announces
13
+ loading via its own `aria-busy`; a `role="status"` here would nest a live
14
+ region inside contexts that already declare one (e.g. Toast). The geometry is
15
+ imported from `spinner-geometry.ts` so it can never drift from the public
16
+ Spinner.
17
+ -->
18
+ <script lang="ts">
19
+ import { SPINNER_ARC_PATH } from './spinner-geometry';
20
+
21
+ let {
22
+ size = 'md',
23
+ class: className = ''
24
+ }: {
25
+ size?: 'xs' | 'sm' | 'md' | 'lg';
26
+ /** Extra classes on the wrapper span (the call site supplies visual identity). */
27
+ class?: string;
28
+ } = $props();
29
+
30
+ // Matches the public Spinner's per-size box (spinner.variants → base sizes).
31
+ const sizeClass = {
32
+ xs: 'w-4 h-4',
33
+ sm: 'w-5 h-5',
34
+ md: 'w-6 h-6',
35
+ lg: 'w-8 h-8'
36
+ };
37
+ </script>
38
+
39
+ <span class={['inline-flex items-center justify-center text-current', sizeClass[size], className]}>
40
+ <svg
41
+ class="w-full h-full animate-spin [animation-duration:1s] motion-reduce:animate-none"
42
+ viewBox="0 0 24 24"
43
+ fill="none"
44
+ xmlns="http://www.w3.org/2000/svg"
45
+ >
46
+ <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" class="opacity-25" />
47
+ <path class="fill-current" d={SPINNER_ARC_PATH} />
48
+ </svg>
49
+ </span>
@@ -0,0 +1,8 @@
1
+ type $$ComponentProps = {
2
+ size?: 'xs' | 'sm' | 'md' | 'lg';
3
+ /** Extra classes on the wrapper span (the call site supplies visual identity). */
4
+ class?: string;
5
+ };
6
+ declare const CoreSpinner: import("svelte").Component<$$ComponentProps, {}, "">;
7
+ type CoreSpinner = ReturnType<typeof CoreSpinner>;
8
+ export default CoreSpinner;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * single source of the spinner geometry — imported by CoreSpinner and the
3
+ * public Spinner. The `d` attribute of the rotating arc `<path>` in the
4
+ * default (spinner) variant. Kept in one place so the two SVGs never drift.
5
+ */
6
+ export declare const SPINNER_ARC_PATH = "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z";
@@ -0,0 +1,6 @@
1
+ /**
2
+ * single source of the spinner geometry — imported by CoreSpinner and the
3
+ * public Spinner. The `d` attribute of the rotating arc `<path>` in the
4
+ * default (spinner) variant. Kept in one place so the two SVGs never drift.
5
+ */
6
+ export const SPINNER_ARC_PATH = 'M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z';
@@ -47,15 +47,33 @@ declare class MintRegistry {
47
47
  * effect. Acceptable for decorative effects (documented contract, see
48
48
  * mint/README.md); consumers who need first-interaction guarantees register
49
49
  * the effect statically up front (`registerDefaultMints()`).
50
+ *
51
+ * One apply() per element: a second apply() on the same element replaces
52
+ * the instances map the first one registered, and the first cleanup then
53
+ * deletes it — `update()` for the second application would go dead. Every
54
+ * in-repo caller pairs exactly one apply() per element with its `$effect`
55
+ * teardown, which upholds this.
50
56
  */
51
57
  apply(el: HTMLElement, mint: MintProp, fallbacks?: MintFallbacks): () => void;
52
58
  /** Normalize polymorphic mint prop to consistent format */
53
59
  private normalizeMintProp;
54
- /** Update mint config for an element */
60
+ /**
61
+ * Update mint config for an element.
62
+ *
63
+ * No-op for a name still inside the demand-load fetch window (it is not in
64
+ * the element's instances map yet) — same decorative-effect contract as the
65
+ * lost-first-interaction case documented on `apply()`.
66
+ */
55
67
  update(el: HTMLElement, name: string, config: MintConfig): void;
56
68
  /** List all registered mint names */
57
69
  list(): string[];
58
- /** Clear all mints */
70
+ /**
71
+ * Clear all mints. Test-only escape hatch: this does NOT reset the
72
+ * demand-load latch (`builtinsLoading` here, `defaultMintsRegistered` in
73
+ * ./presets.ts), so built-ins stay gone until a module reload — tests use
74
+ * `vi.resetModules()` for a fresh registry. Pre-existing behaviour from the
75
+ * eager-registration era, kept as-is.
76
+ */
59
77
  clear(): void;
60
78
  }
61
79
  export declare const mintRegistry: MintRegistry;
@@ -59,6 +59,12 @@ class MintRegistry {
59
59
  * effect. Acceptable for decorative effects (documented contract, see
60
60
  * mint/README.md); consumers who need first-interaction guarantees register
61
61
  * the effect statically up front (`registerDefaultMints()`).
62
+ *
63
+ * One apply() per element: a second apply() on the same element replaces
64
+ * the instances map the first one registered, and the first cleanup then
65
+ * deletes it — `update()` for the second application would go dead. Every
66
+ * in-repo caller pairs exactly one apply() per element with its `$effect`
67
+ * teardown, which upholds this.
62
68
  */
63
69
  apply(el, mint, fallbacks) {
64
70
  const mintDefinitions = this.normalizeMintProp(mint);
@@ -123,7 +129,13 @@ class MintRegistry {
123
129
  }
124
130
  return [];
125
131
  }
126
- /** Update mint config for an element */
132
+ /**
133
+ * Update mint config for an element.
134
+ *
135
+ * No-op for a name still inside the demand-load fetch window (it is not in
136
+ * the element's instances map yet) — same decorative-effect contract as the
137
+ * lost-first-interaction case documented on `apply()`.
138
+ */
127
139
  update(el, name, config) {
128
140
  const elementMints = this.instances.get(el);
129
141
  const mint = elementMints?.get(name);
@@ -135,7 +147,13 @@ class MintRegistry {
135
147
  list() {
136
148
  return Array.from(this.mints.keys());
137
149
  }
138
- /** Clear all mints */
150
+ /**
151
+ * Clear all mints. Test-only escape hatch: this does NOT reset the
152
+ * demand-load latch (`builtinsLoading` here, `defaultMintsRegistered` in
153
+ * ./presets.ts), so built-ins stay gone until a module reload — tests use
154
+ * `vi.resetModules()` for a fresh registry. Pre-existing behaviour from the
155
+ * eager-registration era, kept as-is.
156
+ */
139
157
  clear() {
140
158
  this.mints.clear();
141
159
  }
@@ -1,5 +1,7 @@
1
1
  <script lang="ts">
2
- import { Button, mintRegistry } from '../..';
2
+ import { mintRegistry } from '../..';
3
+ // internal core, not the public component — keeps the public-to-public import graph clean (see internal/core/)
4
+ import CoreIconButton from '../../internal/core/CoreIconButton.svelte';
3
5
  import { getBlocksConfig, resolveSlotClasses } from '../../provider';
4
6
  import { resolveIcon } from '../../icons';
5
7
  import CloseIconDefault from '../../icons/CloseIcon.svelte';
@@ -152,9 +154,7 @@
152
154
  {/if}
153
155
 
154
156
  {#if isRemovable}
155
- <Button
156
- variant="ghost"
157
- size="xs"
157
+ <CoreIconButton
158
158
  {disabled}
159
159
  class={unstyled
160
160
  ? (slotClasses?.removeButton ?? '')
@@ -167,7 +167,7 @@
167
167
  ? (slotClasses?.removeIcon ?? '')
168
168
  : styles.removeIcon({ class: slotClasses?.removeIcon })}
169
169
  />
170
- </Button>
170
+ </CoreIconButton>
171
171
  {/if}
172
172
  </span>
173
173
 
@@ -23,9 +23,25 @@ export const badgeVariants = tv({
23
23
  // arbitrary property inherits `base`'s per-size gap. (Codeberg #21)
24
24
  content: ['flex items-center [gap:inherit]'],
25
25
  // tier: modify — small remove-control on a commit-tier badge.
26
+ //
27
+ // Rendered on the internal CoreIconButton (behaviour-only base: inline-flex
28
+ // centring, cursor/select, focus-visible reset, disabled inertness), so this
29
+ // slot carries the FULL visual identity. The class set below is the exact
30
+ // fold of the `<Button variant="ghost" size="xs">` (intent neutral, tier
31
+ // commit) this control used to be — the ghost/xs Button base plus the
32
+ // remove-control overrides — MINUS the classes CoreIconButton already
33
+ // supplies. Effective render is byte-identical to the pre-extraction Badge,
34
+ // so the removable VR baseline stays green (see internal/core/).
26
35
  removeButton: [
36
+ // ghost/xs neutral Button base (the part CoreIconButton does not supply)
37
+ 'relative font-medium text-center whitespace-nowrap border overflow-hidden',
38
+ 'duration-[var(--blocks-duration-fast)] ease-out',
39
+ 'bg-transparent border-transparent shadow-none',
40
+ 'hover:shadow-[var(--blocks-shadow-md)] active:scale-[0.98] active:shadow-[var(--blocks-shadow-sm)]',
41
+ 'h-6 px-2 text-xs gap-1 focus-visible:ring-offset-2',
42
+ // remove-control overrides (won the fold over the ghost base)
27
43
  'ml-1 shrink-0 rounded-modify transition-colors text-current',
28
- 'hover:bg-neutral-950/10 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-current'
44
+ 'hover:bg-neutral-950/10 focus-visible:ring-1 focus-visible:ring-current'
29
45
  ],
30
46
  removeIcon: ['w-3 h-3']
31
47
  },
@@ -1,5 +1,7 @@
1
1
  <script lang="ts">
2
- import { mintRegistry, Spinner } from '../..';
2
+ import { mintRegistry } from '../..';
3
+ // internal core, not the public component — keeps the public-to-public import graph clean (see internal/core/)
4
+ import CoreSpinner from '../../internal/core/CoreSpinner.svelte';
3
5
  // Direct import (not the barrel): the resolveIcon tree-shaking pattern.
4
6
  // Button's mint default is 'scale', so the scale factory ships statically
5
7
  // as the apply() fallback; every other mint name stays demand-loaded.
@@ -154,7 +156,7 @@
154
156
  : styles.spinner({ class: slotClasses?.spinner })}
155
157
  aria-hidden="true"
156
158
  >
157
- <Spinner size={spinnerSizeMap[effectiveSize]} intent="current" visible={true} />
159
+ <CoreSpinner size={spinnerSizeMap[effectiveSize]} />
158
160
  </span>
159
161
 
160
162
  <span
@@ -109,10 +109,15 @@
109
109
  </button>
110
110
  {/if}
111
111
 
112
+ <!-- `inert` while collapsed (Popover's pattern): the clipped children stay
113
+ mounted for the grid-rows animation, so without it a keyboard user tabs
114
+ into invisible controls (e.g. a CodeBlock copy button inside a collapsed
115
+ ToolCallCard — WCAG 2.4.3/2.4.7) and the subtree stays in the a11y tree. -->
112
116
  <div
113
117
  id={contentId}
114
118
  role="region"
115
119
  aria-labelledby={triggerId}
120
+ inert={!isOpen}
116
121
  class={unstyled
117
122
  ? (slotClasses?.content ?? '')
118
123
  : styles.content({ class: slotClasses?.content })}
@@ -8,7 +8,8 @@
8
8
  import { trapFocus, showDialogModal, closeDialogModal } from '../../utils/overlay';
9
9
  import { composeHandlers } from '../../utils/compose-handlers';
10
10
  import { overlayStack, getOverlayMotion } from '../../utils';
11
- import Button from '../Button/Button.svelte';
11
+ // internal core, not the public component — keeps the public-to-public import graph clean (see internal/core/)
12
+ import CoreIconButton from '../../internal/core/CoreIconButton.svelte';
12
13
  import type { DialogProps } from './index';
13
14
  import { dialogVariants, type DialogVariants } from './dialog.variants';
14
15
 
@@ -316,14 +317,15 @@
316
317
  {title}
317
318
  </h2>
318
319
  {#if !hideCloseButton}
319
- <Button
320
- variant="ghost"
321
- size="sm"
320
+ <CoreIconButton
321
+ class={unstyled
322
+ ? (slotClasses?.closeButton ?? '')
323
+ : styles.closeButton({ class: slotClasses?.closeButton })}
322
324
  onclick={requestClose}
323
325
  aria-label={bt('accessibility.closeDialog')}
324
326
  >
325
327
  <CloseIcon class="h-4 w-4" />
326
- </Button>
328
+ </CoreIconButton>
327
329
  {/if}
328
330
  </header>
329
331
 
@@ -46,6 +46,13 @@ export declare const dialogVariants: ((props?: {
46
46
  } & {
47
47
  class?: string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | /*elided*/ any | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined;
48
48
  }) | undefined) => string;
49
+ closeButton: (props?: ({
50
+ size?: "sm" | "md" | "lg" | "xl" | "fullscreen" | "full" | undefined;
51
+ placement?: "top" | "center" | undefined;
52
+ intent?: "primary" | "secondary" | "success" | "warning" | "danger" | "neutral" | undefined;
53
+ } & {
54
+ class?: string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | /*elided*/ any | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined;
55
+ }) | undefined) => string;
49
56
  body: (props?: ({
50
57
  size?: "sm" | "md" | "lg" | "xl" | "fullscreen" | "full" | undefined;
51
58
  placement?: "top" | "center" | undefined;
@@ -26,6 +26,23 @@ export const dialogVariants = tv({
26
26
  'border-b border-border-hairline flex-shrink-0'
27
27
  ],
28
28
  title: ['text-base font-semibold text-text-primary truncate'],
29
+ // Icon-only header close control (× dismiss). Rendered on the internal
30
+ // CoreIconButton (behaviour-only base: inline-flex centring, cursor/select,
31
+ // focus-visible reset, disabled inertness), so this slot carries the FULL
32
+ // visual identity of the `<Button variant="ghost" size="sm">` (intent
33
+ // neutral, tier commit) it used to be — the exact ghost/sm neutral Button
34
+ // fold MINUS the classes CoreIconButton already supplies. Effective render is
35
+ // byte-identical to the pre-extraction close button (no VR baseline gates
36
+ // overlays; reproduced deterministically from buttonVariants). See
37
+ // internal/core/.
38
+ closeButton: [
39
+ 'relative font-medium text-center whitespace-nowrap border overflow-hidden',
40
+ 'transition-[color,background-color,border-color,box-shadow,opacity,transform] duration-[var(--blocks-duration-fast)] ease-out',
41
+ 'rounded-commit bg-transparent border-transparent shadow-none',
42
+ 'hover:shadow-[var(--blocks-shadow-md)] active:scale-[0.98] active:shadow-[var(--blocks-shadow-sm)]',
43
+ 'h-8 px-3 text-sm gap-1.5 text-neutral-emphasis hover:bg-neutral-subtle',
44
+ 'focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-neutral/50'
45
+ ],
29
46
  body: [
30
47
  'px-5 py-4 flex-1 overflow-y-auto overscroll-contain',
31
48
  'text-sm leading-relaxed text-text-secondary'
@@ -8,7 +8,8 @@
8
8
  import { trapFocus, showDialogModal, closeDialogModal } from '../../utils/overlay';
9
9
  import { composeHandlers } from '../../utils/compose-handlers';
10
10
  import { overlayStack, getOverlayMotion } from '../../utils';
11
- import Button from '../Button/Button.svelte';
11
+ // internal core, not the public component — keeps the public-to-public import graph clean (see internal/core/)
12
+ import CoreIconButton from '../../internal/core/CoreIconButton.svelte';
12
13
  import type { DrawerProps } from './index';
13
14
  import { drawerVariants, type DrawerVariants } from './drawer.variants';
14
15
 
@@ -252,14 +253,15 @@
252
253
  <span></span>
253
254
  {/if}
254
255
  {#if !hideCloseButton}
255
- <Button
256
- variant="ghost"
257
- size="sm"
256
+ <CoreIconButton
257
+ class={unstyled
258
+ ? (slotClasses?.closeButton ?? '')
259
+ : styles.closeButton({ class: slotClasses?.closeButton })}
258
260
  onclick={requestClose}
259
261
  aria-label={bt('accessibility.closeDrawer')}
260
262
  >
261
263
  <CloseIcon class="h-4 w-4" />
262
- </Button>
264
+ </CoreIconButton>
263
265
  {/if}
264
266
  </header>
265
267
  {/if}
@@ -45,6 +45,14 @@ export declare const drawerVariants: ((props?: {
45
45
  } & {
46
46
  class?: string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | /*elided*/ any | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined;
47
47
  }) | undefined) => string;
48
+ closeButton: (props?: ({
49
+ placement?: "top" | "right" | "bottom" | "left" | undefined;
50
+ size?: "sm" | "md" | "lg" | "xl" | "full" | undefined;
51
+ intent?: "primary" | "secondary" | "success" | "warning" | "danger" | "neutral" | undefined;
52
+ accentEdge?: boolean | undefined;
53
+ } & {
54
+ class?: string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | /*elided*/ any | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined;
55
+ }) | undefined) => string;
48
56
  body: (props?: ({
49
57
  placement?: "top" | "right" | "bottom" | "left" | undefined;
50
58
  size?: "sm" | "md" | "lg" | "xl" | "full" | undefined;
@@ -18,6 +18,23 @@ export const drawerVariants = tv({
18
18
  'border-b border-border-hairline flex-shrink-0'
19
19
  ],
20
20
  title: ['text-base font-semibold text-text-primary truncate'],
21
+ // Icon-only header close control (× dismiss). Rendered on the internal
22
+ // CoreIconButton (behaviour-only base: inline-flex centring, cursor/select,
23
+ // focus-visible reset, disabled inertness), so this slot carries the FULL
24
+ // visual identity of the `<Button variant="ghost" size="sm">` (intent
25
+ // neutral, tier commit) it used to be — the exact ghost/sm neutral Button
26
+ // fold MINUS the classes CoreIconButton already supplies. Effective render is
27
+ // byte-identical to the pre-extraction close button (no VR baseline gates
28
+ // overlays; reproduced deterministically from buttonVariants). Mirrors Dialog.
29
+ // See internal/core/.
30
+ closeButton: [
31
+ 'relative font-medium text-center whitespace-nowrap border overflow-hidden',
32
+ 'transition-[color,background-color,border-color,box-shadow,opacity,transform] duration-[var(--blocks-duration-fast)] ease-out',
33
+ 'rounded-commit bg-transparent border-transparent shadow-none',
34
+ 'hover:shadow-[var(--blocks-shadow-md)] active:scale-[0.98] active:shadow-[var(--blocks-shadow-sm)]',
35
+ 'h-8 px-3 text-sm gap-1.5 text-neutral-emphasis hover:bg-neutral-subtle',
36
+ 'focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-neutral/50'
37
+ ],
21
38
  body: [
22
39
  'px-5 py-4 flex-1 overflow-y-auto overscroll-contain',
23
40
  'text-sm leading-relaxed text-text-secondary'
@@ -1,5 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { getBlocksConfig, resolveSlotClasses } from '../../provider';
3
+ // internal core, not the public component — keeps the public-to-public import graph clean (see internal/core/)
4
+ import { SPINNER_ARC_PATH } from '../../internal/core/spinner-geometry';
3
5
  import { spinnerVariants, type SpinnerVariants } from './spinner.variants';
4
6
  import type { SpinnerProps } from './index';
5
7
 
@@ -126,7 +128,7 @@
126
128
  class={unstyled
127
129
  ? (slotClasses?.svgPath ?? '')
128
130
  : styles.svgPath({ class: slotClasses?.svgPath })}
129
- d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
131
+ d={SPINNER_ARC_PATH}
130
132
  />
131
133
  </svg>
132
134
  {/if}