@joewinke/jatui 0.1.33 → 0.2.1

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 (221) hide show
  1. package/CLAUDE.md +22 -0
  2. package/dist/actions/attention.d.ts +24 -0
  3. package/dist/actions/attention.js +94 -0
  4. package/dist/actions/floating.d.ts +53 -0
  5. package/dist/actions/floating.js +158 -0
  6. package/dist/actions/staggerEntrance.d.ts +28 -0
  7. package/dist/actions/staggerEntrance.js +71 -0
  8. package/dist/actions/tooltip.d.ts +28 -0
  9. package/dist/actions/tooltip.js +186 -0
  10. package/dist/assets/sounds/README.md +47 -0
  11. package/dist/assets/sounds/error.d.ts +1 -0
  12. package/dist/assets/sounds/error.js +4 -0
  13. package/dist/assets/sounds/notify.d.ts +1 -0
  14. package/dist/assets/sounds/notify.js +4 -0
  15. package/dist/assets/sounds/pop.d.ts +1 -0
  16. package/dist/assets/sounds/pop.js +4 -0
  17. package/dist/assets/sounds/send.d.ts +1 -0
  18. package/dist/assets/sounds/send.js +4 -0
  19. package/dist/assets/sounds/success.d.ts +1 -0
  20. package/dist/assets/sounds/success.js +4 -0
  21. package/dist/assets/sounds/toggle.d.ts +1 -0
  22. package/dist/assets/sounds/toggle.js +4 -0
  23. package/dist/carousel/index.d.ts +1 -0
  24. package/dist/carousel/index.js +2 -0
  25. package/dist/components/AnimatedNumber.svelte +112 -0
  26. package/dist/components/AnimatedNumber.svelte.d.ts +8 -0
  27. package/dist/components/Badge.svelte +75 -4
  28. package/dist/components/Badge.svelte.d.ts +6 -1
  29. package/dist/components/Button.svelte +196 -21
  30. package/dist/components/Button.svelte.d.ts +10 -1
  31. package/dist/components/Carousel.svelte +265 -0
  32. package/dist/components/Carousel.svelte.d.ts +21 -0
  33. package/dist/components/ChipInput.svelte +63 -3
  34. package/dist/components/Collapse.svelte +70 -14
  35. package/dist/components/Collapse.svelte.d.ts +11 -0
  36. package/dist/components/ColorSelector.svelte +21 -24
  37. package/dist/components/CommandPalette.svelte +475 -0
  38. package/dist/components/CommandPalette.svelte.d.ts +3 -0
  39. package/dist/components/ContextMenu.svelte +9 -73
  40. package/dist/components/CopyButton.svelte +156 -0
  41. package/dist/components/CopyButton.svelte.d.ts +12 -0
  42. package/dist/components/HoldToConfirm.svelte +183 -0
  43. package/dist/components/HoldToConfirm.svelte.d.ts +11 -0
  44. package/dist/components/KeyboardShortcutsOverlay.svelte +54 -8
  45. package/dist/components/KeyboardShortcutsOverlay.svelte.d.ts +3 -0
  46. package/dist/components/PasswordMeter.svelte +87 -0
  47. package/dist/components/PasswordMeter.svelte.d.ts +8 -0
  48. package/dist/components/PinInput.svelte +186 -0
  49. package/dist/components/PinInput.svelte.d.ts +13 -0
  50. package/dist/components/QuantityStepper.svelte +185 -0
  51. package/dist/components/QuantityStepper.svelte.d.ts +12 -0
  52. package/dist/components/Skeleton.svelte +123 -0
  53. package/dist/components/Skeleton.svelte.d.ts +12 -0
  54. package/dist/components/SpeechForm.svelte +37 -0
  55. package/dist/components/StatusBadge.svelte +31 -3
  56. package/dist/components/StatusBadge.svelte.d.ts +0 -5
  57. package/dist/components/Tabs.svelte +270 -0
  58. package/dist/components/Tabs.svelte.d.ts +15 -0
  59. package/dist/components/TextInput.svelte +138 -38
  60. package/dist/components/TextInput.svelte.d.ts +9 -1
  61. package/dist/components/VoicePlayer.svelte +51 -0
  62. package/dist/components/basic/Alert.svelte +36 -0
  63. package/dist/components/basic/Alert.svelte.d.ts +9 -0
  64. package/dist/components/basic/Breadcrumb.svelte +27 -0
  65. package/dist/components/basic/Breadcrumb.svelte.d.ts +11 -0
  66. package/dist/components/basic/ButtonGroup.svelte +15 -0
  67. package/dist/components/basic/ButtonGroup.svelte.d.ts +8 -0
  68. package/dist/components/basic/Checkbox.svelte +85 -0
  69. package/dist/components/basic/Checkbox.svelte.d.ts +15 -0
  70. package/dist/components/basic/Empty.svelte +17 -0
  71. package/dist/components/basic/Empty.svelte.d.ts +8 -0
  72. package/dist/components/basic/Kbd.svelte +16 -0
  73. package/dist/components/basic/Kbd.svelte.d.ts +8 -0
  74. package/dist/components/basic/Pagination.svelte +104 -0
  75. package/dist/components/basic/Pagination.svelte.d.ts +12 -0
  76. package/dist/components/basic/RadioGroup.svelte +71 -0
  77. package/dist/components/basic/RadioGroup.svelte.d.ts +18 -0
  78. package/dist/components/basic/Separator.svelte +19 -0
  79. package/dist/components/basic/Separator.svelte.d.ts +8 -0
  80. package/dist/components/basic/Slider.svelte +72 -0
  81. package/dist/components/basic/Slider.svelte.d.ts +17 -0
  82. package/dist/components/basic/Switch.svelte +88 -0
  83. package/dist/components/basic/Switch.svelte.d.ts +14 -0
  84. package/dist/components/basic/Toggle.svelte +43 -0
  85. package/dist/components/basic/Toggle.svelte.d.ts +14 -0
  86. package/dist/components/graph/GraphCanvas.svelte +103 -0
  87. package/dist/components/graph/GraphCanvas.svelte.d.ts +22 -0
  88. package/dist/components/graph/JatuiNode.svelte +128 -0
  89. package/dist/components/graph/JatuiNode.svelte.d.ts +14 -0
  90. package/dist/components/graph/index.d.ts +4 -0
  91. package/dist/components/graph/index.js +4 -0
  92. package/dist/components/graph/theme.d.ts +27 -0
  93. package/dist/components/graph/theme.js +136 -0
  94. package/dist/components/messaging/MessageItem.svelte +92 -0
  95. package/dist/components/messaging/MessageItem.svelte.d.ts +5 -0
  96. package/dist/components/messaging/TypingIndicator.svelte +63 -0
  97. package/dist/components/messaging/TypingIndicator.svelte.d.ts +7 -0
  98. package/dist/components/pipeline/PipelineColumn.svelte +44 -3
  99. package/dist/components/progress/ProgressBar.svelte +189 -0
  100. package/dist/components/progress/ProgressBar.svelte.d.ts +17 -0
  101. package/dist/components/progress/ProgressRing.svelte +138 -0
  102. package/dist/components/progress/ProgressRing.svelte.d.ts +12 -0
  103. package/dist/components/progress/Steps.svelte +195 -0
  104. package/dist/components/progress/Steps.svelte.d.ts +14 -0
  105. package/dist/components/shell/NavigationMenu.svelte +422 -0
  106. package/dist/components/shell/NavigationMenu.svelte.d.ts +43 -0
  107. package/dist/components/shell/Sidebar.svelte +276 -0
  108. package/dist/components/shell/Sidebar.svelte.d.ts +32 -0
  109. package/dist/components/shell/SidebarGroup.svelte +180 -0
  110. package/dist/components/shell/SidebarGroup.svelte.d.ts +19 -0
  111. package/dist/components/shell/SidebarItem.svelte +170 -0
  112. package/dist/components/shell/SidebarItem.svelte.d.ts +24 -0
  113. package/dist/components/shell/shell.d.ts +30 -0
  114. package/dist/components/shell/shell.js +65 -0
  115. package/dist/components/table/DataTable.svelte +364 -0
  116. package/dist/components/table/DataTable.svelte.d.ts +4 -0
  117. package/dist/components/table/flexRender.d.ts +31 -0
  118. package/dist/components/table/flexRender.js +32 -0
  119. package/dist/components/table/index.d.ts +3 -0
  120. package/dist/components/table/index.js +3 -0
  121. package/dist/components/table/types.d.ts +28 -0
  122. package/dist/components/table/types.js +1 -0
  123. package/dist/components/toast/ToastHost.svelte +21 -0
  124. package/dist/components/toast/ToastHost.svelte.d.ts +3 -0
  125. package/dist/components/toast/ToastItem.svelte +287 -0
  126. package/dist/components/toast/ToastItem.svelte.d.ts +9 -0
  127. package/dist/index.d.ts +51 -0
  128. package/dist/index.js +64 -0
  129. package/dist/showcase/AnimatedBeam.svelte +183 -0
  130. package/dist/showcase/AnimatedBeam.svelte.d.ts +26 -0
  131. package/dist/showcase/AuroraDrift.svelte +74 -0
  132. package/dist/showcase/AuroraDrift.svelte.d.ts +9 -0
  133. package/dist/showcase/BentoCard.svelte +50 -0
  134. package/dist/showcase/BentoCard.svelte.d.ts +14 -0
  135. package/dist/showcase/BentoGrid.svelte +43 -0
  136. package/dist/showcase/BentoGrid.svelte.d.ts +12 -0
  137. package/dist/showcase/BorderBeam.svelte +79 -0
  138. package/dist/showcase/BorderBeam.svelte.d.ts +12 -0
  139. package/dist/showcase/ClipWipe.svelte +82 -0
  140. package/dist/showcase/ClipWipe.svelte.d.ts +12 -0
  141. package/dist/showcase/Confetti.svelte +71 -0
  142. package/dist/showcase/Confetti.svelte.d.ts +10 -0
  143. package/dist/showcase/CursorSpotlight.svelte +85 -0
  144. package/dist/showcase/CursorSpotlight.svelte.d.ts +9 -0
  145. package/dist/showcase/DeviceMock.svelte +186 -0
  146. package/dist/showcase/DeviceMock.svelte.d.ts +11 -0
  147. package/dist/showcase/Dock.svelte +123 -0
  148. package/dist/showcase/Dock.svelte.d.ts +17 -0
  149. package/dist/showcase/FlipCard.svelte +103 -0
  150. package/dist/showcase/FlipCard.svelte.d.ts +10 -0
  151. package/dist/showcase/FocusReveal.svelte +83 -0
  152. package/dist/showcase/FocusReveal.svelte.d.ts +11 -0
  153. package/dist/showcase/Globe.svelte +202 -0
  154. package/dist/showcase/Globe.svelte.d.ts +16 -0
  155. package/dist/showcase/KenBurns.svelte +105 -0
  156. package/dist/showcase/KenBurns.svelte.d.ts +13 -0
  157. package/dist/showcase/Marquee.svelte +89 -0
  158. package/dist/showcase/Marquee.svelte.d.ts +9 -0
  159. package/dist/showcase/ParallaxTilt.svelte +98 -0
  160. package/dist/showcase/ParallaxTilt.svelte.d.ts +10 -0
  161. package/dist/showcase/ScrambleText.svelte +98 -0
  162. package/dist/showcase/ScrambleText.svelte.d.ts +11 -0
  163. package/dist/showcase/ShineSweep.svelte +81 -0
  164. package/dist/showcase/ShineSweep.svelte.d.ts +9 -0
  165. package/dist/showcase/Terminal.svelte +230 -0
  166. package/dist/showcase/Terminal.svelte.d.ts +18 -0
  167. package/dist/showcase/TextSplitReveal.svelte +146 -0
  168. package/dist/showcase/TextSplitReveal.svelte.d.ts +13 -0
  169. package/dist/showcase/TextWave.svelte +55 -0
  170. package/dist/showcase/TextWave.svelte.d.ts +8 -0
  171. package/dist/showcase/TypewriterText.svelte +130 -0
  172. package/dist/showcase/TypewriterText.svelte.d.ts +9 -0
  173. package/dist/showcase/backdrop/GrainGradient.svelte +126 -0
  174. package/dist/showcase/backdrop/GrainGradient.svelte.d.ts +23 -0
  175. package/dist/showcase/backdrop/MeshGradient.svelte +104 -0
  176. package/dist/showcase/backdrop/MeshGradient.svelte.d.ts +20 -0
  177. package/dist/showcase/backdrop/Metaballs.svelte +116 -0
  178. package/dist/showcase/backdrop/Metaballs.svelte.d.ts +18 -0
  179. package/dist/showcase/backdrop/NeuroNoise.svelte +115 -0
  180. package/dist/showcase/backdrop/NeuroNoise.svelte.d.ts +20 -0
  181. package/dist/showcase/backdrop/ShowcaseBackdrop.svelte +48 -0
  182. package/dist/showcase/backdrop/ShowcaseBackdrop.svelte.d.ts +12 -0
  183. package/dist/showcase/backdrop/index.d.ts +5 -0
  184. package/dist/showcase/backdrop/index.js +6 -0
  185. package/dist/showcase/generative/GenerativeBackdrop.svelte +234 -0
  186. package/dist/showcase/generative/GenerativeBackdrop.svelte.d.ts +9 -0
  187. package/dist/showcase/generative/intent.d.ts +7 -0
  188. package/dist/showcase/generative/intent.js +67 -0
  189. package/dist/showcase/generative/manifest.d.ts +9 -0
  190. package/dist/showcase/generative/manifest.js +97 -0
  191. package/dist/showcase/generative/types.d.ts +48 -0
  192. package/dist/showcase/generative/types.js +8 -0
  193. package/dist/showcase/globe/index.d.ts +1 -0
  194. package/dist/showcase/globe/index.js +2 -0
  195. package/dist/showcase/index.d.ts +25 -0
  196. package/dist/showcase/index.js +39 -0
  197. package/dist/showcase/scroll/SmoothScroll.svelte +49 -0
  198. package/dist/showcase/scroll/SmoothScroll.svelte.d.ts +12 -0
  199. package/dist/showcase/scroll/index.d.ts +5 -0
  200. package/dist/showcase/scroll/index.js +4 -0
  201. package/dist/showcase/scroll/scrollProgress.d.ts +24 -0
  202. package/dist/showcase/scroll/scrollProgress.js +65 -0
  203. package/dist/showcase/scroll/scrollReveal.d.ts +30 -0
  204. package/dist/showcase/scroll/scrollReveal.js +70 -0
  205. package/dist/stores/commandPalette.svelte.d.ts +24 -0
  206. package/dist/stores/commandPalette.svelte.js +82 -0
  207. package/dist/stores/toast.svelte.d.ts +28 -0
  208. package/dist/stores/toast.svelte.js +33 -0
  209. package/dist/styles/motion.css +46 -0
  210. package/dist/styles/tokens.css +31 -0
  211. package/dist/transitions/glide.d.ts +31 -0
  212. package/dist/transitions/glide.js +58 -0
  213. package/dist/utils/sound.d.ts +6 -0
  214. package/dist/utils/sound.js +113 -0
  215. package/dist/utils/splitText.d.ts +25 -0
  216. package/dist/utils/splitText.js +94 -0
  217. package/dist/utils/themeWipe.d.ts +21 -0
  218. package/dist/utils/themeWipe.js +94 -0
  219. package/dist/utils/viewTransition.d.ts +23 -0
  220. package/dist/utils/viewTransition.js +75 -0
  221. package/package.json +78 -4
@@ -0,0 +1,156 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ text?: string;
4
+ getText?: () => string | Promise<string>;
5
+ size?: 'xs' | 'sm' | 'md' | 'lg';
6
+ variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'error' | 'warning' | 'success' | 'info';
7
+ label?: string;
8
+ copiedLabel?: string;
9
+ class?: string;
10
+ }
11
+
12
+ let {
13
+ text,
14
+ getText,
15
+ size = 'sm',
16
+ variant = 'ghost',
17
+ label = 'Copy',
18
+ copiedLabel = 'Copied',
19
+ class: className = ''
20
+ }: Props = $props();
21
+
22
+ let copied = $state(false);
23
+ let timer: ReturnType<typeof setTimeout>;
24
+
25
+ const sizeClass = $derived(
26
+ ({ xs: 'btn-xs', sm: 'btn-sm', md: '', lg: 'btn-lg' } as Record<string, string>)[size] ?? ''
27
+ );
28
+ const variantClass = $derived(
29
+ ({
30
+ primary: 'btn-primary',
31
+ secondary: 'btn-secondary',
32
+ outline: 'btn-outline',
33
+ ghost: 'btn-ghost',
34
+ error: 'btn-error',
35
+ warning: 'btn-warning',
36
+ success: 'btn-success',
37
+ info: 'btn-info'
38
+ } as Record<string, string>)[variant] ?? 'btn-ghost'
39
+ );
40
+
41
+ async function handleCopy() {
42
+ if (copied) return;
43
+ try {
44
+ const value = getText ? await getText() : (text ?? '');
45
+ await navigator.clipboard.writeText(value);
46
+ copied = true;
47
+ clearTimeout(timer);
48
+ timer = setTimeout(() => {
49
+ copied = false;
50
+ }, 1400);
51
+ } catch {
52
+ // clipboard not available — no-op
53
+ }
54
+ }
55
+ </script>
56
+
57
+ <button
58
+ type="button"
59
+ class="btn {variantClass} {sizeClass} copy-btn {className}"
60
+ class:copied
61
+ onclick={handleCopy}
62
+ aria-label={copied ? copiedLabel : label}
63
+ >
64
+ <span class="copy-icon-wrap" aria-hidden="true">
65
+ <!-- Copy icon -->
66
+ <svg
67
+ class="ic-copy"
68
+ width="1em"
69
+ height="1em"
70
+ viewBox="0 0 24 24"
71
+ fill="none"
72
+ stroke="currentColor"
73
+ stroke-width="2"
74
+ stroke-linecap="round"
75
+ stroke-linejoin="round"
76
+ >
77
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
78
+ <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
79
+ </svg>
80
+ <!-- Check icon -->
81
+ <svg
82
+ class="ic-check"
83
+ width="1em"
84
+ height="1em"
85
+ viewBox="0 0 24 24"
86
+ fill="none"
87
+ stroke="currentColor"
88
+ stroke-width="2.5"
89
+ stroke-linecap="round"
90
+ stroke-linejoin="round"
91
+ >
92
+ <polyline points="20 6 9 17 4 12" />
93
+ </svg>
94
+ </span>
95
+ <span class="copy-label">{copied ? copiedLabel : label}</span>
96
+
97
+ <!-- Screen-reader live announcement -->
98
+ <span class="sr-only" aria-live="polite" aria-atomic="true">
99
+ {copied ? copiedLabel : ''}
100
+ </span>
101
+ </button>
102
+
103
+ <style>
104
+ .copy-btn {
105
+ gap: 0.35em;
106
+ }
107
+
108
+ .copy-icon-wrap {
109
+ position: relative;
110
+ display: inline-flex;
111
+ width: 1em;
112
+ height: 1em;
113
+ }
114
+
115
+ .copy-icon-wrap svg {
116
+ position: absolute;
117
+ inset: 0;
118
+ transition:
119
+ opacity var(--motion-fast, 150ms) var(--motion-glide, cubic-bezier(0.16, 1, 0.3, 1)),
120
+ transform var(--motion-fast, 150ms) var(--motion-spring, cubic-bezier(0.34, 1.56, 0.64, 1));
121
+ }
122
+
123
+ .ic-check {
124
+ opacity: 0;
125
+ transform: scale(0.5);
126
+ color: oklch(var(--su, 0.72 0.19 150));
127
+ }
128
+
129
+ /* Use DaisyUI success color if --su is not available */
130
+ .ic-check {
131
+ color: var(--color-success, #4cd08a);
132
+ }
133
+
134
+ /* Copied state */
135
+ .copy-btn.copied .ic-copy {
136
+ opacity: 0;
137
+ transform: scale(0.5);
138
+ }
139
+ .copy-btn.copied .ic-check {
140
+ opacity: 1;
141
+ transform: scale(1);
142
+ }
143
+
144
+ .copy-label {
145
+ transition: opacity var(--motion-fast, 150ms) var(--motion-glide, cubic-bezier(0.16, 1, 0.3, 1));
146
+ }
147
+
148
+ @media (prefers-reduced-motion: reduce) {
149
+ .copy-icon-wrap svg {
150
+ transition: opacity var(--motion-instant, 80ms) step-end;
151
+ }
152
+ .copy-label {
153
+ transition: none;
154
+ }
155
+ }
156
+ </style>
@@ -0,0 +1,12 @@
1
+ interface Props {
2
+ text?: string;
3
+ getText?: () => string | Promise<string>;
4
+ size?: 'xs' | 'sm' | 'md' | 'lg';
5
+ variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'error' | 'warning' | 'success' | 'info';
6
+ label?: string;
7
+ copiedLabel?: string;
8
+ class?: string;
9
+ }
10
+ declare const CopyButton: import("svelte").Component<Props, {}, "">;
11
+ type CopyButton = ReturnType<typeof CopyButton>;
12
+ export default CopyButton;
@@ -0,0 +1,183 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ label?: string;
4
+ holdMs?: number;
5
+ onconfirm?: () => void;
6
+ disabled?: boolean;
7
+ size?: 'xs' | 'sm' | 'md' | 'lg';
8
+ class?: string;
9
+ }
10
+
11
+ let {
12
+ label = 'Hold to confirm',
13
+ holdMs = 800,
14
+ onconfirm,
15
+ disabled = false,
16
+ size = 'md',
17
+ class: className = ''
18
+ }: Props = $props();
19
+
20
+ const RADIUS = 33;
21
+ const CIRCUMFERENCE = 2 * Math.PI * RADIUS; // ≈ 207.3
22
+
23
+ let holding = $state(false);
24
+ let done = $state(false);
25
+ let progress = $state(0); // 0–1
26
+ let timer: ReturnType<typeof setTimeout> | undefined;
27
+ let raf: number | undefined;
28
+ let startTime: number;
29
+
30
+ const hintId = `htc-hint-${Math.random().toString(36).slice(2)}`;
31
+
32
+ function startHold() {
33
+ if (disabled || done) return;
34
+ holding = true;
35
+ startTime = performance.now();
36
+
37
+ function tick(now: number) {
38
+ progress = Math.min((now - startTime) / holdMs, 1);
39
+ if (progress < 1) {
40
+ raf = requestAnimationFrame(tick);
41
+ }
42
+ }
43
+ raf = requestAnimationFrame(tick);
44
+
45
+ timer = setTimeout(() => {
46
+ holding = false;
47
+ done = true;
48
+ progress = 1;
49
+ onconfirm?.();
50
+ // spring settle then reset
51
+ setTimeout(() => {
52
+ done = false;
53
+ progress = 0;
54
+ }, 600);
55
+ }, holdMs);
56
+ }
57
+
58
+ function cancelHold() {
59
+ if (!holding) return;
60
+ holding = false;
61
+ clearTimeout(timer);
62
+ cancelAnimationFrame(raf!);
63
+ // quick drain
64
+ progress = 0;
65
+ }
66
+
67
+ function onkeydown(e: KeyboardEvent) {
68
+ if ((e.key === ' ' || e.key === 'Enter') && !e.repeat) {
69
+ e.preventDefault();
70
+ startHold();
71
+ }
72
+ }
73
+
74
+ function onkeyup(e: KeyboardEvent) {
75
+ if (e.key === ' ' || e.key === 'Enter') {
76
+ cancelHold();
77
+ }
78
+ }
79
+
80
+ const strokeOffset = $derived(CIRCUMFERENCE * (1 - progress));
81
+
82
+ const sizeMap: Record<string, { btn: string; ring: number; font: string }> = {
83
+ xs: { btn: 'btn-xs', ring: 40, font: 'text-xs' },
84
+ sm: { btn: 'btn-sm', ring: 52, font: 'text-sm' },
85
+ md: { btn: '', ring: 64, font: 'text-sm' },
86
+ lg: { btn: 'btn-lg', ring: 80, font: 'text-base' }
87
+ };
88
+ const sz = $derived(sizeMap[size] ?? sizeMap.md);
89
+ </script>
90
+
91
+ <button
92
+ type="button"
93
+ class="hold-btn btn btn-error {sz.btn} relative inline-flex items-center gap-2 select-none {className}"
94
+ class:holding
95
+ class:done
96
+ {disabled}
97
+ aria-describedby={hintId}
98
+ onpointerdown={startHold}
99
+ onpointerup={cancelHold}
100
+ onpointerleave={cancelHold}
101
+ onpointercancel={cancelHold}
102
+ {onkeydown}
103
+ {onkeyup}
104
+ >
105
+ <!-- Radial progress ring -->
106
+ <span class="ring-wrap" aria-hidden="true" style="width:{sz.ring}px;height:{sz.ring}px;">
107
+ <svg viewBox="0 0 72 72" class="ring-svg" style="width:{sz.ring}px;height:{sz.ring}px;">
108
+ <!-- track -->
109
+ <circle
110
+ class="ring-track"
111
+ cx="36"
112
+ cy="36"
113
+ r={RADIUS}
114
+ fill="none"
115
+ stroke-width="4"
116
+ stroke-linecap="round"
117
+ />
118
+ <!-- progress arc, starts at top (rotate -90deg) -->
119
+ <circle
120
+ class="ring-prog"
121
+ cx="36"
122
+ cy="36"
123
+ r={RADIUS}
124
+ fill="none"
125
+ stroke-width="4"
126
+ stroke-linecap="round"
127
+ stroke-dasharray={CIRCUMFERENCE}
128
+ stroke-dashoffset={strokeOffset}
129
+ transform="rotate(-90 36 36)"
130
+ />
131
+ </svg>
132
+ </span>
133
+
134
+ <span class={sz.font}>{done ? 'Confirmed' : label}</span>
135
+ </button>
136
+
137
+ <!-- Accessible hint (screen-reader only, referenced by aria-describedby) -->
138
+ <span id={hintId} class="sr-only">Press and hold to confirm. Release early to cancel.</span>
139
+
140
+ <style>
141
+ .hold-btn {
142
+ cursor: pointer;
143
+ }
144
+ .hold-btn:disabled {
145
+ cursor: not-allowed;
146
+ opacity: 0.5;
147
+ }
148
+
149
+ .ring-wrap {
150
+ display: inline-flex;
151
+ flex-shrink: 0;
152
+ }
153
+
154
+ .ring-track {
155
+ stroke: oklch(from currentColor l c h / 0.2);
156
+ }
157
+
158
+ .ring-prog {
159
+ stroke: currentColor;
160
+ /* Default: quick drain on release */
161
+ transition: stroke-dashoffset var(--motion-fast, 150ms) var(--motion-glide, cubic-bezier(0.16, 1, 0.3, 1));
162
+ }
163
+
164
+ /* While holding: fill over holdMs (JS drives via $state) */
165
+ .hold-btn.holding .ring-prog,
166
+ .hold-btn.done .ring-prog {
167
+ transition: none; /* JS RAF drives progress; no CSS transition needed */
168
+ }
169
+
170
+ /* On done: spring settle */
171
+ .hold-btn.done .ring-prog {
172
+ transition: stroke-dashoffset var(--motion-fast, 150ms) var(--motion-spring, cubic-bezier(0.34, 1.56, 0.64, 1));
173
+ }
174
+
175
+ @media (prefers-reduced-motion: reduce) {
176
+ .ring-prog {
177
+ transition: none;
178
+ }
179
+ .hold-btn.done .ring-prog {
180
+ transition: none;
181
+ }
182
+ }
183
+ </style>
@@ -0,0 +1,11 @@
1
+ interface Props {
2
+ label?: string;
3
+ holdMs?: number;
4
+ onconfirm?: () => void;
5
+ disabled?: boolean;
6
+ size?: 'xs' | 'sm' | 'md' | 'lg';
7
+ class?: string;
8
+ }
9
+ declare const HoldToConfirm: import("svelte").Component<Props, {}, "">;
10
+ type HoldToConfirm = ReturnType<typeof HoldToConfirm>;
11
+ export default HoldToConfirm;
@@ -5,6 +5,9 @@
5
5
  * `Escape` to dismiss. Input/contenteditable fields and Ctrl/Meta/Alt
6
6
  * combos are ignored so the shortcut never interferes with typing.
7
7
  *
8
+ * While open, physical keydown events depress the matching on-screen keycap
9
+ * with a brief translateY(1px) + tint (var(--motion-instant)).
10
+ *
8
11
  * Usage:
9
12
  *
10
13
  * <KeyboardShortcutsOverlay shortcuts={[
@@ -57,6 +60,10 @@
57
60
  open = isOpen;
58
61
  });
59
62
 
63
+ // Track which key label is currently depressed
64
+ let depressedKey = $state<string | null>(null);
65
+ let depressTimer: ReturnType<typeof setTimeout> | null = null;
66
+
60
67
  function isTypingTarget(target: EventTarget | null): boolean {
61
68
  if (!(target instanceof HTMLElement)) return false;
62
69
  const tag = target.tagName;
@@ -78,12 +85,34 @@
78
85
  if (isOpen && e.key === 'Escape') {
79
86
  e.preventDefault();
80
87
  isOpen = false;
88
+ return;
81
89
  }
90
+
91
+ // Keycap depress — highlight matching key while overlay is open
92
+ if (isOpen) {
93
+ depressKey(e.key);
94
+ }
95
+ }
96
+
97
+ function depressKey(key: string) {
98
+ if (depressTimer) clearTimeout(depressTimer);
99
+ depressedKey = key;
100
+ depressTimer = setTimeout(() => { depressedKey = null; }, 120);
101
+ }
102
+
103
+ function keyMatchesPressed(keyLabel: string, pressed: string | null): boolean {
104
+ if (!pressed) return false;
105
+ // Match single-char labels directly
106
+ const parts = keyLabel.split(/\s*\/\s*/);
107
+ return parts.some(p => p.trim() === pressed || p.trim().toLowerCase() === pressed.toLowerCase());
82
108
  }
83
109
 
84
110
  $effect(() => {
85
111
  window.addEventListener('keydown', onKeydown);
86
- return () => window.removeEventListener('keydown', onKeydown);
112
+ return () => {
113
+ window.removeEventListener('keydown', onKeydown);
114
+ if (depressTimer) clearTimeout(depressTimer);
115
+ };
87
116
  });
88
117
  </script>
89
118
 
@@ -121,6 +150,7 @@
121
150
  <div class="kso-row">
122
151
  <kbd
123
152
  class="kso-key"
153
+ class:kso-key-pressed={keyMatchesPressed(key, depressedKey)}
124
154
  style={color
125
155
  ? `color:${color};border-color:color-mix(in oklch,${color} 50%,oklch(0.35 0.02 250));background:color-mix(in oklch,${color} 12%,oklch(0.25 0.02 250))`
126
156
  : ''}
@@ -134,17 +164,20 @@
134
164
  {:else}
135
165
  {#each shortcuts as { key, description }, i (key + '::' + i)}
136
166
  <div class="kso-row">
137
- <kbd class="kso-key">{key}</kbd>
167
+ <kbd
168
+ class="kso-key"
169
+ class:kso-key-pressed={keyMatchesPressed(key, depressedKey)}
170
+ >{key}</kbd>
138
171
  <span class="kso-description">{description}</span>
139
172
  </div>
140
173
  {/each}
141
174
  {/if}
142
175
  <div class="kso-row kso-row-meta">
143
- <kbd class="kso-key">?</kbd>
176
+ <kbd class="kso-key" class:kso-key-pressed={keyMatchesPressed('?', depressedKey)}>?</kbd>
144
177
  <span class="kso-description">Toggle this overlay</span>
145
178
  </div>
146
179
  <div class="kso-row kso-row-meta">
147
- <kbd class="kso-key">Esc</kbd>
180
+ <kbd class="kso-key" class:kso-key-pressed={keyMatchesPressed('Escape', depressedKey)}>Esc</kbd>
148
181
  <span class="kso-description">Close overlay</span>
149
182
  </div>
150
183
  </div>
@@ -270,12 +303,18 @@
270
303
  color: oklch(0.93 0.02 250);
271
304
  flex-shrink: 0;
272
305
  white-space: nowrap;
306
+ /* Keycap physical press: quick down + tint, instant easing */
307
+ transition:
308
+ transform var(--motion-instant) ease-out,
309
+ background var(--motion-instant) ease-out,
310
+ border-color var(--motion-instant) ease-out;
273
311
  }
274
312
 
275
- .kso-description {
276
- font-size: 0.85rem;
277
- color: oklch(0.78 0.02 250);
278
- line-height: 1.35;
313
+ /* Depressed state — 1px down, tinted background */
314
+ .kso-key-pressed {
315
+ transform: translateY(1px);
316
+ background: oklch(0.32 0.04 240);
317
+ border-color: oklch(0.45 0.06 240);
279
318
  }
280
319
 
281
320
  @keyframes kso-fade {
@@ -292,5 +331,12 @@
292
331
  .kso-panel {
293
332
  animation: none !important;
294
333
  }
334
+ .kso-key {
335
+ transition: none;
336
+ }
337
+ .kso-key-pressed {
338
+ transform: none;
339
+ background: oklch(0.32 0.04 240);
340
+ }
295
341
  }
296
342
  </style>
@@ -4,6 +4,9 @@
4
4
  * `Escape` to dismiss. Input/contenteditable fields and Ctrl/Meta/Alt
5
5
  * combos are ignored so the shortcut never interferes with typing.
6
6
  *
7
+ * While open, physical keydown events depress the matching on-screen keycap
8
+ * with a brief translateY(1px) + tint (var(--motion-instant)).
9
+ *
7
10
  * Usage:
8
11
  *
9
12
  * <KeyboardShortcutsOverlay shortcuts={[
@@ -0,0 +1,87 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ value?: string;
4
+ label?: string;
5
+ showLabel?: boolean;
6
+ }
7
+
8
+ let {
9
+ value = '',
10
+ label = 'Password strength',
11
+ showLabel = true
12
+ }: Props = $props();
13
+
14
+ // zxcvbn-free heuristic: 0-4
15
+ const score = $derived((() => {
16
+ if (!value) return 0;
17
+ let s = 0;
18
+ if (value.length > 4) s++;
19
+ if (value.length > 8) s++;
20
+ if (/[0-9]/.test(value) && /[a-zA-Z]/.test(value)) s++;
21
+ if (/[^a-zA-Z0-9]/.test(value)) s++;
22
+ return Math.min(s, 4) as 0 | 1 | 2 | 3 | 4;
23
+ })());
24
+
25
+ const scoreLabels = ['', 'Weak', 'Fair', 'Good', 'Strong'] as const;
26
+
27
+ // DaisyUI semantic color classes per score band
28
+ const scoreColors = ['', 'bg-error', 'bg-warning', 'bg-warning', 'bg-success'] as const;
29
+ const scoreTextColors = ['text-base-content/45', 'text-error', 'text-warning', 'text-warning', 'text-success'] as const;
30
+
31
+ const announcementId = `pm-live-${Math.random().toString(36).slice(2, 9)}`;
32
+ </script>
33
+
34
+ <div class="password-meter space-y-1.5">
35
+ {#if showLabel || score > 0}
36
+ <div class="flex items-center justify-between">
37
+ {#if showLabel}
38
+ <span class="text-[0.8125rem] font-medium text-base-content/85">{label}</span>
39
+ {/if}
40
+ <span
41
+ id={announcementId}
42
+ role="status"
43
+ aria-live="polite"
44
+ aria-atomic="true"
45
+ class="text-[0.8125rem] font-medium transition-colors duration-[var(--motion-fast)] {scoreTextColors[score]}"
46
+ >
47
+ {scoreLabels[score]}
48
+ </span>
49
+ </div>
50
+ {/if}
51
+
52
+ <div class="flex gap-1" aria-hidden="true">
53
+ {#each [1, 2, 3, 4] as segment}
54
+ <div
55
+ class="meter-segment h-1.5 flex-1 overflow-hidden rounded-full bg-base-300"
56
+ >
57
+ <div
58
+ class="meter-fill h-full w-full rounded-full {scoreColors[score] || 'bg-base-300'}"
59
+ class:meter-fill--active={score >= segment}
60
+ ></div>
61
+ </div>
62
+ {/each}
63
+ </div>
64
+ </div>
65
+
66
+ <style>
67
+ .meter-fill {
68
+ transform: scaleX(0);
69
+ transform-origin: left;
70
+ transition:
71
+ transform var(--motion-enter) var(--motion-glide),
72
+ background-color var(--motion-fast) var(--motion-glide);
73
+ }
74
+
75
+ .meter-fill--active {
76
+ transform: scaleX(1);
77
+ }
78
+
79
+ @media (prefers-reduced-motion: reduce) {
80
+ .meter-fill {
81
+ transition: background-color var(--motion-fast) var(--motion-glide);
82
+ }
83
+ .meter-fill--active {
84
+ transform: scaleX(1);
85
+ }
86
+ }
87
+ </style>
@@ -0,0 +1,8 @@
1
+ interface Props {
2
+ value?: string;
3
+ label?: string;
4
+ showLabel?: boolean;
5
+ }
6
+ declare const PasswordMeter: import("svelte").Component<Props, {}, "">;
7
+ type PasswordMeter = ReturnType<typeof PasswordMeter>;
8
+ export default PasswordMeter;