@selvajs/ui 6.6.0-beta.4 → 6.6.0-beta.6

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 (114) hide show
  1. package/dist/components/ErrorScreen.svelte +6 -2
  2. package/dist/components/compute/AppLayout.svelte +163 -74
  3. package/dist/components/compute/AppLayout.svelte.d.ts +11 -0
  4. package/dist/components/compute/ComputeApp.svelte +40 -20
  5. package/dist/components/compute/ComputeApp.svelte.d.ts +19 -2
  6. package/dist/components/compute/ComputeMessagesDialog.svelte +30 -40
  7. package/dist/components/compute/ParameterPresetManager.svelte +7 -44
  8. package/dist/components/compute/ParameterPresetManager.svelte.d.ts +4 -5
  9. package/dist/components/compute/SolveMessages.svelte +30 -37
  10. package/dist/components/compute/SolveMessages.svelte.d.ts +0 -12
  11. package/dist/components/layout/PageFooter.svelte +21 -11
  12. package/dist/components/preview/ChartOutput.svelte +17 -8
  13. package/dist/components/preview/Group.svelte +33 -18
  14. package/dist/components/preview/HelpButton.svelte +4 -2
  15. package/dist/components/preview/ImageOutput.svelte +427 -68
  16. package/dist/components/preview/InputControl.svelte +11 -2
  17. package/dist/components/preview/OutputDisplay.svelte +17 -12
  18. package/dist/components/preview/TabContent.svelte +3 -0
  19. package/dist/components/preview/inputs/ColorInput.svelte +3 -1
  20. package/dist/components/preview/inputs/ColorInput.svelte.d.ts +1 -0
  21. package/dist/components/preview/inputs/DropdownInput.svelte +4 -1
  22. package/dist/components/preview/inputs/FileInput.svelte +50 -34
  23. package/dist/components/preview/inputs/NumberInput.svelte +92 -10
  24. package/dist/components/preview/inputs/NumberInput.svelte.d.ts +2 -0
  25. package/dist/components/preview/inputs/TextInput.svelte +8 -3
  26. package/dist/components/primitives/CalculateButton.svelte +49 -23
  27. package/dist/components/primitives/CalculateButton.svelte.d.ts +3 -0
  28. package/dist/components/primitives/SolvingIndicator.svelte +21 -17
  29. package/dist/components/primitives/badge/badge.svelte +2 -2
  30. package/dist/components/primitives/badge/badge.svelte.d.ts +4 -4
  31. package/dist/components/primitives/button/button.svelte +2 -2
  32. package/dist/components/primitives/button/button.svelte.d.ts +4 -4
  33. package/dist/components/primitives/checkbox/checkbox.svelte +1 -1
  34. package/dist/components/primitives/dialog/dialog-content.svelte +6 -2
  35. package/dist/components/primitives/input/input.svelte +2 -2
  36. package/dist/components/primitives/input-group/input-group.svelte +1 -1
  37. package/dist/components/primitives/mode-toggle/mode-toggle.svelte +4 -1
  38. package/dist/components/primitives/scroll-area/scroll-area.svelte +1 -1
  39. package/dist/components/primitives/search/search.svelte +1 -1
  40. package/dist/components/primitives/select/select-trigger.svelte +1 -1
  41. package/dist/components/primitives/slider/slider.svelte +7 -2
  42. package/dist/components/primitives/slider/slider.svelte.d.ts +5 -1
  43. package/dist/components/primitives/switch/switch.svelte +1 -1
  44. package/dist/components/primitives/tabs/tabs-trigger.svelte +1 -1
  45. package/dist/components/primitives/tabs/tabs-trigger.svelte.d.ts +1 -1
  46. package/dist/components/primitives/textarea/textarea.svelte +1 -1
  47. package/dist/components/primitives/toggle/toggle.svelte +1 -1
  48. package/dist/components/primitives/toggle/toggle.svelte.d.ts +1 -1
  49. package/dist/components/viewer/Viewer.svelte +20 -17
  50. package/dist/compute/run-panel-action.d.ts +8 -0
  51. package/dist/compute/run-panel-action.js +12 -0
  52. package/dist/compute/useSolveSession.svelte.js +3 -0
  53. package/dist/i18n/localeContext.svelte.d.ts +10 -3
  54. package/dist/i18n/localeContext.svelte.js +27 -5
  55. package/dist/i18n/messages.d.ts +115 -2
  56. package/dist/i18n/messages.js +199 -11
  57. package/dist/index.d.ts +1 -1
  58. package/dist/public.d.ts +1 -1
  59. package/dist/schema/visibility-rules.js +21 -0
  60. package/dist/styles/base.css +9 -6
  61. package/dist/types/actionButton.d.ts +17 -3
  62. package/dist/types/presetLabels.d.ts +3 -2
  63. package/dist/types/presetLabels.js +9 -8
  64. package/dist/utils/loadScript.d.ts +2 -0
  65. package/dist/utils/loadScript.js +2 -0
  66. package/package.json +8 -8
  67. package/src/lib/components/ErrorScreen.svelte +6 -2
  68. package/src/lib/components/compute/AppLayout.svelte +163 -74
  69. package/src/lib/components/compute/ComputeApp.svelte +40 -20
  70. package/src/lib/components/compute/ComputeMessagesDialog.svelte +30 -40
  71. package/src/lib/components/compute/ParameterPresetManager.svelte +7 -44
  72. package/src/lib/components/compute/SolveMessages.svelte +30 -37
  73. package/src/lib/components/layout/PageFooter.svelte +21 -11
  74. package/src/lib/components/preview/ChartOutput.svelte +17 -8
  75. package/src/lib/components/preview/Group.svelte +33 -18
  76. package/src/lib/components/preview/HelpButton.svelte +4 -2
  77. package/src/lib/components/preview/ImageOutput.svelte +427 -68
  78. package/src/lib/components/preview/InputControl.svelte +11 -2
  79. package/src/lib/components/preview/OutputDisplay.svelte +17 -12
  80. package/src/lib/components/preview/TabContent.svelte +3 -0
  81. package/src/lib/components/preview/inputs/ColorInput.svelte +3 -1
  82. package/src/lib/components/preview/inputs/DropdownInput.svelte +4 -1
  83. package/src/lib/components/preview/inputs/FileInput.svelte +50 -34
  84. package/src/lib/components/preview/inputs/NumberInput.svelte +92 -10
  85. package/src/lib/components/preview/inputs/TextInput.svelte +8 -3
  86. package/src/lib/components/primitives/CalculateButton.svelte +49 -23
  87. package/src/lib/components/primitives/SolvingIndicator.svelte +21 -17
  88. package/src/lib/components/primitives/badge/badge.svelte +2 -2
  89. package/src/lib/components/primitives/button/button.svelte +2 -2
  90. package/src/lib/components/primitives/checkbox/checkbox.svelte +1 -1
  91. package/src/lib/components/primitives/dialog/dialog-content.svelte +6 -2
  92. package/src/lib/components/primitives/input/input.svelte +2 -2
  93. package/src/lib/components/primitives/input-group/input-group.svelte +1 -1
  94. package/src/lib/components/primitives/mode-toggle/mode-toggle.svelte +4 -1
  95. package/src/lib/components/primitives/scroll-area/scroll-area.svelte +1 -1
  96. package/src/lib/components/primitives/search/search.svelte +1 -1
  97. package/src/lib/components/primitives/select/select-trigger.svelte +1 -1
  98. package/src/lib/components/primitives/slider/slider.svelte +7 -2
  99. package/src/lib/components/primitives/switch/switch.svelte +1 -1
  100. package/src/lib/components/primitives/tabs/tabs-trigger.svelte +1 -1
  101. package/src/lib/components/primitives/textarea/textarea.svelte +1 -1
  102. package/src/lib/components/primitives/toggle/toggle.svelte +1 -1
  103. package/src/lib/components/viewer/Viewer.svelte +20 -17
  104. package/src/lib/compute/run-panel-action.ts +16 -0
  105. package/src/lib/compute/useSolveSession.svelte.ts +3 -0
  106. package/src/lib/i18n/localeContext.svelte.ts +40 -5
  107. package/src/lib/i18n/messages.ts +351 -13
  108. package/src/lib/index.ts +1 -0
  109. package/src/lib/public.ts +1 -0
  110. package/src/lib/schema/visibility-rules.ts +23 -0
  111. package/src/lib/styles/base.css +9 -6
  112. package/src/lib/types/actionButton.ts +16 -3
  113. package/src/lib/types/presetLabels.ts +13 -11
  114. package/src/lib/utils/loadScript.ts +3 -0
@@ -11,21 +11,25 @@
11
11
  const t = $derived(locale.messages);
12
12
  </script>
13
13
 
14
- {#if show}
15
- <div
16
- class="bottom-6 left-6 gap-3 px-5 py-3 shadow-xl animate-in fade-in slide-in-from-left fixed z-50 flex items-center rounded-lg border border-primary/20 bg-primary text-primary-foreground duration-200"
17
- >
18
- <div class="h-5 w-5 relative flex shrink-0 items-center justify-center">
19
- <div
20
- class="animate-ping absolute h-full w-full rounded-full bg-primary-foreground opacity-30"
21
- ></div>
22
- <div
23
- class="h-3 w-3 animate-spin rounded-full border-2 border-primary-foreground border-t-transparent"
24
- ></div>
14
+ <!-- The live region stays mounted: a screen reader only announces text that changes inside a
15
+ region it already knows about, so one created together with its text says nothing. -->
16
+ <div role="status" aria-live="polite" aria-atomic="true">
17
+ {#if show}
18
+ <div
19
+ class="bottom-6 left-6 gap-3 px-5 py-3 shadow-xl animate-in fade-in slide-in-from-left fixed z-50 flex items-center rounded-lg border border-primary/20 bg-primary text-primary-foreground duration-200"
20
+ >
21
+ <div class="h-5 w-5 relative flex shrink-0 items-center justify-center" aria-hidden="true">
22
+ <div
23
+ class="animate-ping absolute h-full w-full rounded-full bg-primary-foreground opacity-30"
24
+ ></div>
25
+ <div
26
+ class="h-3 w-3 animate-spin rounded-full border-2 border-primary-foreground border-t-transparent"
27
+ ></div>
28
+ </div>
29
+ <div class="leading-tight flex flex-col">
30
+ <span class="text-sm font-semibold">{t.solving}</span>
31
+ <span class="text-xs opacity-70">{t.solvingSubtitle}</span>
32
+ </div>
25
33
  </div>
26
- <div class="leading-tight flex flex-col">
27
- <span class="text-sm font-semibold">{t.solving}</span>
28
- <span class="text-xs opacity-70">{t.solvingSubtitle}</span>
29
- </div>
30
- </div>
31
- {/if}
34
+ {/if}
35
+ </div>
@@ -2,14 +2,14 @@
2
2
  import { type VariantProps, tv } from 'tailwind-variants';
3
3
 
4
4
  export const badgeVariants = tv({
5
- base: 'focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border px-2 py-0.5 text-xs font-medium whitespace-nowrap transition focus-visible:ring-3 [&>svg]:pointer-events-none [&>svg]:size-3',
5
+ base: 'focus-visible:border-ring focus-visible:ring-ring aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border px-2 py-0.5 text-xs font-medium whitespace-nowrap transition focus-visible:ring-3 [&>svg]:pointer-events-none [&>svg]:size-3',
6
6
  variants: {
7
7
  variant: {
8
8
  default: 'bg-primary text-primary-foreground [a&]:hover:bg-primary/90 border-transparent',
9
9
  secondary:
10
10
  'bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90 border-transparent',
11
11
  destructive:
12
- 'bg-destructive [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/70 border-transparent text-destructive-foreground',
12
+ 'bg-destructive [a&]:hover:bg-destructive/90 dark:bg-destructive/70 border-transparent text-destructive-foreground',
13
13
  outline: 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground'
14
14
  }
15
15
  },
@@ -3,21 +3,21 @@ export declare const badgeVariants: import("tailwind-variants").TVReturnType<{
3
3
  variant: {
4
4
  default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90 border-transparent";
5
5
  secondary: "bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90 border-transparent";
6
- destructive: "bg-destructive [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/70 border-transparent text-destructive-foreground";
6
+ destructive: "bg-destructive [a&]:hover:bg-destructive/90 dark:bg-destructive/70 border-transparent text-destructive-foreground";
7
7
  outline: "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground";
8
8
  };
9
- }, undefined, "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border px-2 py-0.5 text-xs font-medium whitespace-nowrap transition focus-visible:ring-3 [&>svg]:pointer-events-none [&>svg]:size-3", {
9
+ }, undefined, "focus-visible:border-ring focus-visible:ring-ring aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border px-2 py-0.5 text-xs font-medium whitespace-nowrap transition focus-visible:ring-3 [&>svg]:pointer-events-none [&>svg]:size-3", {
10
10
  variant: {
11
11
  default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90 border-transparent";
12
12
  secondary: "bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90 border-transparent";
13
- destructive: "bg-destructive [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/70 border-transparent text-destructive-foreground";
13
+ destructive: "bg-destructive [a&]:hover:bg-destructive/90 dark:bg-destructive/70 border-transparent text-destructive-foreground";
14
14
  outline: "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground";
15
15
  };
16
16
  }, undefined, import("tailwind-variants").TVReturnTypeLike<{
17
17
  variant: {
18
18
  default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90 border-transparent";
19
19
  secondary: "bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90 border-transparent";
20
- destructive: "bg-destructive [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/70 border-transparent text-destructive-foreground";
20
+ destructive: "bg-destructive [a&]:hover:bg-destructive/90 dark:bg-destructive/70 border-transparent text-destructive-foreground";
21
21
  outline: "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground";
22
22
  };
23
23
  }, undefined>>;
@@ -4,12 +4,12 @@
4
4
  import { type VariantProps, tv } from 'tailwind-variants';
5
5
 
6
6
  export const buttonVariants = tv({
7
- base: "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium outline-none transition-all focus-visible:ring-3 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
7
+ base: "focus-visible:border-ring focus-visible:ring-ring aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium outline-none transition-all focus-visible:ring-3 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
8
8
  variants: {
9
9
  variant: {
10
10
  default: 'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90',
11
11
  destructive:
12
- 'bg-destructive shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 text-destructive-foreground',
12
+ 'bg-destructive shadow-xs hover:bg-destructive/90 dark:bg-destructive/60 text-destructive-foreground',
13
13
  outline:
14
14
  'bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 border',
15
15
  secondary: 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',
@@ -4,7 +4,7 @@ import { type VariantProps } from 'tailwind-variants';
4
4
  export declare const buttonVariants: import("tailwind-variants").TVReturnType<{
5
5
  variant: {
6
6
  default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90";
7
- destructive: "bg-destructive shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 text-destructive-foreground";
7
+ destructive: "bg-destructive shadow-xs hover:bg-destructive/90 dark:bg-destructive/60 text-destructive-foreground";
8
8
  outline: "bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 border";
9
9
  secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80";
10
10
  ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50";
@@ -20,10 +20,10 @@ export declare const buttonVariants: import("tailwind-variants").TVReturnType<{
20
20
  'icon-sm': "size-8";
21
21
  'icon-lg': "size-10";
22
22
  };
23
- }, undefined, "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium outline-none transition-all focus-visible:ring-3 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", {
23
+ }, undefined, "focus-visible:border-ring focus-visible:ring-ring aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium outline-none transition-all focus-visible:ring-3 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", {
24
24
  variant: {
25
25
  default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90";
26
- destructive: "bg-destructive shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 text-destructive-foreground";
26
+ destructive: "bg-destructive shadow-xs hover:bg-destructive/90 dark:bg-destructive/60 text-destructive-foreground";
27
27
  outline: "bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 border";
28
28
  secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80";
29
29
  ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50";
@@ -42,7 +42,7 @@ export declare const buttonVariants: import("tailwind-variants").TVReturnType<{
42
42
  }, undefined, import("tailwind-variants").TVReturnTypeLike<{
43
43
  variant: {
44
44
  default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90";
45
- destructive: "bg-destructive shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 text-destructive-foreground";
45
+ destructive: "bg-destructive shadow-xs hover:bg-destructive/90 dark:bg-destructive/60 text-destructive-foreground";
46
46
  outline: "bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 border";
47
47
  secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80";
48
48
  ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50";
@@ -17,7 +17,7 @@
17
17
  bind:ref
18
18
  data-slot="checkbox"
19
19
  class={cn(
20
- 'peer size-4 shadow-xs flex shrink-0 items-center justify-center rounded-sm border border-input transition-shadow outline-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:bg-input/30 dark:aria-invalid:ring-destructive/40 dark:data-[state=checked]:bg-primary',
20
+ 'peer size-4 shadow-xs flex shrink-0 items-center justify-center rounded-sm border border-input transition-shadow outline-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:bg-input/30 dark:aria-invalid:ring-destructive/40 dark:data-[state=checked]:bg-primary',
21
21
  className
22
22
  )}
23
23
  bind:checked
@@ -4,6 +4,7 @@
4
4
  import type { Snippet } from 'svelte';
5
5
  import * as Dialog from './index.js';
6
6
  import { cn, type WithoutChildrenOrChild } from '../../../utils.js';
7
+ import { getLocaleContext } from '../../../i18n/localeContext.svelte';
7
8
 
8
9
  let {
9
10
  ref = $bindable(null),
@@ -20,6 +21,8 @@
20
21
  /** `flush` drops padding and gap, for content that draws its own sections edge to edge. */
21
22
  variant?: 'default' | 'flush';
22
23
  } = $props();
24
+
25
+ const locale = getLocaleContext();
23
26
  </script>
24
27
 
25
28
  <Dialog.Portal {...portalProps}>
@@ -36,11 +39,12 @@
36
39
  >
37
40
  {@render children?.()}
38
41
  {#if showCloseButton}
42
+ <!-- 24px around the 16px icon, and 44px of hit area on touch, without moving the icon. -->
39
43
  <DialogPrimitive.Close
40
- class="end-4 top-4 rounded-xs [&_svg:not([class*='size-'])]:size-4 absolute opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0"
44
+ class="end-3 top-3 p-1 rounded-xs [&_svg:not([class*='size-'])]:size-4 pointer-coarse:after:-inset-2.5 absolute opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none pointer-coarse:after:absolute [&_svg]:pointer-events-none [&_svg]:shrink-0"
41
45
  >
42
46
  <XIcon />
43
- <span class="sr-only">Close</span>
47
+ <span class="sr-only">{locale.messages.close}</span>
44
48
  </DialogPrimitive.Close>
45
49
  {/if}
46
50
  </DialogPrimitive.Content>
@@ -26,7 +26,7 @@
26
26
  data-slot={dataSlot}
27
27
  class={cn(
28
28
  'h-9 min-w-0 px-3 pt-1.5 text-sm font-medium shadow-xs flex w-full rounded-md border border-input bg-transparent ring-offset-background transition outline-none selection:bg-primary selection:text-primary-foreground placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50 dark:bg-input/30',
29
- 'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50',
29
+ 'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring',
30
30
  'aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40',
31
31
  className
32
32
  )}
@@ -41,7 +41,7 @@
41
41
  data-slot={dataSlot}
42
42
  class={cn(
43
43
  'h-9 min-w-0 px-3 py-1 text-base shadow-xs md:text-sm flex w-full rounded-md border border-input bg-background ring-offset-background transition outline-none selection:bg-primary selection:text-primary-foreground placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50 dark:bg-input/30',
44
- 'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50',
44
+ 'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring',
45
45
  'aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40',
46
46
  className
47
47
  )}
@@ -15,7 +15,7 @@
15
15
  data-slot="input-group"
16
16
  role="group"
17
17
  class={cn(
18
- 'group/input-group h-9 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5 min-w-0 shadow-xs relative flex w-full items-center rounded-md border border-input bg-background transition-colors outline-none in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-disabled:bg-input/50 has-disabled:opacity-50 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>textarea]:h-auto dark:bg-input/30 dark:has-disabled:bg-input/80 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40',
18
+ 'group/input-group h-9 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5 min-w-0 shadow-xs relative flex w-full items-center rounded-md border border-input bg-background transition-colors outline-none in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-disabled:bg-input/50 has-disabled:opacity-50 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot=input-group-control]:focus-visible]:ring-ring has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>textarea]:h-auto dark:bg-input/30 dark:has-disabled:bg-input/80 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40',
19
19
  className
20
20
  )}
21
21
  {...props}
@@ -3,10 +3,13 @@
3
3
  import Moon from '@lucide/svelte/icons/moon';
4
4
  import { toggleMode } from 'mode-watcher';
5
5
  import { Button } from '../button';
6
+ import { getLocaleContext } from '../../../i18n/localeContext.svelte';
7
+
8
+ const locale = getLocaleContext();
6
9
  </script>
7
10
 
8
11
  <Button onclick={toggleMode} variant="outline" size="icon">
9
12
  <Sun class="size-5 scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90" />
10
13
  <Moon class="size-5 absolute scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0" />
11
- <span class="sr-only">Toggle theme</span>
14
+ <span class="sr-only">{locale.messages.toggleTheme}</span>
12
15
  </Button>
@@ -29,7 +29,7 @@
29
29
  <ScrollAreaPrimitive.Viewport
30
30
  bind:ref={viewportRef}
31
31
  data-slot="scroll-area-viewport"
32
- class="size-full rounded-inherit ring-ring/10 outline-ring/50 transition focus-visible:ring-4 focus-visible:outline-1 dark:ring-ring/20 dark:outline-ring/40"
32
+ class="size-full rounded-inherit ring-ring/10 outline-ring transition focus-visible:ring-4 focus-visible:outline-1 dark:ring-ring/20"
33
33
  >
34
34
  {@render children?.()}
35
35
  </ScrollAreaPrimitive.Viewport>
@@ -46,7 +46,7 @@
46
46
  data-slot="input"
47
47
  class={cn(
48
48
  'h-9 min-w-0 px-3 py-1 text-base shadow-xs md:text-sm flex w-full rounded-md border border-input bg-background ring-offset-background transition outline-none selection:bg-primary selection:text-primary-foreground placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50 dark:bg-input/30',
49
- 'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50',
49
+ 'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring',
50
50
  'aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40',
51
51
  'pl-9 pr-12',
52
52
  className
@@ -19,7 +19,7 @@
19
19
  data-slot="select-trigger"
20
20
  data-size={size}
21
21
  class={cn(
22
- "gap-2 px-3 py-2 text-sm shadow-xs data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:gap-2 [&_svg:not([class*='size-'])]:size-4 flex w-fit items-center justify-between rounded-md border border-input bg-transparent whitespace-nowrap transition outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[placeholder]:text-muted-foreground *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='text-'])]:text-muted-foreground",
22
+ "gap-2 px-3 py-2 text-sm shadow-xs data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:gap-2 [&_svg:not([class*='size-'])]:size-4 flex w-fit items-center justify-between rounded-md border border-input bg-transparent whitespace-nowrap transition outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[placeholder]:text-muted-foreground *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='text-'])]:text-muted-foreground",
23
23
  className
24
24
  )}
25
25
  {...restProps}
@@ -7,8 +7,12 @@
7
7
  value = $bindable(),
8
8
  orientation = 'horizontal',
9
9
  class: className,
10
+ thumbLabelledby,
10
11
  ...restProps
11
- }: WithoutChildrenOrChild<SliderPrimitive.RootProps> = $props();
12
+ }: WithoutChildrenOrChild<SliderPrimitive.RootProps> & {
13
+ /** Id of the element naming the thumbs, which carry the slider role. */
14
+ thumbLabelledby?: string;
15
+ } = $props();
12
16
  </script>
13
17
 
14
18
  <!--
@@ -45,7 +49,8 @@ get along, so we shut typescript up by casting `value` to `never`.
45
49
  <SliderPrimitive.Thumb
46
50
  data-slot="slider-thumb"
47
51
  index={thumb}
48
- class="size-4 shadow-sm block shrink-0 rounded-full border border-primary bg-background ring-ring/50 transition hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50"
52
+ aria-labelledby={thumbLabelledby}
53
+ class="size-4 shadow-sm block shrink-0 rounded-full border border-primary bg-background ring-ring/50 transition hover:ring-4 focus-visible:ring-4 focus-visible:ring-ring focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50"
49
54
  />
50
55
  {/each}
51
56
  {/snippet}
@@ -1,5 +1,9 @@
1
1
  import { Slider as SliderPrimitive } from 'bits-ui';
2
2
  import { type WithoutChildrenOrChild } from '../../../utils.js';
3
- declare const Slider: import("svelte").Component<WithoutChildrenOrChild<SliderPrimitive.RootProps>, {}, "ref" | "value">;
3
+ type $$ComponentProps = WithoutChildrenOrChild<SliderPrimitive.RootProps> & {
4
+ /** Id of the element naming the thumbs, which carry the slider role. */
5
+ thumbLabelledby?: string;
6
+ };
7
+ declare const Slider: import("svelte").Component<$$ComponentProps, {}, "ref" | "value">;
4
8
  type Slider = ReturnType<typeof Slider>;
5
9
  export default Slider;
@@ -15,7 +15,7 @@
15
15
  bind:checked
16
16
  data-slot="switch"
17
17
  class={cn(
18
- 'peer w-8 shadow-xs h-4.5 inline-flex shrink-0 items-center rounded-full border border-transparent transition-all outline-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input dark:data-[state=unchecked]:bg-input/80',
18
+ 'peer w-8 shadow-xs h-4.5 inline-flex shrink-0 items-center rounded-full border border-transparent transition-all outline-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input dark:data-[state=unchecked]:bg-input/80',
19
19
  className
20
20
  )}
21
21
  {...restProps}
@@ -2,7 +2,7 @@
2
2
  import { type VariantProps, tv } from 'tailwind-variants';
3
3
 
4
4
  export const tabsTriggerVariants = tv({
5
- base: "gap-1.5 text-sm font-medium [&_svg:not([class*='size-'])]:size-4 inline-flex items-center justify-center whitespace-nowrap focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
5
+ base: "gap-1.5 text-sm font-medium [&_svg:not([class*='size-'])]:size-4 inline-flex items-center justify-center whitespace-nowrap focus-visible:ring-3 focus-visible:ring-ring focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
6
6
  variants: {
7
7
  variant: {
8
8
  default:
@@ -4,7 +4,7 @@ export declare const tabsTriggerVariants: import("tailwind-variants").TVReturnTy
4
4
  default: "px-2 py-1 data-[state=active]:shadow-sm h-full flex-1 rounded-md border border-transparent text-foreground transition focus-visible:border-ring data-[state=active]:bg-background dark:text-muted-foreground dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 dark:data-[state=active]:text-foreground";
5
5
  line: "px-2.5 py-3 relative flex-none text-muted-foreground transition-colors after:absolute after:inset-x-2 after:bottom-0 after:h-0.5 after:rounded-full hover:text-foreground data-[state=active]:text-foreground data-[state=active]:after:bg-primary";
6
6
  };
7
- }, undefined, "gap-1.5 text-sm font-medium [&_svg:not([class*='size-'])]:size-4 inline-flex items-center justify-center whitespace-nowrap focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0", {
7
+ }, undefined, "gap-1.5 text-sm font-medium [&_svg:not([class*='size-'])]:size-4 inline-flex items-center justify-center whitespace-nowrap focus-visible:ring-3 focus-visible:ring-ring focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0", {
8
8
  variant: {
9
9
  default: "px-2 py-1 data-[state=active]:shadow-sm h-full flex-1 rounded-md border border-transparent text-foreground transition focus-visible:border-ring data-[state=active]:bg-background dark:text-muted-foreground dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 dark:data-[state=active]:text-foreground";
10
10
  line: "px-2.5 py-3 relative flex-none text-muted-foreground transition-colors after:absolute after:inset-x-2 after:bottom-0 after:h-0.5 after:rounded-full hover:text-foreground data-[state=active]:text-foreground data-[state=active]:after:bg-primary";
@@ -15,7 +15,7 @@
15
15
  bind:this={ref}
16
16
  data-slot={dataSlot}
17
17
  class={cn(
18
- 'min-h-16 px-3 py-2 text-base shadow-xs md:text-sm flex field-sizing-content w-full rounded-md border border-input bg-transparent transition outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:bg-input/30 dark:aria-invalid:ring-destructive/40',
18
+ 'min-h-16 px-3 py-2 text-base shadow-xs md:text-sm flex field-sizing-content w-full rounded-md border border-input bg-transparent transition outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:bg-input/30 dark:aria-invalid:ring-destructive/40',
19
19
  className
20
20
  )}
21
21
  bind:value
@@ -2,7 +2,7 @@
2
2
  import { type VariantProps, tv } from 'tailwind-variants';
3
3
 
4
4
  export const toggleVariants = tv({
5
- base: "hover:text-foreground aria-pressed:bg-muted focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive data-[state=on]:bg-muted gap-1 rounded-lg text-sm font-medium transition-all [&_svg:not([class*='size-'])]:size-4 group/toggle inline-flex items-center justify-center whitespace-nowrap outline-none hover:bg-muted focus-visible:ring-3 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
5
+ base: "hover:text-foreground aria-pressed:bg-muted focus-visible:border-ring focus-visible:ring-ring aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive data-[state=on]:bg-muted gap-1 rounded-lg text-sm font-medium transition-all [&_svg:not([class*='size-'])]:size-4 group/toggle inline-flex items-center justify-center whitespace-nowrap outline-none hover:bg-muted focus-visible:ring-3 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
6
6
  variants: {
7
7
  variant: {
8
8
  default: 'bg-transparent',
@@ -9,7 +9,7 @@ export declare const toggleVariants: import("tailwind-variants").TVReturnType<{
9
9
  sm: "h-7 min-w-7 rounded-md px-2.5 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5";
10
10
  lg: "h-9 min-w-9 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2";
11
11
  };
12
- }, undefined, "hover:text-foreground aria-pressed:bg-muted focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive data-[state=on]:bg-muted gap-1 rounded-lg text-sm font-medium transition-all [&_svg:not([class*='size-'])]:size-4 group/toggle inline-flex items-center justify-center whitespace-nowrap outline-none hover:bg-muted focus-visible:ring-3 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0", {
12
+ }, undefined, "hover:text-foreground aria-pressed:bg-muted focus-visible:border-ring focus-visible:ring-ring aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive data-[state=on]:bg-muted gap-1 rounded-lg text-sm font-medium transition-all [&_svg:not([class*='size-'])]:size-4 group/toggle inline-flex items-center justify-center whitespace-nowrap outline-none hover:bg-muted focus-visible:ring-3 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0", {
13
13
  variant: {
14
14
  default: "bg-transparent";
15
15
  outline: "border-input hover:bg-muted border bg-transparent";
@@ -39,7 +39,7 @@
39
39
  import * as DropdownMenu from '../primitives/dropdown-menu/index.js';
40
40
  import { Button } from '../primitives/button/index.js';
41
41
  import type { Locale } from '../../i18n/messages';
42
- import { setLocaleContext, getLocaleContext } from '../../i18n/localeContext.svelte';
42
+ import { layerLocaleContext } from '../../i18n/localeContext.svelte';
43
43
 
44
44
  export interface ViewerConfig {
45
45
  showScreenshotButton?: boolean;
@@ -103,12 +103,8 @@
103
103
 
104
104
  const config = $derived({ ...defaultViewerConfig, ...viewerConfig });
105
105
 
106
- // Read the host's locale before overriding it for our subtree.
107
- const hostLocale = getLocaleContext();
108
-
109
106
  // A getter, not a value: it is re-read reactively, so changing `lang` updates the chrome live.
110
- setLocaleContext(() => lang ?? hostLocale.locale);
111
- const locale = getLocaleContext();
107
+ const locale = layerLocaleContext(() => lang);
112
108
  const t = $derived(locale.messages);
113
109
 
114
110
  let canvas: HTMLCanvasElement;
@@ -146,16 +142,23 @@
146
142
  let selectedMeshMetadata: Record<string, any> | null = $state(null);
147
143
  let selectedMeshName: string | null = $state(null);
148
144
 
149
- // Derived from LOOKS so adding a look in @selvajs/visualization shows up here with no edit; the
150
- // map only overrides the names that don't survive capitalisation ('xray' → 'X-Ray').
151
- const LOOK_LABELS: Partial<Record<Look, string>> = {
152
- xray: 'X-Ray',
153
- lineart: 'Line Art'
154
- };
155
- const STYLE_OPTIONS: { look: Look; label: string }[] = LOOKS.map((look) => ({
156
- look,
157
- label: LOOK_LABELS[look] ?? look.charAt(0).toUpperCase() + look.slice(1)
158
- }));
145
+ // Derived from LOOKS so a look added in @selvajs/visualization shows up here with no edit,
146
+ // under its capitalised name until it gets a message.
147
+ const lookLabels = $derived<Partial<Record<Look, string>>>({
148
+ technical: t.lookTechnical,
149
+ studio: t.lookStudio,
150
+ showcase: t.lookShowcase,
151
+ arctic: t.lookArctic,
152
+ xray: t.lookXray,
153
+ lineart: t.lookLineart,
154
+ wireframe: t.lookWireframe
155
+ });
156
+ const styleOptions = $derived<{ look: Look; label: string }[]>(
157
+ LOOKS.map((look) => ({
158
+ look,
159
+ label: lookLabels[look] ?? look.charAt(0).toUpperCase() + look.slice(1)
160
+ }))
161
+ );
159
162
 
160
163
  const VIEW_PRESETS: { preset: ViewPreset; label: () => string }[] = [
161
164
  { preset: 'top', label: () => t.viewTop },
@@ -527,7 +530,7 @@
527
530
  value={renderStyle}
528
531
  onValueChange={(look) => setRenderStyle(look as typeof renderStyle)}
529
532
  >
530
- {#each STYLE_OPTIONS as { look, label } (look)}
533
+ {#each styleOptions as { look, label } (look)}
531
534
  <DropdownMenu.RadioItem value={look} closeOnSelect={false}>
532
535
  {label}
533
536
  </DropdownMenu.RadioItem>
@@ -0,0 +1,8 @@
1
+ import type { SolveSettlement } from '@selvajs/solve/client';
2
+ import type { ActionButton } from '../types/actionButton';
3
+ /**
4
+ * Runs a footer-menu action. A `solvesFirst` action waits for the result for the current
5
+ * inputs and runs only on an applied one; a refused or failed solve has already shown the user
6
+ * why. A host that can't solve (`ensureSolved` undefined) runs it straight away.
7
+ */
8
+ export declare function runPanelAction(action: ActionButton, ensureSolved: (() => Promise<SolveSettlement>) | undefined): Promise<void>;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Runs a footer-menu action. A `solvesFirst` action waits for the result for the current
3
+ * inputs and runs only on an applied one; a refused or failed solve has already shown the user
4
+ * why. A host that can't solve (`ensureSolved` undefined) runs it straight away.
5
+ */
6
+ export async function runPanelAction(action, ensureSolved) {
7
+ if (!action.solvesFirst || !ensureSolved)
8
+ return action.onclick();
9
+ const settled = await ensureSolved();
10
+ if (settled.status === 'applied')
11
+ await action.onclick(settled);
12
+ }
@@ -79,10 +79,13 @@ export function useSolveSession(args) {
79
79
  discard: () => session.discard(),
80
80
  setValue: (id, value, forceSolve) => session.setValue(id, value, forceSolve),
81
81
  solve: () => session.solve(),
82
+ ensureSolved: () => session.ensureSolved(),
83
+ onSettled: (listener) => session.onSettled(listener),
82
84
  loadValues: (incoming) => session.loadValues(incoming),
83
85
  rebuild: (schema, scopeKey) => session.rebuild(schema, scopeKey),
84
86
  report: (result) => session.report(result),
85
87
  reportError: (message) => session.reportError(message),
88
+ reportAborted: () => session.reportAborted(),
86
89
  subscribe: (listener) => session.subscribe(listener),
87
90
  notify: () => session.notify(),
88
91
  dispose: () => session.dispose()
@@ -4,11 +4,18 @@ export interface LocaleContext {
4
4
  readonly messages: ViewerMessages;
5
5
  }
6
6
  /**
7
- * Provide the locale to descendants. Pass a getter, not a value: it is re-read on every
7
+ * Provide the locale to descendants. Pass getters, not values: they are re-read on every
8
8
  * consumer render, so a reactive source (a `$state`, the app's Paraglide locale) switches
9
- * the language live.
9
+ * the language live. `getOverrides` replaces individual strings of the resolved catalog.
10
10
  */
11
- export declare function setLocaleContext(getLocale: () => Locale | undefined): void;
11
+ export declare function setLocaleContext(getLocale: () => Locale | undefined, getOverrides?: () => Partial<ViewerMessages> | undefined): void;
12
+ /**
13
+ * Re-provides the nearest context for a component that takes its own `lang`/`messages` props.
14
+ * Without `lang`, or with the inherited locale, the subtree keeps the overrides an outer provider
15
+ * applied; a different `lang` starts from that locale's catalog, since those overrides were
16
+ * written for another language.
17
+ */
18
+ export declare function layerLocaleContext(getLang: () => Locale | undefined, getOverrides?: () => Partial<ViewerMessages> | undefined): LocaleContext;
12
19
  /**
13
20
  * Falls back to an English-only context when no provider exists (a primitive used in
14
21
  * isolation), so consumers never need a null check.
@@ -1,10 +1,11 @@
1
1
  import { getContext, setContext } from 'svelte';
2
- import { messagesFor, DEFAULT_LOCALE } from './messages';
2
+ import { messagesFor, withOverrides, DEFAULT_LOCALE } from './messages';
3
3
  // Carries the UI locale down to the viewer and its panels without threading a
4
4
  // `lang` prop through every layer.
5
5
  //
6
6
  // Resolution order for any consuming component:
7
7
  // explicit `lang` prop → nearest locale context → English default
8
+ // and the strings are that locale's catalog with any `messages` overrides on top.
8
9
  //
9
10
  // Two ways to provide it:
10
11
  // - Standalone: <Viewer lang="de" /> — Viewer provides the context itself.
@@ -12,21 +13,42 @@ import { messagesFor, DEFAULT_LOCALE } from './messages';
12
13
  // viewer (and anything else) reads it.
13
14
  const LOCALE_CONTEXT_KEY = Symbol('viewer-locale-context');
14
15
  /**
15
- * Provide the locale to descendants. Pass a getter, not a value: it is re-read on every
16
+ * Provide the locale to descendants. Pass getters, not values: they are re-read on every
16
17
  * consumer render, so a reactive source (a `$state`, the app's Paraglide locale) switches
17
- * the language live.
18
+ * the language live. `getOverrides` replaces individual strings of the resolved catalog.
18
19
  */
19
- export function setLocaleContext(getLocale) {
20
+ export function setLocaleContext(getLocale, getOverrides) {
20
21
  const ctx = {
21
22
  get locale() {
22
23
  return getLocale() ?? DEFAULT_LOCALE;
23
24
  },
24
25
  get messages() {
25
- return messagesFor(getLocale());
26
+ return withOverrides(messagesFor(getLocale()), getOverrides?.());
26
27
  }
27
28
  };
28
29
  setContext(LOCALE_CONTEXT_KEY, ctx);
29
30
  }
31
+ /**
32
+ * Re-provides the nearest context for a component that takes its own `lang`/`messages` props.
33
+ * Without `lang`, or with the inherited locale, the subtree keeps the overrides an outer provider
34
+ * applied; a different `lang` starts from that locale's catalog, since those overrides were
35
+ * written for another language.
36
+ */
37
+ export function layerLocaleContext(getLang, getOverrides) {
38
+ const outer = getLocaleContext();
39
+ const ctx = {
40
+ get locale() {
41
+ return getLang() ?? outer.locale;
42
+ },
43
+ get messages() {
44
+ const lang = getLang();
45
+ const base = lang === undefined || lang === outer.locale ? outer.messages : messagesFor(lang);
46
+ return withOverrides(base, getOverrides?.());
47
+ }
48
+ };
49
+ setContext(LOCALE_CONTEXT_KEY, ctx);
50
+ return ctx;
51
+ }
30
52
  /**
31
53
  * Falls back to an English-only context when no provider exists (a primitive used in
32
54
  * isolation), so consumers never need a null check.