@mieweb/ui 0.9.0 → 0.10.0-dev.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 (121) hide show
  1. package/MIGRATION.md +112 -0
  2. package/dist/{Animated-Cs-naa2g.d.cts → Animated-XmH6k8pZ.d.cts} +16 -3
  3. package/dist/{Animated-Cs-naa2g.d.ts → Animated-XmH6k8pZ.d.ts} +16 -3
  4. package/dist/{chunk-XUVEF2RQ.js → chunk-24SEAAOH.js} +2 -2
  5. package/dist/{chunk-XUVEF2RQ.js.map → chunk-24SEAAOH.js.map} +1 -1
  6. package/dist/{chunk-FUFBRKOG.cjs → chunk-2WBPNXO2.cjs} +21 -21
  7. package/dist/{chunk-FUFBRKOG.cjs.map → chunk-2WBPNXO2.cjs.map} +1 -1
  8. package/dist/{chunk-IUDY6S7I.cjs → chunk-37UZHCLM.cjs} +35 -14
  9. package/dist/chunk-37UZHCLM.cjs.map +1 -0
  10. package/dist/{chunk-L52N6BEN.cjs → chunk-AUOTJQGO.cjs} +7 -7
  11. package/dist/{chunk-L52N6BEN.cjs.map → chunk-AUOTJQGO.cjs.map} +1 -1
  12. package/dist/{chunk-6XDPJWSC.cjs → chunk-E7G22IQM.cjs} +32 -28
  13. package/dist/chunk-E7G22IQM.cjs.map +1 -0
  14. package/dist/chunk-IIFQ6BSJ.js +1888 -0
  15. package/dist/chunk-IIFQ6BSJ.js.map +1 -0
  16. package/dist/{chunk-ONNY7UO4.js → chunk-JDJQTKTX.js} +3 -3
  17. package/dist/{chunk-ONNY7UO4.js.map → chunk-JDJQTKTX.js.map} +1 -1
  18. package/dist/{chunk-BJKAVWED.js → chunk-MBWF36DJ.js} +35 -14
  19. package/dist/chunk-MBWF36DJ.js.map +1 -0
  20. package/dist/chunk-NV2CWBI2.cjs +290 -0
  21. package/dist/chunk-NV2CWBI2.cjs.map +1 -0
  22. package/dist/{chunk-FQBTGS3E.cjs → chunk-OFUKTVBN.cjs} +363 -285
  23. package/dist/chunk-OFUKTVBN.cjs.map +1 -0
  24. package/dist/{chunk-7KN6J7XV.cjs → chunk-OMAACCFW.cjs} +7 -7
  25. package/dist/{chunk-7KN6J7XV.cjs.map → chunk-OMAACCFW.cjs.map} +1 -1
  26. package/dist/chunk-OYQ7GYAA.cjs +1921 -0
  27. package/dist/chunk-OYQ7GYAA.cjs.map +1 -0
  28. package/dist/{chunk-YJP6DUFO.cjs → chunk-PKD3JLS2.cjs} +6 -6
  29. package/dist/{chunk-YJP6DUFO.cjs.map → chunk-PKD3JLS2.cjs.map} +1 -1
  30. package/dist/{chunk-CXDMZT45.cjs → chunk-PY7XAG4D.cjs} +27 -4
  31. package/dist/chunk-PY7XAG4D.cjs.map +1 -0
  32. package/dist/{chunk-5H7SAEQO.js → chunk-QKFJDQEK.js} +3 -3
  33. package/dist/{chunk-5H7SAEQO.js.map → chunk-QKFJDQEK.js.map} +1 -1
  34. package/dist/{chunk-XJTFZLWH.cjs → chunk-QO6PRHDX.cjs} +2 -2
  35. package/dist/{chunk-XJTFZLWH.cjs.map → chunk-QO6PRHDX.cjs.map} +1 -1
  36. package/dist/{chunk-OHDIJOGG.js → chunk-RD3JDFYJ.js} +3 -3
  37. package/dist/{chunk-OHDIJOGG.js.map → chunk-RD3JDFYJ.js.map} +1 -1
  38. package/dist/{chunk-4SM3I7WE.cjs → chunk-RXPEZXAB.cjs} +67 -38
  39. package/dist/chunk-RXPEZXAB.cjs.map +1 -0
  40. package/dist/{chunk-VTR2KGLY.cjs → chunk-S3BCHPXT.cjs} +9 -9
  41. package/dist/{chunk-VTR2KGLY.cjs.map → chunk-S3BCHPXT.cjs.map} +1 -1
  42. package/dist/{chunk-KU3XRHWA.js → chunk-TZX76ZNZ.js} +67 -38
  43. package/dist/chunk-TZX76ZNZ.js.map +1 -0
  44. package/dist/{chunk-MXQ7J4BB.js → chunk-VSVGMMNO.js} +361 -284
  45. package/dist/chunk-VSVGMMNO.js.map +1 -0
  46. package/dist/{chunk-Q7G56ZIO.js → chunk-WUBMDOTG.js} +3 -3
  47. package/dist/{chunk-Q7G56ZIO.js.map → chunk-WUBMDOTG.js.map} +1 -1
  48. package/dist/{chunk-DGRXAIWK.js → chunk-X5YM3KXY.js} +27 -4
  49. package/dist/chunk-X5YM3KXY.js.map +1 -0
  50. package/dist/chunk-XCP454Q6.js +263 -0
  51. package/dist/chunk-XCP454Q6.js.map +1 -0
  52. package/dist/{chunk-I3DQXUSC.js → chunk-Y6DFMDMH.js} +3 -3
  53. package/dist/{chunk-I3DQXUSC.js.map → chunk-Y6DFMDMH.js.map} +1 -1
  54. package/dist/{chunk-ODYQYLO2.js → chunk-YSEMAI4I.js} +8 -4
  55. package/dist/chunk-YSEMAI4I.js.map +1 -0
  56. package/dist/components/AlertDialog/index.cjs +4 -4
  57. package/dist/components/AlertDialog/index.js +3 -3
  58. package/dist/components/Collapsible/index.cjs +6 -4
  59. package/dist/components/Collapsible/index.js +3 -1
  60. package/dist/components/Dropdown/index.cjs +9 -8
  61. package/dist/components/Dropdown/index.js +2 -1
  62. package/dist/components/KeyboardShortcutsOverlay/index.cjs +5 -5
  63. package/dist/components/KeyboardShortcutsOverlay/index.js +3 -3
  64. package/dist/components/MediaEditor/index.cjs +8 -8
  65. package/dist/components/MediaEditor/index.js +3 -3
  66. package/dist/components/Modal/index.cjs +10 -10
  67. package/dist/components/Modal/index.js +2 -2
  68. package/dist/components/RichTextEditor/index.cjs +6 -5
  69. package/dist/components/RichTextEditor/index.js +3 -2
  70. package/dist/components/SuperChat/index.cjs +40 -17
  71. package/dist/components/SuperChat/index.cjs.map +1 -1
  72. package/dist/components/SuperChat/index.d.cts +20 -7
  73. package/dist/components/SuperChat/index.d.ts +20 -7
  74. package/dist/components/SuperChat/index.js +35 -12
  75. package/dist/components/SuperChat/index.js.map +1 -1
  76. package/dist/components/SuperChat/plugins/index.cjs +10 -3
  77. package/dist/components/SuperChat/plugins/index.cjs.map +1 -1
  78. package/dist/components/SuperChat/plugins/index.d.cts +3 -3
  79. package/dist/components/SuperChat/plugins/index.d.ts +3 -3
  80. package/dist/components/SuperChat/plugins/index.js +9 -2
  81. package/dist/components/SuperChat/plugins/index.js.map +1 -1
  82. package/dist/esheet.cjs +6 -6
  83. package/dist/esheet.js +3 -3
  84. package/dist/hooks/index.cjs +13 -13
  85. package/dist/hooks/index.js +1 -1
  86. package/dist/index.cjs +720 -1671
  87. package/dist/index.cjs.map +1 -1
  88. package/dist/index.d.cts +347 -347
  89. package/dist/index.d.ts +347 -347
  90. package/dist/index.js +459 -1405
  91. package/dist/index.js.map +1 -1
  92. package/dist/motion.cjs +7 -7
  93. package/dist/motion.d.cts +1 -1
  94. package/dist/motion.d.ts +1 -1
  95. package/dist/motion.js +2 -2
  96. package/dist/styles.css +1 -1
  97. package/dist/tailwind-preset.cjs +4 -4
  98. package/dist/tailwind-preset.js +1 -1
  99. package/dist/{types-DZDroUW8.d.cts → types-B97JNRNQ.d.cts} +1 -1
  100. package/dist/{types-B2RLmdVd.d.ts → types-CnPEwzlp.d.cts} +6 -2
  101. package/dist/{types-B2RLmdVd.d.cts → types-CnPEwzlp.d.ts} +6 -2
  102. package/dist/{types-9vK_YdEb.d.ts → types-gZXrfLj6.d.ts} +1 -1
  103. package/package.json +2 -1
  104. package/dist/chunk-4SM3I7WE.cjs.map +0 -1
  105. package/dist/chunk-5BVFVXHN.cjs +0 -1244
  106. package/dist/chunk-5BVFVXHN.cjs.map +0 -1
  107. package/dist/chunk-6XDPJWSC.cjs.map +0 -1
  108. package/dist/chunk-BJKAVWED.js.map +0 -1
  109. package/dist/chunk-BN75TLUN.cjs +0 -163
  110. package/dist/chunk-BN75TLUN.cjs.map +0 -1
  111. package/dist/chunk-CXDMZT45.cjs.map +0 -1
  112. package/dist/chunk-DGRXAIWK.js.map +0 -1
  113. package/dist/chunk-FQBTGS3E.cjs.map +0 -1
  114. package/dist/chunk-IUDY6S7I.cjs.map +0 -1
  115. package/dist/chunk-KU3XRHWA.js.map +0 -1
  116. package/dist/chunk-MXQ7J4BB.js.map +0 -1
  117. package/dist/chunk-O6GJ4IYS.js +0 -1210
  118. package/dist/chunk-O6GJ4IYS.js.map +0 -1
  119. package/dist/chunk-ODYQYLO2.js.map +0 -1
  120. package/dist/chunk-WYMO3IIK.js +0 -136
  121. package/dist/chunk-WYMO3IIK.js.map +0 -1
package/MIGRATION.md ADDED
@@ -0,0 +1,112 @@
1
+ # Migration guide
2
+
3
+ Breaking-change recipes for `@mieweb/ui` consumers. Each section is anchored
4
+ so release notes and dev-console notices can deep-link to it.
5
+
6
+ ## Chat composer
7
+
8
+ **`MessageComposer` was retired in 0.10.0.** The shared **`ChatComposer`** is
9
+ the single composer across the library — `SuperChat`, `AIChat`, and
10
+ `MessageThread` all mount it internally. If you only used those surfaces, no
11
+ code changes are required (their public props are unchanged); the notes below
12
+ about DOM/selector changes still apply to any CSS or tests that reached into
13
+ the composer.
14
+
15
+ ### Removed exports
16
+
17
+ | Removed in 0.10.0 | Replacement |
18
+ | -------------------------------------------- | ------------------------------------------------------------------------------- |
19
+ | `MessageComposer` | `ChatComposer` |
20
+ | `MessageComposerProps` | `ChatComposerProps` |
21
+ | `CharacterCounter`, `CharacterCounterProps` | none — internal to the retired component (ChatComposer renders its own counter) |
22
+ | `SendButton`, `SendButtonProps`, `sendButtonVariants` | none — internal to the retired component |
23
+
24
+ Still exported and unchanged: `MentionOption`, `NewMessage`,
25
+ `AttachmentPicker`, `DragDropZone`, `CameraButton`, and the Messaging hooks.
26
+
27
+ ### Prop mapping
28
+
29
+ Most props transfer 1:1 — `onSend(NewMessage)`, `value` / `onValueChange`,
30
+ `placeholder`, `disabled`, `isSending`, `autoFocus`, `maxLength`,
31
+ `showCharacterCount`, `acceptedFileTypes`, `maxFileSize`, `maxAttachments`,
32
+ `replyTo` / `onCancelReply`, `mentionOptions`, `className`.
33
+
34
+ | MessageComposer | ChatComposer |
35
+ | -------------------------------- | -------------------------------------------------------------------------------------------------------------- |
36
+ | `showAttachmentPicker` (default `true`) | `allowAttachments` (default `true`) |
37
+ | `showCameraButton` | none built in — pass `micSlot={<CameraButton onCapture={(f) => ref.current?.addFiles([f])} />}` (MessageThread's pattern; `addFiles` works even with `allowAttachments={false}`) |
38
+ | `onTypingStart` / `onTypingStop` | none built in — drive them from `useTypingEmulation({ value, onTypingStart, onTypingStop })` (exported from the Messaging module; same 2s-idle state machine) |
39
+ | `inputTrailing` slot | `micSlot` (rendered in the icon row; `micBehavior` controls visibility) |
40
+ | `variant="minimal"` | none — ChatComposer has a single card presentation |
41
+ | `onError(message: string)` | `onError(message, context?)` — `context.reason` is a stable machine key: `'file-type' \| 'file-size' \| 'attachment-limit' \| 'send-failed'` |
42
+ | `maxLength` default `1600` | no default cap — pass `maxLength={1600}` to keep the old limit |
43
+
44
+ ### Failed-send restore semantics (behavior change)
45
+
46
+ `MessageComposer` restored the typed text itself when `onSend` threw or
47
+ rejected. **`ChatComposer` clears the draft optimistically and delegates
48
+ restore to the host**: a rejection is reported through
49
+ `onError(sendFailedLabel, { reason: 'send-failed' })` and the host restores
50
+ the draft via controlled `value` / `onValueChange`. See `MessageThread.tsx`
51
+ for the reference implementation (epoch-guarded so a stale failure never
52
+ overwrites newer typed input). In both old and new worlds, attachments are
53
+ **not** restaged after a failure.
54
+
55
+ ### Attach flow: paperclip → `+` menu
56
+
57
+ The inline paperclip button is gone; attaching is behind the `+` menu.
58
+
59
+ ```ts
60
+ // Before — one click on the paperclip:
61
+ page.getByRole('button', { name: 'Attach files' }).click();
62
+
63
+ // After — open the + menu, then pick the item:
64
+ page.getByRole('button', { name: 'Add to message' }).click(); // + button
65
+ page.getByRole('menuitem', { name: 'Attach files' }).click();
66
+ ```
67
+
68
+ Paste-to-attach still works, and drag-and-drop is now **built in**: the whole
69
+ composer card is a drop target (overlay text "Drop files here"). Hosts no
70
+ longer need their own `DragDropZone` around the composer — page-level drop
71
+ zones can forward files through `ChatComposerHandle.addFiles()`.
72
+
73
+ ### DOM / selector anchors
74
+
75
+ `data-slot` renames (old rows without a counterpart were internal wrappers
76
+ with no replacement):
77
+
78
+ | MessageComposer slot | ChatComposer slot |
79
+ | --------------------------- | ------------------------------ |
80
+ | `message-composer` (root) | `chat-composer` (root) |
81
+ | `composer-input` | `chat-composer-input` |
82
+ | `composer-send-button` | `chat-composer-send-button` |
83
+ | `composer-attachments` | `chat-composer-attachments` |
84
+ | `composer-reply-preview` | `chat-composer-reply-preview` |
85
+ | `composer-char-count` | `chat-composer-char-count` |
86
+ | `composer-input-trailing` | `chat-composer-mic-slot` |
87
+ | `composer-input-area`, `composer-input-wrapper` | — (card layout: `chat-composer-card`) |
88
+
89
+ New slots without an old counterpart: `chat-composer-add-button`,
90
+ `chat-composer-cancel-reply`, `chat-composer-mic-button`,
91
+ `chat-composer-stop-button`, `chat-composer-readonly`,
92
+ `chat-composer-selectors`, `chat-composer-agent-trigger`.
93
+
94
+ The shared @mention listbox keeps its `composer-mention-list` default slot in
95
+ standalone use; ChatComposer renders it as `chat-composer-mention-list`.
96
+
97
+ Accessible-name changes (all overridable via label props):
98
+
99
+ | Element | Before | After (default) |
100
+ | -------------- | -------------------- | ---------------------------------------- |
101
+ | Textarea | `Message` | `Message input` (`inputLabel`) |
102
+ | Send button | `Send message` / `Sending message` | `Send message` / `Sending message…` (`sendLabel` / `sendingLabel`) |
103
+ | Attach control | `Attach files` (paperclip) | `Add to message` (+ menu) → `Attach files` (menu item) |
104
+
105
+ ### `composerProps` on AIChat
106
+
107
+ `AIChat`'s `composerProps` is now `Partial<ChatComposerProps>` (it forwards to
108
+ the internal `ChatComposer`). Legacy `MessageComposerProps`-era keys —
109
+ `showAttachmentPicker`, `showCameraButton`, `onTypingStart` / `onTypingStop`,
110
+ `inputTrailing`, `onSend`, controlled `value` / `onValueChange` — are still
111
+ accepted and mapped for compatibility, but new code should use the
112
+ `ChatComposerProps` names directly.
@@ -17,7 +17,17 @@ import * as React from 'react';
17
17
  * exiting back to `closed` (a modal).
18
18
  */
19
19
  /** Named animation recipes shared by components. */
20
- type MotionPreset = 'drawerStart' | 'overlay' | 'modalContent' | 'sidebarLabel';
20
+ type MotionPreset = 'drawerStart' | 'overlay' | 'modalContent' | 'sidebarLabel' | 'menu' | 'collapse' | 'toast' | 'fade' | 'pop';
21
+ /**
22
+ * A physical edge, already resolved from any logical (`start`/`end`) position
23
+ * and from writing direction.
24
+ *
25
+ * Presets that move an element along an axis take one of these as `custom`.
26
+ * Resolution is the component's job, not the preset's: only the component knows
27
+ * whether its `end` means right or left, and — for anything anchored — which
28
+ * side it actually landed on after flipping away from a viewport edge.
29
+ */
30
+ type MotionEdge = 'top' | 'bottom' | 'left' | 'right';
21
31
  /**
22
32
  * A variant value. Either a plain target object or a function of the element's
23
33
  * `custom` value — the latter lets a single preset adapt to context, e.g. a
@@ -35,7 +45,10 @@ interface MotionPresetDefinition {
35
45
  * `custom` contract per preset:
36
46
  *
37
47
  * - `drawerStart` — `boolean`, whether the document direction is RTL.
38
- * - `overlay`, `modalContent`, `sidebarLabel` — unused.
48
+ * - `menu`, `toast` — a {@link MotionEdge}, already resolved from logical
49
+ * position and writing direction by the component.
50
+ * - `overlay`, `modalContent`, `sidebarLabel`, `collapse`, `fade`, `pop` —
51
+ * unused.
39
52
  */
40
53
  declare const motionPresets: Record<MotionPreset, MotionPresetDefinition>;
41
54
 
@@ -172,4 +185,4 @@ declare namespace AnimatedPresence {
172
185
  var displayName: string;
173
186
  }
174
187
 
175
- export { Animated as A, type MotionPreset as M, AnimatedPresence as a, type AnimatedProps as b, type MotionRuntime as c, type MotionPresenceProps as d, type MotionPresetDefinition as e, type MotionVariant as f, motionPresets as m, useMotionRuntime as u };
188
+ export { Animated as A, type MotionEdge as M, AnimatedPresence as a, type AnimatedProps as b, type MotionPreset as c, type MotionRuntime as d, type MotionPresenceProps as e, type MotionPresetDefinition as f, type MotionVariant as g, motionPresets as m, useMotionRuntime as u };
@@ -17,7 +17,17 @@ import * as React from 'react';
17
17
  * exiting back to `closed` (a modal).
18
18
  */
19
19
  /** Named animation recipes shared by components. */
20
- type MotionPreset = 'drawerStart' | 'overlay' | 'modalContent' | 'sidebarLabel';
20
+ type MotionPreset = 'drawerStart' | 'overlay' | 'modalContent' | 'sidebarLabel' | 'menu' | 'collapse' | 'toast' | 'fade' | 'pop';
21
+ /**
22
+ * A physical edge, already resolved from any logical (`start`/`end`) position
23
+ * and from writing direction.
24
+ *
25
+ * Presets that move an element along an axis take one of these as `custom`.
26
+ * Resolution is the component's job, not the preset's: only the component knows
27
+ * whether its `end` means right or left, and — for anything anchored — which
28
+ * side it actually landed on after flipping away from a viewport edge.
29
+ */
30
+ type MotionEdge = 'top' | 'bottom' | 'left' | 'right';
21
31
  /**
22
32
  * A variant value. Either a plain target object or a function of the element's
23
33
  * `custom` value — the latter lets a single preset adapt to context, e.g. a
@@ -35,7 +45,10 @@ interface MotionPresetDefinition {
35
45
  * `custom` contract per preset:
36
46
  *
37
47
  * - `drawerStart` — `boolean`, whether the document direction is RTL.
38
- * - `overlay`, `modalContent`, `sidebarLabel` — unused.
48
+ * - `menu`, `toast` — a {@link MotionEdge}, already resolved from logical
49
+ * position and writing direction by the component.
50
+ * - `overlay`, `modalContent`, `sidebarLabel`, `collapse`, `fade`, `pop` —
51
+ * unused.
39
52
  */
40
53
  declare const motionPresets: Record<MotionPreset, MotionPresetDefinition>;
41
54
 
@@ -172,4 +185,4 @@ declare namespace AnimatedPresence {
172
185
  var displayName: string;
173
186
  }
174
187
 
175
- export { Animated as A, type MotionPreset as M, AnimatedPresence as a, type AnimatedProps as b, type MotionRuntime as c, type MotionPresenceProps as d, type MotionPresetDefinition as e, type MotionVariant as f, motionPresets as m, useMotionRuntime as u };
188
+ export { Animated as A, type MotionEdge as M, AnimatedPresence as a, type AnimatedProps as b, type MotionPreset as c, type MotionRuntime as d, type MotionPresenceProps as e, type MotionPresetDefinition as f, type MotionVariant as g, motionPresets as m, useMotionRuntime as u };
@@ -126,5 +126,5 @@ function useIsMobileOrTablet() {
126
126
  }
127
127
 
128
128
  export { RTL_LOCALES, isRtlLocale, useCommandK, useDirection, useIsDesktop, useIsLargeDesktop, useIsMobile, useIsMobileOrTablet, useIsSmallTablet, useIsTablet, useKeyboardShortcut, useMediaQuery };
129
- //# sourceMappingURL=chunk-XUVEF2RQ.js.map
130
- //# sourceMappingURL=chunk-XUVEF2RQ.js.map
129
+ //# sourceMappingURL=chunk-24SEAAOH.js.map
130
+ //# sourceMappingURL=chunk-24SEAAOH.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/hooks/useDirection.ts","../src/hooks/useKeyboardShortcut.ts","../src/hooks/useMediaQuery.ts"],"names":["useEffect","useState"],"mappings":"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","file":"chunk-XUVEF2RQ.js","sourcesContent":["import { useState, useEffect } from 'react';\nimport type { RefObject } from 'react';\n\nexport type Direction = 'ltr' | 'rtl';\n\n/** Locales whose primary script reads right-to-left. */\nexport const RTL_LOCALES = ['ar', 'he', 'fa', 'ur'] as const;\n\n/**\n * Returns true when the given locale (e.g. `ar`, `ar-SA`, `he_IL`) uses a\n * right-to-left script.\n */\nexport function isRtlLocale(locale: string): boolean {\n const lang = locale.toLowerCase().split(/[-_]/)[0];\n return (RTL_LOCALES as readonly string[]).includes(lang);\n}\n\nfunction resolveDirection(element?: HTMLElement | null): Direction {\n if (typeof document === 'undefined') return 'ltr';\n const target = element ?? document.documentElement;\n return getComputedStyle(target).direction === 'rtl' ? 'rtl' : 'ltr';\n}\n\n/**\n * Hook returning the effective text direction (`ltr` | `rtl`) inherited from\n * the nearest `dir` attribute — zero setup for consumers who set\n * `<html dir=\"rtl\">`.\n *\n * Use for JS-level direction logic that CSS logical properties can't cover:\n * inverting ArrowLeft/ArrowRight keyboard handling, popover/toast placement,\n * drag math, etc.\n *\n * @param elementRef - Optional ref; when provided, direction is resolved from\n * that element (respecting any local `dir` override) instead of `<html>`.\n *\n * @example\n * ```tsx\n * const direction = useDirection();\n * const nextKey = direction === 'rtl' ? 'ArrowLeft' : 'ArrowRight';\n * ```\n */\nexport function useDirection(\n elementRef?: RefObject<HTMLElement | null>\n): Direction {\n const [direction, setDirection] = useState<Direction>(() =>\n resolveDirection(elementRef?.current)\n );\n\n useEffect(() => {\n const update = () => setDirection(resolveDirection(elementRef?.current));\n update();\n // dir changes are rare; observing the whole tree for just this attribute is cheap.\n const observer = new MutationObserver(update);\n observer.observe(document.documentElement, {\n attributes: true,\n attributeFilter: ['dir'],\n subtree: true,\n });\n return () => observer.disconnect();\n }, [elementRef]);\n\n // Re-sync when a re-render points elementRef.current at a different node:\n // the MutationObserver only fires on `dir` attribute mutations, so a swapped\n // ref target with a different inherited direction would otherwise go stale.\n const currentElement = elementRef?.current ?? null;\n useEffect(() => {\n setDirection(resolveDirection(currentElement));\n }, [currentElement]);\n\n return direction;\n}\n","import { useEffect, useCallback } from 'react';\n\n/**\n * Options for keyboard shortcut hook\n */\nexport interface KeyboardShortcutOptions {\n /** Whether the shortcut is currently enabled (default: true) */\n enabled?: boolean;\n /** Modifier keys required (default: none) */\n modifiers?: {\n ctrl?: boolean;\n shift?: boolean;\n alt?: boolean;\n meta?: boolean;\n };\n /** Whether to prevent default browser behavior (default: true) */\n preventDefault?: boolean;\n /** Whether to stop event propagation (default: false) */\n stopPropagation?: boolean;\n /** Element types to ignore when the shortcut is pressed (default: ['INPUT', 'TEXTAREA', 'SELECT']) */\n ignoreInputs?: boolean;\n}\n\n/**\n * Hook that triggers a callback when a specific keyboard shortcut is pressed.\n * Supports modifier keys (Ctrl, Shift, Alt, Meta) and ignores input fields by default.\n *\n * @param key - The key to listen for (e.g., 'k', 'Enter', 'Escape', '/')\n * @param callback - Function to call when the shortcut is pressed\n * @param options - Configuration options\n *\n * @example\n * ```tsx\n * // Cmd/Ctrl+K to open search\n * useKeyboardShortcut('k', openSearch, { modifiers: { meta: true, ctrl: true } });\n *\n * // Forward slash to focus search (ignoring when typing)\n * useKeyboardShortcut('/', focusSearch);\n *\n * // Escape to close modal\n * useKeyboardShortcut('Escape', closeModal);\n * ```\n */\nexport function useKeyboardShortcut(\n key: string,\n callback: (event: KeyboardEvent) => void,\n options: KeyboardShortcutOptions = {}\n): void {\n const {\n enabled = true,\n modifiers = {},\n preventDefault = true,\n stopPropagation = false,\n ignoreInputs = true,\n } = options;\n\n const handleKeyDown = useCallback(\n (event: KeyboardEvent) => {\n // Check if we should ignore input fields\n if (ignoreInputs) {\n const target = event.target as HTMLElement;\n const tagName = target.tagName.toUpperCase();\n if (\n tagName === 'INPUT' ||\n tagName === 'TEXTAREA' ||\n tagName === 'SELECT' ||\n target.isContentEditable\n ) {\n return;\n }\n }\n\n // Check modifiers\n const { ctrl, shift, alt, meta } = modifiers;\n\n // For Cmd+K style shortcuts, allow either meta (Mac) or ctrl (Windows/Linux)\n const metaOrCtrl = meta || ctrl;\n if (metaOrCtrl) {\n if (!(event.metaKey || event.ctrlKey)) return;\n } else {\n // If no meta/ctrl modifier specified, ensure neither is pressed\n if (event.metaKey || event.ctrlKey) return;\n }\n\n if (shift && !event.shiftKey) return;\n if (alt && !event.altKey) return;\n\n // Check key match (case-insensitive for letters)\n const eventKey =\n event.key.length === 1 ? event.key.toLowerCase() : event.key;\n const targetKey = key.length === 1 ? key.toLowerCase() : key;\n if (eventKey !== targetKey) return;\n\n if (preventDefault) {\n event.preventDefault();\n }\n if (stopPropagation) {\n event.stopPropagation();\n }\n\n callback(event);\n },\n [key, callback, modifiers, preventDefault, stopPropagation, ignoreInputs]\n );\n\n useEffect(() => {\n if (!enabled) return;\n\n document.addEventListener('keydown', handleKeyDown);\n return () => document.removeEventListener('keydown', handleKeyDown);\n }, [handleKeyDown, enabled]);\n}\n\n/**\n * Hook for the common Cmd/Ctrl+K shortcut pattern (command palette, search, etc.)\n *\n * @param callback - Function to call when Cmd/Ctrl+K is pressed\n * @param enabled - Whether the shortcut is active (default: true)\n *\n * @example\n * ```tsx\n * useCommandK(() => setIsOpen(true));\n * ```\n */\nexport function useCommandK(callback: () => void, enabled = true): void {\n useKeyboardShortcut('k', callback, {\n enabled,\n modifiers: { meta: true, ctrl: true },\n ignoreInputs: false, // Cmd+K should work even in inputs\n });\n}\n","import { useState, useEffect } from 'react';\n\n/**\n * Hook that tracks whether a media query matches.\n * Uses the native `matchMedia` API for efficient media query tracking.\n *\n * @param query - CSS media query string (e.g., '(min-width: 768px)')\n * @returns Boolean indicating whether the media query matches\n *\n * @example\n * ```tsx\n * function ResponsiveComponent() {\n * const isMobile = useMediaQuery('(max-width: 767px)');\n * const isTablet = useMediaQuery('(min-width: 768px) and (max-width: 1023px)');\n * const isDesktop = useMediaQuery('(min-width: 1024px)');\n *\n * return (\n * <div>\n * {isMobile && <MobileLayout />}\n * {isTablet && <TabletLayout />}\n * {isDesktop && <DesktopLayout />}\n * </div>\n * );\n * }\n * ```\n */\nexport function useMediaQuery(query: string): boolean {\n // Initialize with null to indicate SSR/initial state\n const [matches, setMatches] = useState<boolean>(() => {\n // Check if we're in a browser environment\n if (typeof window === 'undefined') return false;\n return window.matchMedia(query).matches;\n });\n\n useEffect(() => {\n if (typeof window === 'undefined') return;\n\n const mediaQuery = window.matchMedia(query);\n\n // Set initial value\n setMatches(mediaQuery.matches);\n\n // Handler for media query changes\n const handler = (event: MediaQueryListEvent) => {\n setMatches(event.matches);\n };\n\n // Modern browsers use addEventListener\n if (mediaQuery.addEventListener) {\n mediaQuery.addEventListener('change', handler);\n return () => mediaQuery.removeEventListener('change', handler);\n }\n\n // Fallback for older browsers\n mediaQuery.addListener(handler);\n return () => mediaQuery.removeListener(handler);\n }, [query]);\n\n return matches;\n}\n\n/**\n * Preset breakpoint hooks following common responsive design patterns\n */\n\n/** Returns true when viewport is smaller than 640px (mobile) */\nexport function useIsMobile(): boolean {\n return useMediaQuery('(max-width: 639px)');\n}\n\n/** Returns true when viewport is 640px-767px (large mobile / small tablet) */\nexport function useIsSmallTablet(): boolean {\n return useMediaQuery('(min-width: 640px) and (max-width: 767px)');\n}\n\n/** Returns true when viewport is 768px-1023px (tablet) */\nexport function useIsTablet(): boolean {\n return useMediaQuery('(min-width: 768px) and (max-width: 1023px)');\n}\n\n/** Returns true when viewport is 1024px or larger (desktop) */\nexport function useIsDesktop(): boolean {\n return useMediaQuery('(min-width: 1024px)');\n}\n\n/** Returns true when viewport is 1280px or larger (large desktop) */\nexport function useIsLargeDesktop(): boolean {\n return useMediaQuery('(min-width: 1280px)');\n}\n\n/** Returns true when viewport is smaller than 1024px (mobile/tablet) */\nexport function useIsMobileOrTablet(): boolean {\n return useMediaQuery('(max-width: 1023px)');\n}\n"]}
1
+ {"version":3,"sources":["../src/hooks/useDirection.ts","../src/hooks/useKeyboardShortcut.ts","../src/hooks/useMediaQuery.ts"],"names":["useEffect","useState"],"mappings":"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","file":"chunk-24SEAAOH.js","sourcesContent":["import { useState, useEffect } from 'react';\nimport type { RefObject } from 'react';\n\nexport type Direction = 'ltr' | 'rtl';\n\n/** Locales whose primary script reads right-to-left. */\nexport const RTL_LOCALES = ['ar', 'he', 'fa', 'ur'] as const;\n\n/**\n * Returns true when the given locale (e.g. `ar`, `ar-SA`, `he_IL`) uses a\n * right-to-left script.\n */\nexport function isRtlLocale(locale: string): boolean {\n const lang = locale.toLowerCase().split(/[-_]/)[0];\n return (RTL_LOCALES as readonly string[]).includes(lang);\n}\n\nfunction resolveDirection(element?: HTMLElement | null): Direction {\n if (typeof document === 'undefined') return 'ltr';\n const target = element ?? document.documentElement;\n return getComputedStyle(target).direction === 'rtl' ? 'rtl' : 'ltr';\n}\n\n/**\n * Hook returning the effective text direction (`ltr` | `rtl`) inherited from\n * the nearest `dir` attribute — zero setup for consumers who set\n * `<html dir=\"rtl\">`.\n *\n * Use for JS-level direction logic that CSS logical properties can't cover:\n * inverting ArrowLeft/ArrowRight keyboard handling, popover/toast placement,\n * drag math, etc.\n *\n * @param elementRef - Optional ref; when provided, direction is resolved from\n * that element (respecting any local `dir` override) instead of `<html>`.\n *\n * @example\n * ```tsx\n * const direction = useDirection();\n * const nextKey = direction === 'rtl' ? 'ArrowLeft' : 'ArrowRight';\n * ```\n */\nexport function useDirection(\n elementRef?: RefObject<HTMLElement | null>\n): Direction {\n const [direction, setDirection] = useState<Direction>(() =>\n resolveDirection(elementRef?.current)\n );\n\n useEffect(() => {\n const update = () => setDirection(resolveDirection(elementRef?.current));\n update();\n // dir changes are rare; observing the whole tree for just this attribute is cheap.\n const observer = new MutationObserver(update);\n observer.observe(document.documentElement, {\n attributes: true,\n attributeFilter: ['dir'],\n subtree: true,\n });\n return () => observer.disconnect();\n }, [elementRef]);\n\n // Re-sync when a re-render points elementRef.current at a different node:\n // the MutationObserver only fires on `dir` attribute mutations, so a swapped\n // ref target with a different inherited direction would otherwise go stale.\n const currentElement = elementRef?.current ?? null;\n useEffect(() => {\n setDirection(resolveDirection(currentElement));\n }, [currentElement]);\n\n return direction;\n}\n","import { useEffect, useCallback } from 'react';\n\n/**\n * Options for keyboard shortcut hook\n */\nexport interface KeyboardShortcutOptions {\n /** Whether the shortcut is currently enabled (default: true) */\n enabled?: boolean;\n /** Modifier keys required (default: none) */\n modifiers?: {\n ctrl?: boolean;\n shift?: boolean;\n alt?: boolean;\n meta?: boolean;\n };\n /** Whether to prevent default browser behavior (default: true) */\n preventDefault?: boolean;\n /** Whether to stop event propagation (default: false) */\n stopPropagation?: boolean;\n /** Element types to ignore when the shortcut is pressed (default: ['INPUT', 'TEXTAREA', 'SELECT']) */\n ignoreInputs?: boolean;\n}\n\n/**\n * Hook that triggers a callback when a specific keyboard shortcut is pressed.\n * Supports modifier keys (Ctrl, Shift, Alt, Meta) and ignores input fields by default.\n *\n * @param key - The key to listen for (e.g., 'k', 'Enter', 'Escape', '/')\n * @param callback - Function to call when the shortcut is pressed\n * @param options - Configuration options\n *\n * @example\n * ```tsx\n * // Cmd/Ctrl+K to open search\n * useKeyboardShortcut('k', openSearch, { modifiers: { meta: true, ctrl: true } });\n *\n * // Forward slash to focus search (ignoring when typing)\n * useKeyboardShortcut('/', focusSearch);\n *\n * // Escape to close modal\n * useKeyboardShortcut('Escape', closeModal);\n * ```\n */\nexport function useKeyboardShortcut(\n key: string,\n callback: (event: KeyboardEvent) => void,\n options: KeyboardShortcutOptions = {}\n): void {\n const {\n enabled = true,\n modifiers = {},\n preventDefault = true,\n stopPropagation = false,\n ignoreInputs = true,\n } = options;\n\n const handleKeyDown = useCallback(\n (event: KeyboardEvent) => {\n // Check if we should ignore input fields\n if (ignoreInputs) {\n const target = event.target as HTMLElement;\n const tagName = target.tagName.toUpperCase();\n if (\n tagName === 'INPUT' ||\n tagName === 'TEXTAREA' ||\n tagName === 'SELECT' ||\n target.isContentEditable\n ) {\n return;\n }\n }\n\n // Check modifiers\n const { ctrl, shift, alt, meta } = modifiers;\n\n // For Cmd+K style shortcuts, allow either meta (Mac) or ctrl (Windows/Linux)\n const metaOrCtrl = meta || ctrl;\n if (metaOrCtrl) {\n if (!(event.metaKey || event.ctrlKey)) return;\n } else {\n // If no meta/ctrl modifier specified, ensure neither is pressed\n if (event.metaKey || event.ctrlKey) return;\n }\n\n if (shift && !event.shiftKey) return;\n if (alt && !event.altKey) return;\n\n // Check key match (case-insensitive for letters)\n const eventKey =\n event.key.length === 1 ? event.key.toLowerCase() : event.key;\n const targetKey = key.length === 1 ? key.toLowerCase() : key;\n if (eventKey !== targetKey) return;\n\n if (preventDefault) {\n event.preventDefault();\n }\n if (stopPropagation) {\n event.stopPropagation();\n }\n\n callback(event);\n },\n [key, callback, modifiers, preventDefault, stopPropagation, ignoreInputs]\n );\n\n useEffect(() => {\n if (!enabled) return;\n\n document.addEventListener('keydown', handleKeyDown);\n return () => document.removeEventListener('keydown', handleKeyDown);\n }, [handleKeyDown, enabled]);\n}\n\n/**\n * Hook for the common Cmd/Ctrl+K shortcut pattern (command palette, search, etc.)\n *\n * @param callback - Function to call when Cmd/Ctrl+K is pressed\n * @param enabled - Whether the shortcut is active (default: true)\n *\n * @example\n * ```tsx\n * useCommandK(() => setIsOpen(true));\n * ```\n */\nexport function useCommandK(callback: () => void, enabled = true): void {\n useKeyboardShortcut('k', callback, {\n enabled,\n modifiers: { meta: true, ctrl: true },\n ignoreInputs: false, // Cmd+K should work even in inputs\n });\n}\n","import { useState, useEffect } from 'react';\n\n/**\n * Hook that tracks whether a media query matches.\n * Uses the native `matchMedia` API for efficient media query tracking.\n *\n * @param query - CSS media query string (e.g., '(min-width: 768px)')\n * @returns Boolean indicating whether the media query matches\n *\n * @example\n * ```tsx\n * function ResponsiveComponent() {\n * const isMobile = useMediaQuery('(max-width: 767px)');\n * const isTablet = useMediaQuery('(min-width: 768px) and (max-width: 1023px)');\n * const isDesktop = useMediaQuery('(min-width: 1024px)');\n *\n * return (\n * <div>\n * {isMobile && <MobileLayout />}\n * {isTablet && <TabletLayout />}\n * {isDesktop && <DesktopLayout />}\n * </div>\n * );\n * }\n * ```\n */\nexport function useMediaQuery(query: string): boolean {\n // Initialize with null to indicate SSR/initial state\n const [matches, setMatches] = useState<boolean>(() => {\n // Check if we're in a browser environment\n if (typeof window === 'undefined') return false;\n return window.matchMedia(query).matches;\n });\n\n useEffect(() => {\n if (typeof window === 'undefined') return;\n\n const mediaQuery = window.matchMedia(query);\n\n // Set initial value\n setMatches(mediaQuery.matches);\n\n // Handler for media query changes\n const handler = (event: MediaQueryListEvent) => {\n setMatches(event.matches);\n };\n\n // Modern browsers use addEventListener\n if (mediaQuery.addEventListener) {\n mediaQuery.addEventListener('change', handler);\n return () => mediaQuery.removeEventListener('change', handler);\n }\n\n // Fallback for older browsers\n mediaQuery.addListener(handler);\n return () => mediaQuery.removeListener(handler);\n }, [query]);\n\n return matches;\n}\n\n/**\n * Preset breakpoint hooks following common responsive design patterns\n */\n\n/** Returns true when viewport is smaller than 640px (mobile) */\nexport function useIsMobile(): boolean {\n return useMediaQuery('(max-width: 639px)');\n}\n\n/** Returns true when viewport is 640px-767px (large mobile / small tablet) */\nexport function useIsSmallTablet(): boolean {\n return useMediaQuery('(min-width: 640px) and (max-width: 767px)');\n}\n\n/** Returns true when viewport is 768px-1023px (tablet) */\nexport function useIsTablet(): boolean {\n return useMediaQuery('(min-width: 768px) and (max-width: 1023px)');\n}\n\n/** Returns true when viewport is 1024px or larger (desktop) */\nexport function useIsDesktop(): boolean {\n return useMediaQuery('(min-width: 1024px)');\n}\n\n/** Returns true when viewport is 1280px or larger (large desktop) */\nexport function useIsLargeDesktop(): boolean {\n return useMediaQuery('(min-width: 1280px)');\n}\n\n/** Returns true when viewport is smaller than 1024px (mobile/tablet) */\nexport function useIsMobileOrTablet(): boolean {\n return useMediaQuery('(max-width: 1023px)');\n}\n"]}
@@ -4,7 +4,7 @@ var chunkMYAU2ZUC_cjs = require('./chunk-MYAU2ZUC.cjs');
4
4
  var chunkIFDC4TXM_cjs = require('./chunk-IFDC4TXM.cjs');
5
5
  var chunk7AECNMYV_cjs = require('./chunk-7AECNMYV.cjs');
6
6
  var chunkXEEVDZDK_cjs = require('./chunk-XEEVDZDK.cjs');
7
- var chunkL52N6BEN_cjs = require('./chunk-L52N6BEN.cjs');
7
+ var chunkAUOTJQGO_cjs = require('./chunk-AUOTJQGO.cjs');
8
8
  var chunkZ5S5JEJD_cjs = require('./chunk-Z5S5JEJD.cjs');
9
9
  var chunkIINZWUGK_cjs = require('./chunk-IINZWUGK.cjs');
10
10
  var chunkNPKPCYAT_cjs = require('./chunk-NPKPCYAT.cjs');
@@ -166,7 +166,7 @@ var FillerWordsModal = ({
166
166
  );
167
167
  };
168
168
  return /* @__PURE__ */ jsxRuntime.jsxs(
169
- chunkL52N6BEN_cjs.Modal,
169
+ chunkAUOTJQGO_cjs.Modal,
170
170
  {
171
171
  open: isOpen,
172
172
  onOpenChange: (open) => {
@@ -174,11 +174,11 @@ var FillerWordsModal = ({
174
174
  },
175
175
  size: "lg",
176
176
  children: [
177
- /* @__PURE__ */ jsxRuntime.jsxs(chunkL52N6BEN_cjs.ModalHeader, { children: [
178
- /* @__PURE__ */ jsxRuntime.jsx(chunkL52N6BEN_cjs.ModalTitle, { children: "Remove Filler Words" }),
179
- /* @__PURE__ */ jsxRuntime.jsx(chunkL52N6BEN_cjs.ModalClose, { "aria-label": "Close modal" })
177
+ /* @__PURE__ */ jsxRuntime.jsxs(chunkAUOTJQGO_cjs.ModalHeader, { children: [
178
+ /* @__PURE__ */ jsxRuntime.jsx(chunkAUOTJQGO_cjs.ModalTitle, { children: "Remove Filler Words" }),
179
+ /* @__PURE__ */ jsxRuntime.jsx(chunkAUOTJQGO_cjs.ModalClose, { "aria-label": "Close modal" })
180
180
  ] }),
181
- /* @__PURE__ */ jsxRuntime.jsxs(chunkL52N6BEN_cjs.ModalBody, { className: "flex flex-col gap-3", children: [
181
+ /* @__PURE__ */ jsxRuntime.jsxs(chunkAUOTJQGO_cjs.ModalBody, { className: "flex flex-col gap-3", children: [
182
182
  /* @__PURE__ */ jsxRuntime.jsx(
183
183
  chunkZ5S5JEJD_cjs.Input,
184
184
  {
@@ -258,7 +258,7 @@ var FillerWordsModal = ({
258
258
  )
259
259
  ] })
260
260
  ] }),
261
- /* @__PURE__ */ jsxRuntime.jsxs(chunkL52N6BEN_cjs.ModalFooter, { className: "justify-between", children: [
261
+ /* @__PURE__ */ jsxRuntime.jsxs(chunkAUOTJQGO_cjs.ModalFooter, { className: "justify-between", children: [
262
262
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-muted-foreground text-sm", children: [
263
263
  totalMatches,
264
264
  " items \u2022 saves ",
@@ -372,7 +372,7 @@ var WordEditorModal = ({
372
372
  const preview = isSilence ? parseSilenceSplit(inputValue) : null;
373
373
  const applyDisabled = isSilence ? !inputValue.trim() || !!preview?.error || !preview || preview.durations.length === 0 : !inputValue.trim();
374
374
  return /* @__PURE__ */ jsxRuntime.jsxs(
375
- chunkL52N6BEN_cjs.Modal,
375
+ chunkAUOTJQGO_cjs.Modal,
376
376
  {
377
377
  open: isOpen && editableWord !== null,
378
378
  onOpenChange: (open) => {
@@ -380,11 +380,11 @@ var WordEditorModal = ({
380
380
  },
381
381
  size: "md",
382
382
  children: [
383
- /* @__PURE__ */ jsxRuntime.jsxs(chunkL52N6BEN_cjs.ModalHeader, { children: [
384
- /* @__PURE__ */ jsxRuntime.jsx(chunkL52N6BEN_cjs.ModalTitle, { children: isSilence ? "Split Silence" : "Edit Word" }),
385
- /* @__PURE__ */ jsxRuntime.jsx(chunkL52N6BEN_cjs.ModalClose, { "aria-label": "Close editor" })
383
+ /* @__PURE__ */ jsxRuntime.jsxs(chunkAUOTJQGO_cjs.ModalHeader, { children: [
384
+ /* @__PURE__ */ jsxRuntime.jsx(chunkAUOTJQGO_cjs.ModalTitle, { children: isSilence ? "Split Silence" : "Edit Word" }),
385
+ /* @__PURE__ */ jsxRuntime.jsx(chunkAUOTJQGO_cjs.ModalClose, { "aria-label": "Close editor" })
386
386
  ] }),
387
- /* @__PURE__ */ jsxRuntime.jsx(chunkL52N6BEN_cjs.ModalBody, { className: "flex flex-col gap-2", children: isSilence ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
387
+ /* @__PURE__ */ jsxRuntime.jsx(chunkAUOTJQGO_cjs.ModalBody, { className: "flex flex-col gap-2", children: isSilence ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
388
388
  /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-foreground m-0 text-sm", children: [
389
389
  "Silence duration:",
390
390
  " ",
@@ -430,7 +430,7 @@ var WordEditorModal = ({
430
430
  }
431
431
  )
432
432
  ] }) }),
433
- /* @__PURE__ */ jsxRuntime.jsxs(chunkL52N6BEN_cjs.ModalFooter, { className: "justify-between", children: [
433
+ /* @__PURE__ */ jsxRuntime.jsxs(chunkAUOTJQGO_cjs.ModalFooter, { className: "justify-between", children: [
434
434
  /* @__PURE__ */ jsxRuntime.jsx(
435
435
  chunkIINZWUGK_cjs.Button,
436
436
  {
@@ -487,7 +487,7 @@ var SilenceSettingsModal = ({
487
487
  };
488
488
  const formatSeconds = (v) => `${v.toFixed(1)}s`;
489
489
  return /* @__PURE__ */ jsxRuntime.jsxs(
490
- chunkL52N6BEN_cjs.Modal,
490
+ chunkAUOTJQGO_cjs.Modal,
491
491
  {
492
492
  open: isOpen,
493
493
  onOpenChange: (open) => {
@@ -495,11 +495,11 @@ var SilenceSettingsModal = ({
495
495
  },
496
496
  size: "md",
497
497
  children: [
498
- /* @__PURE__ */ jsxRuntime.jsxs(chunkL52N6BEN_cjs.ModalHeader, { children: [
499
- /* @__PURE__ */ jsxRuntime.jsx(chunkL52N6BEN_cjs.ModalTitle, { children: "Silence Settings" }),
500
- /* @__PURE__ */ jsxRuntime.jsx(chunkL52N6BEN_cjs.ModalClose, { "aria-label": "Close modal" })
498
+ /* @__PURE__ */ jsxRuntime.jsxs(chunkAUOTJQGO_cjs.ModalHeader, { children: [
499
+ /* @__PURE__ */ jsxRuntime.jsx(chunkAUOTJQGO_cjs.ModalTitle, { children: "Silence Settings" }),
500
+ /* @__PURE__ */ jsxRuntime.jsx(chunkAUOTJQGO_cjs.ModalClose, { "aria-label": "Close modal" })
501
501
  ] }),
502
- /* @__PURE__ */ jsxRuntime.jsxs(chunkL52N6BEN_cjs.ModalBody, { className: "flex flex-col gap-6", children: [
502
+ /* @__PURE__ */ jsxRuntime.jsxs(chunkAUOTJQGO_cjs.ModalBody, { className: "flex flex-col gap-6", children: [
503
503
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
504
504
  /* @__PURE__ */ jsxRuntime.jsx(
505
505
  chunkMYAU2ZUC_cjs.Slider,
@@ -535,7 +535,7 @@ var SilenceSettingsModal = ({
535
535
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground m-0 text-xs", children: "Silences longer than this will create paragraph breaks." })
536
536
  ] })
537
537
  ] }),
538
- /* @__PURE__ */ jsxRuntime.jsxs(chunkL52N6BEN_cjs.ModalFooter, { children: [
538
+ /* @__PURE__ */ jsxRuntime.jsxs(chunkAUOTJQGO_cjs.ModalFooter, { children: [
539
539
  /* @__PURE__ */ jsxRuntime.jsx(chunkIINZWUGK_cjs.Button, { variant: "secondary", size: "sm", onClick: onClose, children: "Cancel" }),
540
540
  /* @__PURE__ */ jsxRuntime.jsx(chunkIINZWUGK_cjs.Button, { variant: "primary", size: "sm", onClick: handleApply, children: "Apply" })
541
541
  ] })
@@ -2025,5 +2025,5 @@ exports.MediaEditor = MediaEditor;
2025
2025
  exports.SilenceSettingsModal = SilenceSettingsModal;
2026
2026
  exports.SpeedMarkerMenu = SpeedMarkerMenu;
2027
2027
  exports.WordEditorModal = WordEditorModal;
2028
- //# sourceMappingURL=chunk-FUFBRKOG.cjs.map
2029
- //# sourceMappingURL=chunk-FUFBRKOG.cjs.map
2028
+ //# sourceMappingURL=chunk-2WBPNXO2.cjs.map
2029
+ //# sourceMappingURL=chunk-2WBPNXO2.cjs.map