@ttt-productions/ui-core 0.11.34 → 0.11.35

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 (142) hide show
  1. package/README.md +1 -1
  2. package/dist/browser/index.d.ts.map +1 -1
  3. package/dist/browser/index.js +0 -1
  4. package/dist/browser/index.js.map +1 -1
  5. package/dist/browser/reload-recovery-watch.d.ts +0 -4
  6. package/dist/browser/reload-recovery-watch.d.ts.map +1 -1
  7. package/dist/browser/reload-recovery-watch.js +0 -5
  8. package/dist/browser/reload-recovery-watch.js.map +1 -1
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +0 -1
  11. package/dist/index.js.map +1 -1
  12. package/dist/lib/format-numbers.d.ts +0 -6
  13. package/dist/lib/format-numbers.d.ts.map +1 -1
  14. package/dist/lib/format-numbers.js +0 -6
  15. package/dist/lib/format-numbers.js.map +1 -1
  16. package/dist/lib/reload-recovery.d.ts +1 -9
  17. package/dist/lib/reload-recovery.d.ts.map +1 -1
  18. package/dist/lib/reload-recovery.js +1 -11
  19. package/dist/lib/reload-recovery.js.map +1 -1
  20. package/dist/lib/utils.d.ts +0 -3
  21. package/dist/lib/utils.d.ts.map +1 -1
  22. package/dist/lib/utils.js +0 -3
  23. package/dist/lib/utils.js.map +1 -1
  24. package/dist/react/components/accordion.d.ts.map +1 -1
  25. package/dist/react/components/accordion.js.map +1 -1
  26. package/dist/react/components/action-row.d.ts +0 -7
  27. package/dist/react/components/action-row.d.ts.map +1 -1
  28. package/dist/react/components/action-row.js +1 -5
  29. package/dist/react/components/action-row.js.map +1 -1
  30. package/dist/react/components/button.d.ts +0 -10
  31. package/dist/react/components/button.d.ts.map +1 -1
  32. package/dist/react/components/button.js +2 -7
  33. package/dist/react/components/button.js.map +1 -1
  34. package/dist/react/components/consequence-dialog.d.ts +1 -48
  35. package/dist/react/components/consequence-dialog.d.ts.map +1 -1
  36. package/dist/react/components/consequence-dialog.js +3 -21
  37. package/dist/react/components/consequence-dialog.js.map +1 -1
  38. package/dist/react/components/date-picker.d.ts +0 -17
  39. package/dist/react/components/date-picker.d.ts.map +1 -1
  40. package/dist/react/components/date-picker.js +0 -32
  41. package/dist/react/components/date-picker.js.map +1 -1
  42. package/dist/react/components/date-time-picker.d.ts +0 -23
  43. package/dist/react/components/date-time-picker.d.ts.map +1 -1
  44. package/dist/react/components/date-time-picker.js +0 -19
  45. package/dist/react/components/date-time-picker.js.map +1 -1
  46. package/dist/react/components/dropdown-menu.d.ts +0 -8
  47. package/dist/react/components/dropdown-menu.d.ts.map +1 -1
  48. package/dist/react/components/dropdown-menu.js +1 -2
  49. package/dist/react/components/dropdown-menu.js.map +1 -1
  50. package/dist/react/components/format-control.d.ts +1 -21
  51. package/dist/react/components/format-control.d.ts.map +1 -1
  52. package/dist/react/components/format-control.js +1 -21
  53. package/dist/react/components/format-control.js.map +1 -1
  54. package/dist/react/components/input.d.ts +0 -17
  55. package/dist/react/components/input.d.ts.map +1 -1
  56. package/dist/react/components/input.js +1 -3
  57. package/dist/react/components/input.js.map +1 -1
  58. package/dist/react/components/layout/screen-adaptive-view.d.ts +0 -7
  59. package/dist/react/components/layout/screen-adaptive-view.d.ts.map +1 -1
  60. package/dist/react/components/layout/screen-adaptive-view.js +1 -2
  61. package/dist/react/components/layout/screen-adaptive-view.js.map +1 -1
  62. package/dist/react/components/list-pagination.d.ts +0 -30
  63. package/dist/react/components/list-pagination.d.ts.map +1 -1
  64. package/dist/react/components/list-pagination.js +0 -27
  65. package/dist/react/components/list-pagination.js.map +1 -1
  66. package/dist/react/components/opener-focus.d.ts +1 -7
  67. package/dist/react/components/opener-focus.d.ts.map +1 -1
  68. package/dist/react/components/opener-focus.js +1 -7
  69. package/dist/react/components/opener-focus.js.map +1 -1
  70. package/dist/react/components/pending-guard.d.ts +0 -6
  71. package/dist/react/components/pending-guard.d.ts.map +1 -1
  72. package/dist/react/components/pending-guard.js +0 -6
  73. package/dist/react/components/pending-guard.js.map +1 -1
  74. package/dist/react/components/relative-time.d.ts +0 -8
  75. package/dist/react/components/relative-time.d.ts.map +1 -1
  76. package/dist/react/components/relative-time.js +0 -4
  77. package/dist/react/components/relative-time.js.map +1 -1
  78. package/dist/react/components/reload-recovery-notice.d.ts +0 -14
  79. package/dist/react/components/reload-recovery-notice.d.ts.map +1 -1
  80. package/dist/react/components/reload-recovery-notice.js +0 -5
  81. package/dist/react/components/reload-recovery-notice.js.map +1 -1
  82. package/dist/react/components/scroll-to-top-button.d.ts +1 -25
  83. package/dist/react/components/scroll-to-top-button.d.ts.map +1 -1
  84. package/dist/react/components/scroll-to-top-button.js.map +1 -1
  85. package/dist/react/components/search-dropdown.d.ts +0 -53
  86. package/dist/react/components/search-dropdown.d.ts.map +1 -1
  87. package/dist/react/components/search-dropdown.js +2 -36
  88. package/dist/react/components/search-dropdown.js.map +1 -1
  89. package/dist/react/components/select.d.ts +0 -7
  90. package/dist/react/components/select.d.ts.map +1 -1
  91. package/dist/react/components/select.js +0 -2
  92. package/dist/react/components/select.js.map +1 -1
  93. package/dist/react/components/show-more-toggle.d.ts +0 -16
  94. package/dist/react/components/show-more-toggle.d.ts.map +1 -1
  95. package/dist/react/components/show-more-toggle.js +0 -5
  96. package/dist/react/components/show-more-toggle.js.map +1 -1
  97. package/dist/react/components/spinner.d.ts +0 -14
  98. package/dist/react/components/spinner.d.ts.map +1 -1
  99. package/dist/react/components/spinner.js +0 -8
  100. package/dist/react/components/spinner.js.map +1 -1
  101. package/dist/react/components/switch.d.ts +0 -7
  102. package/dist/react/components/switch.d.ts.map +1 -1
  103. package/dist/react/components/switch.js +0 -2
  104. package/dist/react/components/switch.js.map +1 -1
  105. package/dist/react/components/table.d.ts +0 -10
  106. package/dist/react/components/table.d.ts.map +1 -1
  107. package/dist/react/components/table.js +0 -6
  108. package/dist/react/components/table.js.map +1 -1
  109. package/dist/react/components/textarea.d.ts +0 -11
  110. package/dist/react/components/textarea.d.ts.map +1 -1
  111. package/dist/react/components/textarea.js +1 -3
  112. package/dist/react/components/textarea.js.map +1 -1
  113. package/dist/react/components/toast.d.ts +0 -4
  114. package/dist/react/components/toast.d.ts.map +1 -1
  115. package/dist/react/components/toast.js +3 -24
  116. package/dist/react/components/toast.js.map +1 -1
  117. package/dist/react/components/toaster.d.ts.map +1 -1
  118. package/dist/react/components/toaster.js.map +1 -1
  119. package/dist/react/hooks/use-async-action.d.ts +1 -21
  120. package/dist/react/hooks/use-async-action.d.ts.map +1 -1
  121. package/dist/react/hooks/use-async-action.js +1 -9
  122. package/dist/react/hooks/use-async-action.js.map +1 -1
  123. package/dist/react/hooks/use-cursor-page.d.ts +0 -62
  124. package/dist/react/hooks/use-cursor-page.d.ts.map +1 -1
  125. package/dist/react/hooks/use-cursor-page.js +1 -37
  126. package/dist/react/hooks/use-cursor-page.js.map +1 -1
  127. package/dist/react/hooks/use-paged-list.d.ts +0 -43
  128. package/dist/react/hooks/use-paged-list.d.ts.map +1 -1
  129. package/dist/react/hooks/use-paged-list.js +0 -15
  130. package/dist/react/hooks/use-paged-list.js.map +1 -1
  131. package/dist/react/hooks/use-return-scroll.d.ts +0 -9
  132. package/dist/react/hooks/use-return-scroll.d.ts.map +1 -1
  133. package/dist/react/hooks/use-return-scroll.js +2 -34
  134. package/dist/react/hooks/use-return-scroll.js.map +1 -1
  135. package/dist/react/hooks/use-toast.d.ts +0 -8
  136. package/dist/react/hooks/use-toast.d.ts.map +1 -1
  137. package/dist/react/hooks/use-toast.js +1 -4
  138. package/dist/react/hooks/use-toast.js.map +1 -1
  139. package/dist/react/index.d.ts.map +1 -1
  140. package/dist/react/index.js +0 -6
  141. package/dist/react/index.js.map +1 -1
  142. package/package.json +4 -4
@@ -3,10 +3,6 @@ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
3
3
  import * as React from 'react';
4
4
  import { formatDistanceToNowStrict } from 'date-fns';
5
5
  import { toDate } from '@ttt-productions/firebase-helpers';
6
- /**
7
- * SSR-safe relative-time component.
8
- * Renders `placeholder` on the server and pre-mount, then the live value.
9
- */
10
6
  export function RelativeTime({ timestamp, placeholder = '...', fallback = 'a while ago', refreshIntervalMs = 60_000, }) {
11
7
  const [value, setValue] = React.useState(placeholder);
12
8
  React.useEffect(() => {
@@ -1 +1 @@
1
- {"version":3,"file":"relative-time.js","sourceRoot":"","sources":["../../../src/react/components/relative-time.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,yBAAyB,EAAE,MAAM,UAAU,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAa3D;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,EAC3B,SAAS,EACT,WAAW,GAAG,KAAK,EACnB,QAAQ,GAAG,aAAa,EACxB,iBAAiB,GAAG,MAAM,GACR;IAClB,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAS,WAAW,CAAC,CAAC;IAE9D,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,IAAI,IAAI,GAAgB,IAAI,CAAC;QAC7B,IAAI,SAAS,YAAY,IAAI,EAAE,CAAC;YAC9B,IAAI,GAAG,SAAS,CAAC;QACnB,CAAC;aAAM,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE,CAAC;YACzC,IAAI,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;QAC3B,CAAC;aAAM,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE,CAAC;YACzC,IAAI,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC;QAC7B,CAAC;aAAM,IAAI,SAAS,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,QAAQ,IAAI,SAAS,EAAE,CAAC;YAC/E,IAAI,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;QAC3B,CAAC;QAED,IAAI,CAAC,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;YACnC,QAAQ,CAAC,QAAQ,CAAC,CAAC;YACnB,OAAO;QACT,CAAC;QAED,QAAQ,CAAC,yBAAyB,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAE/D,IAAI,iBAAiB,IAAI,CAAC;YAAE,OAAO;QACnC,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,EAAE;YAChC,QAAQ,CAAC,yBAAyB,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QACjE,CAAC,EAAE,iBAAiB,CAAC,CAAC;QACtB,OAAO,GAAG,EAAE,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;IACvC,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,iBAAiB,CAAC,CAAC,CAAC;IAE7C,OAAO,4BAAG,KAAK,GAAI,CAAC;AACtB,CAAC","sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { formatDistanceToNowStrict } from 'date-fns';\nimport { toDate } from '@ttt-productions/firebase-helpers';\n\nexport interface RelativeTimeProps {\n /** Number (ms or seconds), ISO string, Firestore Timestamp, or Date. */\n timestamp: number | string | Date | undefined | null;\n /** Fallback rendered before client hydration completes. Defaults to \"...\". */\n placeholder?: string;\n /** Fallback rendered when the timestamp can't be parsed. Defaults to \"a while ago\". */\n fallback?: string;\n /** Auto-refresh interval in ms. Defaults to 60000. Set to 0 to disable. */\n refreshIntervalMs?: number;\n}\n\n/**\n * SSR-safe relative-time component.\n * Renders `placeholder` on the server and pre-mount, then the live value.\n */\nexport function RelativeTime({\n timestamp,\n placeholder = '...',\n fallback = 'a while ago',\n refreshIntervalMs = 60_000,\n}: RelativeTimeProps) {\n const [value, setValue] = React.useState<string>(placeholder);\n\n React.useEffect(() => {\n let date: Date | null = null;\n if (timestamp instanceof Date) {\n date = timestamp;\n } else if (typeof timestamp === 'number') {\n date = toDate(timestamp);\n } else if (typeof timestamp === 'string') {\n date = new Date(timestamp);\n } else if (timestamp && typeof timestamp === 'object' && 'toDate' in timestamp) {\n date = toDate(timestamp);\n }\n\n if (!date || isNaN(date.getTime())) {\n setValue(fallback);\n return;\n }\n\n setValue(formatDistanceToNowStrict(date, { addSuffix: true }));\n\n if (refreshIntervalMs <= 0) return;\n const interval = setInterval(() => {\n setValue(formatDistanceToNowStrict(date, { addSuffix: true }));\n }, refreshIntervalMs);\n return () => clearInterval(interval);\n }, [timestamp, fallback, refreshIntervalMs]);\n\n return <>{value}</>;\n}\n"]}
1
+ {"version":3,"file":"relative-time.js","sourceRoot":"","sources":["../../../src/react/components/relative-time.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,yBAAyB,EAAE,MAAM,UAAU,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAS3D,MAAM,UAAU,YAAY,CAAC,EAC3B,SAAS,EACT,WAAW,GAAG,KAAK,EACnB,QAAQ,GAAG,aAAa,EACxB,iBAAiB,GAAG,MAAM,GACR;IAClB,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAS,WAAW,CAAC,CAAC;IAE9D,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,IAAI,IAAI,GAAgB,IAAI,CAAC;QAC7B,IAAI,SAAS,YAAY,IAAI,EAAE,CAAC;YAC9B,IAAI,GAAG,SAAS,CAAC;QACnB,CAAC;aAAM,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE,CAAC;YACzC,IAAI,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;QAC3B,CAAC;aAAM,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE,CAAC;YACzC,IAAI,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC;QAC7B,CAAC;aAAM,IAAI,SAAS,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,QAAQ,IAAI,SAAS,EAAE,CAAC;YAC/E,IAAI,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;QAC3B,CAAC;QAED,IAAI,CAAC,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;YACnC,QAAQ,CAAC,QAAQ,CAAC,CAAC;YACnB,OAAO;QACT,CAAC;QAED,QAAQ,CAAC,yBAAyB,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAE/D,IAAI,iBAAiB,IAAI,CAAC;YAAE,OAAO;QACnC,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,EAAE;YAChC,QAAQ,CAAC,yBAAyB,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QACjE,CAAC,EAAE,iBAAiB,CAAC,CAAC;QACtB,OAAO,GAAG,EAAE,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;IACvC,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,iBAAiB,CAAC,CAAC,CAAC;IAE7C,OAAO,4BAAG,KAAK,GAAI,CAAC;AACtB,CAAC","sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { formatDistanceToNowStrict } from 'date-fns';\nimport { toDate } from '@ttt-productions/firebase-helpers';\n\nexport interface RelativeTimeProps {\n timestamp: number | string | Date | undefined | null;\n placeholder?: string;\n fallback?: string;\n refreshIntervalMs?: number;\n}\n\nexport function RelativeTime({\n timestamp,\n placeholder = '...',\n fallback = 'a while ago',\n refreshIntervalMs = 60_000,\n}: RelativeTimeProps) {\n const [value, setValue] = React.useState<string>(placeholder);\n\n React.useEffect(() => {\n let date: Date | null = null;\n if (timestamp instanceof Date) {\n date = timestamp;\n } else if (typeof timestamp === 'number') {\n date = toDate(timestamp);\n } else if (typeof timestamp === 'string') {\n date = new Date(timestamp);\n } else if (timestamp && typeof timestamp === 'object' && 'toDate' in timestamp) {\n date = toDate(timestamp);\n }\n\n if (!date || isNaN(date.getTime())) {\n setValue(fallback);\n return;\n }\n\n setValue(formatDistanceToNowStrict(date, { addSuffix: true }));\n\n if (refreshIntervalMs <= 0) return;\n const interval = setInterval(() => {\n setValue(formatDistanceToNowStrict(date, { addSuffix: true }));\n }, refreshIntervalMs);\n return () => clearInterval(interval);\n }, [timestamp, fallback, refreshIntervalMs]);\n\n return <>{value}</>;\n}\n"]}
@@ -2,26 +2,12 @@ import * as React from 'react';
2
2
  import type { ReloadRecoveryWatch } from '../../browser/reload-recovery-watch.js';
3
3
  import type { ReloadRecoveryKind } from '../../lib/reload-recovery.js';
4
4
  export interface ReloadRecoveryNoticeProps {
5
- /** The watch the app's client entry started. */
6
5
  watch: ReloadRecoveryWatch;
7
- /** The app's sentence for each failure. */
8
6
  messages: Readonly<Record<ReloadRecoveryKind, React.ReactNode>>;
9
- /** The app's label for the refresh control. */
10
7
  refreshLabel: React.ReactNode;
11
- /**
12
- * The app's own reload, which runs its unsaved-work guard first. Refresh is pending until it
13
- * settles: a reload that proceeds keeps it pending until the page unloads, and a cancelled guard
14
- * settles it with the page, its input, and the notice as they were.
15
- */
16
8
  onRefresh: () => Promise<unknown>;
17
- /** Receives anything `onRefresh` throws. */
18
9
  onRefreshError: (error: unknown) => void;
19
10
  className?: string;
20
11
  }
21
- /**
22
- * One notice pinned to the top of the screen once the page hits a failure only a reload repairs.
23
- * It cannot be dismissed and never reloads by itself: the page and every editor beneath it stay
24
- * mounted and usable until the person presses Refresh. A repeat of the failure changes nothing.
25
- */
26
12
  export declare function ReloadRecoveryNotice({ watch, messages, refreshLabel, onRefresh, onRefreshError, className, }: ReloadRecoveryNoticeProps): React.JSX.Element | null;
27
13
  //# sourceMappingURL=reload-recovery-notice.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"reload-recovery-notice.d.ts","sourceRoot":"","sources":["../../../src/react/components/reload-recovery-notice.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAC;AAClF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAMvE,MAAM,WAAW,yBAAyB;IACxC,gDAAgD;IAChD,KAAK,EAAE,mBAAmB,CAAC;IAC3B,2CAA2C;IAC3C,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,kBAAkB,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC;IAChE,+CAA+C;IAC/C,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC;IAC9B;;;;OAIG;IACH,SAAS,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAClC,4CAA4C;IAC5C,cAAc,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACzC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAID;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,cAAc,EACd,SAAS,GACV,EAAE,yBAAyB,4BAiB3B"}
1
+ {"version":3,"file":"reload-recovery-notice.d.ts","sourceRoot":"","sources":["../../../src/react/components/reload-recovery-notice.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAC;AAClF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAMvE,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,mBAAmB,CAAC;IAC3B,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,kBAAkB,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC;IAChE,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC;IAC9B,SAAS,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAClC,cAAc,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACzC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAID,wBAAgB,oBAAoB,CAAC,EACnC,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,cAAc,EACd,SAAS,GACV,EAAE,yBAAyB,4BAiB3B"}
@@ -6,11 +6,6 @@ import { useAsyncAction } from '../hooks/use-async-action.js';
6
6
  import { Alert, AlertDescription } from './alert.js';
7
7
  import { Button } from './button.js';
8
8
  const noFailureOnServer = () => null;
9
- /**
10
- * One notice pinned to the top of the screen once the page hits a failure only a reload repairs.
11
- * It cannot be dismissed and never reloads by itself: the page and every editor beneath it stay
12
- * mounted and usable until the person presses Refresh. A repeat of the failure changes nothing.
13
- */
14
9
  export function ReloadRecoveryNotice({ watch, messages, refreshLabel, onRefresh, onRefreshError, className, }) {
15
10
  const kind = React.useSyncExternalStore(watch.subscribe, watch.current, noFailureOnServer);
16
11
  const refresh = useAsyncAction(onRefresh, { onError: onRefreshError });
@@ -1 +1 @@
1
- {"version":3,"file":"reload-recovery-notice.js","sourceRoot":"","sources":["../../../src/react/components/reload-recovery-notice.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,EAAE,EAAE,EAAE,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAoBrC,MAAM,iBAAiB,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC;AAErC;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,EACnC,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,cAAc,EACd,SAAS,GACiB;IAC1B,MAAM,IAAI,GAAG,KAAK,CAAC,oBAAoB,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,EAAE,iBAAiB,CAAC,CAAC;IAC3F,MAAM,OAAO,GAAG,cAAc,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC,CAAC;IACvE,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC/B,OAAO,CACL,MAAC,KAAK,IACJ,SAAS,EAAE,EAAE,CACX,iIAAiI,EACjI,SAAS,CACV,aAED,KAAC,gBAAgB,cAAE,QAAQ,CAAC,IAAI,CAAC,GAAoB,EACrD,KAAC,MAAM,IAAC,IAAI,EAAC,QAAQ,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,OAAO,CAAC,GAAG,EAAE,YACxF,YAAY,GACN,IACH,CACT,CAAC;AACJ,CAAC","sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport type { ReloadRecoveryWatch } from '../../browser/reload-recovery-watch.js';\nimport type { ReloadRecoveryKind } from '../../lib/reload-recovery.js';\nimport { cn } from '../../lib/utils.js';\nimport { useAsyncAction } from '../hooks/use-async-action.js';\nimport { Alert, AlertDescription } from './alert.js';\nimport { Button } from './button.js';\n\nexport interface ReloadRecoveryNoticeProps {\n /** The watch the app's client entry started. */\n watch: ReloadRecoveryWatch;\n /** The app's sentence for each failure. */\n messages: Readonly<Record<ReloadRecoveryKind, React.ReactNode>>;\n /** The app's label for the refresh control. */\n refreshLabel: React.ReactNode;\n /**\n * The app's own reload, which runs its unsaved-work guard first. Refresh is pending until it\n * settles: a reload that proceeds keeps it pending until the page unloads, and a cancelled guard\n * settles it with the page, its input, and the notice as they were.\n */\n onRefresh: () => Promise<unknown>;\n /** Receives anything `onRefresh` throws. */\n onRefreshError: (error: unknown) => void;\n className?: string;\n}\n\nconst noFailureOnServer = () => null;\n\n/**\n * One notice pinned to the top of the screen once the page hits a failure only a reload repairs.\n * It cannot be dismissed and never reloads by itself: the page and every editor beneath it stay\n * mounted and usable until the person presses Refresh. A repeat of the failure changes nothing.\n */\nexport function ReloadRecoveryNotice({\n watch,\n messages,\n refreshLabel,\n onRefresh,\n onRefreshError,\n className,\n}: ReloadRecoveryNoticeProps) {\n const kind = React.useSyncExternalStore(watch.subscribe, watch.current, noFailureOnServer);\n const refresh = useAsyncAction(onRefresh, { onError: onRefreshError });\n if (kind === null) return null;\n return (\n <Alert\n className={cn(\n 'ui-reload-recovery-notice fixed inset-x-0 top-0 z-50 flex items-center justify-between gap-4 rounded-none border-x-0 border-t-0',\n className,\n )}\n >\n <AlertDescription>{messages[kind]}</AlertDescription>\n <Button type=\"button\" size=\"sm\" pending={refresh.pending} onClick={() => void refresh.run()}>\n {refreshLabel}\n </Button>\n </Alert>\n );\n}\n"]}
1
+ {"version":3,"file":"reload-recovery-notice.js","sourceRoot":"","sources":["../../../src/react/components/reload-recovery-notice.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,EAAE,EAAE,EAAE,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAWrC,MAAM,iBAAiB,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC;AAErC,MAAM,UAAU,oBAAoB,CAAC,EACnC,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,cAAc,EACd,SAAS,GACiB;IAC1B,MAAM,IAAI,GAAG,KAAK,CAAC,oBAAoB,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,EAAE,iBAAiB,CAAC,CAAC;IAC3F,MAAM,OAAO,GAAG,cAAc,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC,CAAC;IACvE,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC/B,OAAO,CACL,MAAC,KAAK,IACJ,SAAS,EAAE,EAAE,CACX,iIAAiI,EACjI,SAAS,CACV,aAED,KAAC,gBAAgB,cAAE,QAAQ,CAAC,IAAI,CAAC,GAAoB,EACrD,KAAC,MAAM,IAAC,IAAI,EAAC,QAAQ,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,OAAO,CAAC,GAAG,EAAE,YACxF,YAAY,GACN,IACH,CACT,CAAC;AACJ,CAAC","sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport type { ReloadRecoveryWatch } from '../../browser/reload-recovery-watch.js';\nimport type { ReloadRecoveryKind } from '../../lib/reload-recovery.js';\nimport { cn } from '../../lib/utils.js';\nimport { useAsyncAction } from '../hooks/use-async-action.js';\nimport { Alert, AlertDescription } from './alert.js';\nimport { Button } from './button.js';\n\nexport interface ReloadRecoveryNoticeProps {\n watch: ReloadRecoveryWatch;\n messages: Readonly<Record<ReloadRecoveryKind, React.ReactNode>>;\n refreshLabel: React.ReactNode;\n onRefresh: () => Promise<unknown>;\n onRefreshError: (error: unknown) => void;\n className?: string;\n}\n\nconst noFailureOnServer = () => null;\n\nexport function ReloadRecoveryNotice({\n watch,\n messages,\n refreshLabel,\n onRefresh,\n onRefreshError,\n className,\n}: ReloadRecoveryNoticeProps) {\n const kind = React.useSyncExternalStore(watch.subscribe, watch.current, noFailureOnServer);\n const refresh = useAsyncAction(onRefresh, { onError: onRefreshError });\n if (kind === null) return null;\n return (\n <Alert\n className={cn(\n 'ui-reload-recovery-notice fixed inset-x-0 top-0 z-50 flex items-center justify-between gap-4 rounded-none border-x-0 border-t-0',\n className,\n )}\n >\n <AlertDescription>{messages[kind]}</AlertDescription>\n <Button type=\"button\" size=\"sm\" pending={refresh.pending} onClick={() => void refresh.run()}>\n {refreshLabel}\n </Button>\n </Alert>\n );\n}\n"]}
@@ -1,38 +1,14 @@
1
1
  import * as React from 'react';
2
2
  import { type ButtonProps } from './button.js';
3
- /**
4
- * Standard button DOM props forwarded to the rendered element, minus the props
5
- * this component owns. Owned props are excluded (not just overridden) so a
6
- * consumer can never route them through the passthrough channel: `aria-label`
7
- * is set via {@link ScrollToTopButtonProps.ariaLabel}, extra classes via the
8
- * merge-aware {@link ScrollToTopButtonProps.className}, content via
9
- * {@link ScrollToTopButtonProps.icon}, and `onClick` is fixed to scroll-to-top
10
- * (its scroll animation configurable via
11
- * {@link ScrollToTopButtonProps.scrollBehavior}). This mirrors how the sibling
12
- * `Button` destructures its owned `className` out of the forwarded `...props`.
13
- */
14
3
  type ScrollToTopButtonPassthrough = Omit<React.ComponentPropsWithoutRef<'button'>, 'className' | 'onClick' | 'aria-label' | 'children'>;
15
4
  export interface ScrollToTopButtonProps extends ScrollToTopButtonPassthrough {
16
- /** Scroll threshold (px) before the button appears. Default 400. */
17
5
  threshold?: number;
18
- /** Aria label. Default "Scroll to top". */
19
6
  ariaLabel?: string;
20
- /** Optional extra classes for the button. Merged with the component's own classes. */
21
7
  className?: string;
22
- /** Optional override for the icon. */
23
8
  icon?: React.ReactNode;
24
- /** Button variant. Forwarded to the underlying Button. Defaults to Button's own default. */
25
9
  variant?: ButtonProps['variant'];
26
- /** Button size. Forwarded to the underlying Button. Default "icon". */
27
10
  size?: ButtonProps['size'];
28
- /**
29
- * Scroll behavior passed to `window.scrollTo`. Default "smooth". Pass "auto"
30
- * to skip the smooth animation (e.g. for a reduced-motion mode). The package
31
- * stays motion-policy agnostic: the consumer decides what to pass here — an
32
- * explicit JS `behavior: "smooth"` is not overridden by a CSS
33
- * `scroll-behavior` kill-switch, so an app-level reduced-motion mode must
34
- * pass "auto" to actually stop the animation.
35
- */
11
+ /** An explicit JS `behavior: "smooth"` ignores a CSS `scroll-behavior` override, so a reduced-motion mode passes "auto". */
36
12
  scrollBehavior?: ScrollBehavior;
37
13
  }
38
14
  export declare function ScrollToTopButton({ threshold, ariaLabel, className, icon, variant, size, scrollBehavior, ...rest }: ScrollToTopButtonProps): React.JSX.Element;
@@ -1 +1 @@
1
- {"version":3,"file":"scroll-to-top-button.d.ts","sourceRoot":"","sources":["../../../src/react/components/scroll-to-top-button.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAAU,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAGvD;;;;;;;;;;GAUG;AACH,KAAK,4BAA4B,GAAG,IAAI,CACtC,KAAK,CAAC,wBAAwB,CAAC,QAAQ,CAAC,EACxC,WAAW,GAAG,SAAS,GAAG,YAAY,GAAG,UAAU,CACpD,CAAC;AAEF,MAAM,WAAW,sBAAuB,SAAQ,4BAA4B;IAC1E,oEAAoE;IACpE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sFAAsF;IACtF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sCAAsC;IACtC,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,4FAA4F;IAC5F,OAAO,CAAC,EAAE,WAAW,CAAC,SAAS,CAAC,CAAC;IACjC,uEAAuE;IACvE,IAAI,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC3B;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAED,wBAAgB,iBAAiB,CAAC,EAChC,SAAe,EACf,SAA2B,EAC3B,SAAS,EACT,IAAI,EACJ,OAAO,EACP,IAAa,EACb,cAAyB,EACzB,GAAG,IAAI,EACR,EAAE,sBAAsB,qBA4BxB"}
1
+ {"version":3,"file":"scroll-to-top-button.d.ts","sourceRoot":"","sources":["../../../src/react/components/scroll-to-top-button.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAAU,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAGvD,KAAK,4BAA4B,GAAG,IAAI,CACtC,KAAK,CAAC,wBAAwB,CAAC,QAAQ,CAAC,EACxC,WAAW,GAAG,SAAS,GAAG,YAAY,GAAG,UAAU,CACpD,CAAC;AAEF,MAAM,WAAW,sBAAuB,SAAQ,4BAA4B;IAC1E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,OAAO,CAAC,EAAE,WAAW,CAAC,SAAS,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC3B,4HAA4H;IAC5H,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAED,wBAAgB,iBAAiB,CAAC,EAChC,SAAe,EACf,SAA2B,EAC3B,SAAS,EACT,IAAI,EACJ,OAAO,EACP,IAAa,EACb,cAAyB,EACzB,GAAG,IAAI,EACR,EAAE,sBAAsB,qBA4BxB"}
@@ -1 +1 @@
1
- {"version":3,"file":"scroll-to-top-button.js","sourceRoot":"","sources":["../../../src/react/components/scroll-to-top-button.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,MAAM,EAAoB,MAAM,aAAa,CAAC;AACvD,OAAO,EAAE,EAAE,EAAE,MAAM,oBAAoB,CAAC;AA0CxC,MAAM,UAAU,iBAAiB,CAAC,EAChC,SAAS,GAAG,GAAG,EACf,SAAS,GAAG,eAAe,EAC3B,SAAS,EACT,IAAI,EACJ,OAAO,EACP,IAAI,GAAG,MAAM,EACb,cAAc,GAAG,QAAQ,EACzB,GAAG,IAAI,EACgB;IACvB,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAExD,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,MAAM,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;QAC9D,MAAM,EAAE,CAAC;QACT,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7D,OAAO,GAAG,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC5D,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;IAEhB,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC,CAAC;IAE5E,OAAO,CACL,KAAC,MAAM,OACD,IAAI,EACR,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,OAAO,gBACJ,SAAS,EACrB,SAAS,EAAE,EAAE,CACX,yJAAyJ,EACzJ,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,+BAA+B,EAC3D,SAAS,CACV,YAEA,IAAI,IAAI,KAAC,SAAS,IAAC,SAAS,EAAC,SAAS,GAAG,GACnC,CACV,CAAC;AACJ,CAAC","sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { ChevronUp } from 'lucide-react';\nimport { Button, type ButtonProps } from './button.js';\nimport { cn } from '../../lib/utils.js';\n\n/**\n * Standard button DOM props forwarded to the rendered element, minus the props\n * this component owns. Owned props are excluded (not just overridden) so a\n * consumer can never route them through the passthrough channel: `aria-label`\n * is set via {@link ScrollToTopButtonProps.ariaLabel}, extra classes via the\n * merge-aware {@link ScrollToTopButtonProps.className}, content via\n * {@link ScrollToTopButtonProps.icon}, and `onClick` is fixed to scroll-to-top\n * (its scroll animation configurable via\n * {@link ScrollToTopButtonProps.scrollBehavior}). This mirrors how the sibling\n * `Button` destructures its owned `className` out of the forwarded `...props`.\n */\ntype ScrollToTopButtonPassthrough = Omit<\n React.ComponentPropsWithoutRef<'button'>,\n 'className' | 'onClick' | 'aria-label' | 'children'\n>;\n\nexport interface ScrollToTopButtonProps extends ScrollToTopButtonPassthrough {\n /** Scroll threshold (px) before the button appears. Default 400. */\n threshold?: number;\n /** Aria label. Default \"Scroll to top\". */\n ariaLabel?: string;\n /** Optional extra classes for the button. Merged with the component's own classes. */\n className?: string;\n /** Optional override for the icon. */\n icon?: React.ReactNode;\n /** Button variant. Forwarded to the underlying Button. Defaults to Button's own default. */\n variant?: ButtonProps['variant'];\n /** Button size. Forwarded to the underlying Button. Default \"icon\". */\n size?: ButtonProps['size'];\n /**\n * Scroll behavior passed to `window.scrollTo`. Default \"smooth\". Pass \"auto\"\n * to skip the smooth animation (e.g. for a reduced-motion mode). The package\n * stays motion-policy agnostic: the consumer decides what to pass here — an\n * explicit JS `behavior: \"smooth\"` is not overridden by a CSS\n * `scroll-behavior` kill-switch, so an app-level reduced-motion mode must\n * pass \"auto\" to actually stop the animation.\n */\n scrollBehavior?: ScrollBehavior;\n}\n\nexport function ScrollToTopButton({\n threshold = 400,\n ariaLabel = 'Scroll to top',\n className,\n icon,\n variant,\n size = 'icon',\n scrollBehavior = 'smooth',\n ...rest\n}: ScrollToTopButtonProps) {\n const [isVisible, setIsVisible] = React.useState(false);\n\n React.useEffect(() => {\n const toggle = () => setIsVisible(window.scrollY > threshold);\n toggle();\n window.addEventListener('scroll', toggle, { passive: true });\n return () => window.removeEventListener('scroll', toggle);\n }, [threshold]);\n\n const onClick = () => window.scrollTo({ top: 0, behavior: scrollBehavior });\n\n return (\n <Button\n {...rest}\n variant={variant}\n size={size}\n onClick={onClick}\n aria-label={ariaLabel}\n className={cn(\n 'fixed bottom-24 left-1/2 -translate-x-1/2 z-50 rounded-full elevation-raised transition-opacity duration-[var(--motion-base)] ease-[var(--motion-ease)]',\n isVisible ? 'opacity-100' : 'opacity-0 pointer-events-none',\n className,\n )}\n >\n {icon ?? <ChevronUp className=\"h-5 w-5\" />}\n </Button>\n );\n}\n"]}
1
+ {"version":3,"file":"scroll-to-top-button.js","sourceRoot":"","sources":["../../../src/react/components/scroll-to-top-button.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,MAAM,EAAoB,MAAM,aAAa,CAAC;AACvD,OAAO,EAAE,EAAE,EAAE,MAAM,oBAAoB,CAAC;AAkBxC,MAAM,UAAU,iBAAiB,CAAC,EAChC,SAAS,GAAG,GAAG,EACf,SAAS,GAAG,eAAe,EAC3B,SAAS,EACT,IAAI,EACJ,OAAO,EACP,IAAI,GAAG,MAAM,EACb,cAAc,GAAG,QAAQ,EACzB,GAAG,IAAI,EACgB;IACvB,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAExD,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,MAAM,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;QAC9D,MAAM,EAAE,CAAC;QACT,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7D,OAAO,GAAG,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC5D,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;IAEhB,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC,CAAC;IAE5E,OAAO,CACL,KAAC,MAAM,OACD,IAAI,EACR,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,OAAO,gBACJ,SAAS,EACrB,SAAS,EAAE,EAAE,CACX,yJAAyJ,EACzJ,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,+BAA+B,EAC3D,SAAS,CACV,YAEA,IAAI,IAAI,KAAC,SAAS,IAAC,SAAS,EAAC,SAAS,GAAG,GACnC,CACV,CAAC;AACJ,CAAC","sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { ChevronUp } from 'lucide-react';\nimport { Button, type ButtonProps } from './button.js';\nimport { cn } from '../../lib/utils.js';\n\ntype ScrollToTopButtonPassthrough = Omit<\n React.ComponentPropsWithoutRef<'button'>,\n 'className' | 'onClick' | 'aria-label' | 'children'\n>;\n\nexport interface ScrollToTopButtonProps extends ScrollToTopButtonPassthrough {\n threshold?: number;\n ariaLabel?: string;\n className?: string;\n icon?: React.ReactNode;\n variant?: ButtonProps['variant'];\n size?: ButtonProps['size'];\n /** An explicit JS `behavior: \"smooth\"` ignores a CSS `scroll-behavior` override, so a reduced-motion mode passes \"auto\". */\n scrollBehavior?: ScrollBehavior;\n}\n\nexport function ScrollToTopButton({\n threshold = 400,\n ariaLabel = 'Scroll to top',\n className,\n icon,\n variant,\n size = 'icon',\n scrollBehavior = 'smooth',\n ...rest\n}: ScrollToTopButtonProps) {\n const [isVisible, setIsVisible] = React.useState(false);\n\n React.useEffect(() => {\n const toggle = () => setIsVisible(window.scrollY > threshold);\n toggle();\n window.addEventListener('scroll', toggle, { passive: true });\n return () => window.removeEventListener('scroll', toggle);\n }, [threshold]);\n\n const onClick = () => window.scrollTo({ top: 0, behavior: scrollBehavior });\n\n return (\n <Button\n {...rest}\n variant={variant}\n size={size}\n onClick={onClick}\n aria-label={ariaLabel}\n className={cn(\n 'fixed bottom-24 left-1/2 -translate-x-1/2 z-50 rounded-full elevation-raised transition-opacity duration-[var(--motion-base)] ease-[var(--motion-ease)]',\n isVisible ? 'opacity-100' : 'opacity-0 pointer-events-none',\n className,\n )}\n >\n {icon ?? <ChevronUp className=\"h-5 w-5\" />}\n </Button>\n );\n}\n"]}
@@ -1,75 +1,22 @@
1
1
  export interface SearchDropdownProps<T> {
2
- /** Current search value */
3
2
  value: string;
4
- /** Called when search value changes */
5
3
  onValueChange: (value: string) => void;
6
- /** Search results array */
7
4
  results: T[];
8
- /** Loading state */
9
5
  isLoading: boolean;
10
- /** The search's failure exactly as the data layer reported it; `null` or `undefined` when it did not fail. */
11
6
  error: unknown;
12
- /**
13
- * Renders a failed search inside the open dropdown. The consumer owns the failure's words and any
14
- * retry, so the dropdown never shows a failure state of its own.
15
- */
16
7
  renderError: (error: unknown) => React.ReactNode;
17
- /** Called when a result is selected */
18
8
  onSelect: (result: T) => void;
19
- /** Called when search is cleared (after the value has been reset). Optional notification hook. */
20
9
  onClear?: () => void;
21
- /** Placeholder text */
22
10
  placeholder?: string;
23
- /** Label for the input */
24
11
  label?: string;
25
- /** Custom className */
26
12
  className?: string;
27
- /** Disabled state */
28
13
  disabled?: boolean;
29
- /** Icon to show in input (default: Search) */
30
14
  icon?: React.ReactNode;
31
- /**
32
- * The search rule of the hook behind the dropdown, passed from that hook rather than restated:
33
- * `isSearchable(value)` decides whether the value is searched (and the dropdown opens), and
34
- * `minChars` is the minimum the below-minimum hint names.
35
- */
36
15
  minChars: number;
37
16
  isSearchable: (value: string) => boolean;
38
- /** Message to show when no results found */
39
17
  emptyMessage?: string;
40
- /** What assistive technology hears when results arrive (default: "1 result" / "N results"). */
41
18
  resultsAnnouncement?: (count: number) => string;
42
- /** Custom render function for each result */
43
19
  renderResult: (result: T, index: number) => React.ReactNode;
44
20
  }
45
- /**
46
- * A search box with a results dropdown, following the combobox pattern: the input keeps focus, the
47
- * results are a listbox, and the arrow-highlighted result is the input's active descendant. Every
48
- * instance carries its own ids, so several can share a page.
49
- *
50
- * Once the value is long enough the dropdown stays open through every settled state — results, an
51
- * empty answer, or a failure — until the user dismisses it (Escape, a click outside, a selection).
52
- * A dismissal holds for the value it was made on: re-rendered props never reopen it, while typing,
53
- * a new value, or ArrowDown does.
54
- *
55
- * @example
56
- * ```tsx
57
- * <SearchDropdown<User>
58
- * value={searchValue}
59
- * onValueChange={setSearchValue}
60
- * results={users}
61
- * isLoading={isLoading}
62
- * error={error}
63
- * renderError={(error) => <MyErrorState error={error} />}
64
- * minChars={SEARCH_MIN_LENGTH}
65
- * isSearchable={isSearchableText}
66
- * onSelect={(user) => console.log(user)}
67
- * placeholder="Search users..."
68
- * renderResult={(user) => (
69
- * <div>{user.displayName}</div>
70
- * )}
71
- * />
72
- * ```
73
- */
74
21
  export declare function SearchDropdown<T>({ value, onValueChange, results, isLoading, error, renderError, onSelect, onClear, placeholder, label, className, disabled, icon, minChars, isSearchable, emptyMessage, resultsAnnouncement, renderResult, }: SearchDropdownProps<T>): import("react").JSX.Element;
75
22
  //# sourceMappingURL=search-dropdown.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"search-dropdown.d.ts","sourceRoot":"","sources":["../../../src/react/components/search-dropdown.tsx"],"names":[],"mappings":"AAaA,MAAM,WAAW,mBAAmB,CAAC,CAAC;IACpC,2BAA2B;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,uCAAuC;IACvC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,2BAA2B;IAC3B,OAAO,EAAE,CAAC,EAAE,CAAC;IACb,oBAAoB;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,8GAA8G;IAC9G,KAAK,EAAE,OAAO,CAAC;IACf;;;OAGG;IACH,WAAW,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,KAAK,CAAC,SAAS,CAAC;IACjD,uCAAuC;IACvC,QAAQ,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,IAAI,CAAC;IAC9B,kGAAkG;IAClG,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,uBAAuB;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0BAA0B;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uBAAuB;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,8CAA8C;IAC9C,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IACzC,4CAA4C;IAC5C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,+FAA+F;IAC/F,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAChD,6CAA6C;IAC7C,YAAY,EAAE,CAAC,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC;CAC7D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,EAChC,KAAK,EACL,aAAa,EACb,OAAO,EACP,SAAS,EACT,KAAK,EACL,WAAW,EACX,QAAQ,EACR,OAAO,EACP,WAAyB,EACzB,KAAK,EACL,SAAS,EACT,QAAgB,EAChB,IAAqC,EACrC,QAAQ,EACR,YAAY,EACZ,YAAiC,EACjC,mBAAgD,EAChD,YAAY,GACb,EAAE,mBAAmB,CAAC,CAAC,CAAC,+BA2MxB"}
1
+ {"version":3,"file":"search-dropdown.d.ts","sourceRoot":"","sources":["../../../src/react/components/search-dropdown.tsx"],"names":[],"mappings":"AAaA,MAAM,WAAW,mBAAmB,CAAC,CAAC;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,OAAO,EAAE,CAAC,EAAE,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;IACf,WAAW,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,KAAK,CAAC,SAAS,CAAC;IACjD,QAAQ,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,IAAI,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IACzC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAChD,YAAY,EAAE,CAAC,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC;CAC7D;AAED,wBAAgB,cAAc,CAAC,CAAC,EAAE,EAChC,KAAK,EACL,aAAa,EACb,OAAO,EACP,SAAS,EACT,KAAK,EACL,WAAW,EACX,QAAQ,EACR,OAAO,EACP,WAAyB,EACzB,KAAK,EACL,SAAS,EACT,QAAgB,EAChB,IAAqC,EACrC,QAAQ,EACR,YAAY,EACZ,YAAiC,EACjC,mBAAgD,EAChD,YAAY,GACb,EAAE,mBAAmB,CAAC,CAAC,CAAC,+BAqMxB"}
@@ -9,35 +9,6 @@ import { cn } from '../../lib/utils.js';
9
9
  function defaultResultsAnnouncement(count) {
10
10
  return count === 1 ? '1 result' : `${count} results`;
11
11
  }
12
- /**
13
- * A search box with a results dropdown, following the combobox pattern: the input keeps focus, the
14
- * results are a listbox, and the arrow-highlighted result is the input's active descendant. Every
15
- * instance carries its own ids, so several can share a page.
16
- *
17
- * Once the value is long enough the dropdown stays open through every settled state — results, an
18
- * empty answer, or a failure — until the user dismisses it (Escape, a click outside, a selection).
19
- * A dismissal holds for the value it was made on: re-rendered props never reopen it, while typing,
20
- * a new value, or ArrowDown does.
21
- *
22
- * @example
23
- * ```tsx
24
- * <SearchDropdown<User>
25
- * value={searchValue}
26
- * onValueChange={setSearchValue}
27
- * results={users}
28
- * isLoading={isLoading}
29
- * error={error}
30
- * renderError={(error) => <MyErrorState error={error} />}
31
- * minChars={SEARCH_MIN_LENGTH}
32
- * isSearchable={isSearchableText}
33
- * onSelect={(user) => console.log(user)}
34
- * placeholder="Search users..."
35
- * renderResult={(user) => (
36
- * <div>{user.displayName}</div>
37
- * )}
38
- * />
39
- * ```
40
- */
41
12
  export function SearchDropdown({ value, onValueChange, results, isLoading, error, renderError, onSelect, onClear, placeholder = 'Search...', label, className, disabled = false, icon = _jsx(Search, { className: "h-4 w-4" }), minChars, isSearchable, emptyMessage = 'No results found', resultsAnnouncement = defaultResultsAnnouncement, renderResult, }) {
42
13
  const baseId = useId();
43
14
  const inputId = `${baseId}-input`;
@@ -47,8 +18,6 @@ export function SearchDropdown({ value, onValueChange, results, isLoading, error
47
18
  const hintId = `${baseId}-hint`;
48
19
  const optionId = (index) => `${baseId}-option-${index}`;
49
20
  const [dismissedFor, setDismissedFor] = useState(null);
50
- // The highlight belongs to the value it was made on, so a new query starts with none, while a
51
- // refreshed result list for the same query keeps it.
52
21
  const [highlight, setHighlight] = useState(null);
53
22
  const containerRef = useRef(null);
54
23
  const inputRef = useRef(null);
@@ -112,8 +81,7 @@ export function SearchDropdown({ value, onValueChange, results, isLoading, error
112
81
  select(results[activeIndex]);
113
82
  break;
114
83
  case 'Escape':
115
- // Escape never falls through to the search field's own clear, which would skip onClear:
116
- // it closes an open dropdown, and clears a closed one through handleClear.
84
+ // Escape never falls through to the search field's own clear, which would skip onClear.
117
85
  e.preventDefault();
118
86
  if (isOpen)
119
87
  setDismissedFor(value);
@@ -131,8 +99,6 @@ export function SearchDropdown({ value, onValueChange, results, isLoading, error
131
99
  return (_jsxs("div", { ref: containerRef, className: cn('relative', className), children: [label && (_jsx(Label, { id: labelId, htmlFor: inputId, className: "mb-2 block", children: label })), _jsxs("div", { className: "relative", children: [_jsx("div", { className: "absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground", "aria-hidden": "true", children: icon }), _jsx(Input, { ref: inputRef, id: inputId, type: "search", role: "combobox", "aria-autocomplete": "list", "aria-expanded": isOpen, "aria-controls": isOpen ? (showOptions ? listboxId : panelId) : undefined, "aria-activedescendant": activeIndex >= 0 ? optionId(activeIndex) : undefined, "aria-describedby": showHint ? hintId : undefined, autoComplete: "off", value: value, onChange: (e) => {
132
100
  setDismissedFor(null);
133
101
  onValueChange(e.target.value);
134
- }, onKeyDown: handleKeyDown, placeholder: placeholder, disabled: disabled, className: "pl-10 pr-10 [&::-webkit-search-cancel-button]:appearance-none" }), _jsx("div", { className: "absolute right-3 top-1/2 -translate-y-1/2", children: isLoading ? (_jsx(Spinner, { size: "xs", className: "text-muted-foreground" })) : value ? (_jsx("button", { type: "button", onClick: handleClear, className: "text-muted-foreground hover:text-foreground transition-colors", "aria-label": "Clear search", children: _jsx(X, { className: "h-4 w-4" }) })) : null })] }), isOpen && (_jsx("div", { id: panelId, className: "absolute z-50 w-full mt-1 bg-popover border rounded-md elevation-raised max-h-60 overflow-auto", children: failed ? (renderError(error)) : isLoading ? (_jsxs("div", { className: "px-3 py-2 text-sm text-muted-foreground flex items-center gap-2", children: [_jsx(Spinner, { size: "xs" }), "Searching..."] })) : results.length === 0 ? (_jsx("div", { className: "px-3 py-2 text-sm text-muted-foreground", children: emptyMessage })) : (_jsx("div", { role: "listbox", id: listboxId, "aria-labelledby": label ? labelId : undefined, "aria-label": label ? undefined : placeholder, children: results.map((result, index) => (_jsx("div", { id: optionId(index), role: "option", "aria-selected": index === activeIndex,
135
- // Keeps focus in the input, where the combobox's keyboard handling lives.
136
- onMouseDown: (e) => e.preventDefault(), onClick: () => select(result), onMouseEnter: () => setHighlight({ value, index }), className: cn('w-full text-left transition-colors cursor-pointer hover:bg-accent', index === activeIndex && 'bg-accent'), children: renderResult(result, index) }, index))) })) })), _jsx("div", { role: "status", "aria-live": "polite", className: "sr-only", children: announcement }), showHint && (_jsxs("p", { id: hintId, className: "text-xs text-muted-foreground mt-1", children: ["Type at least ", minChars, " characters to search"] }))] }));
102
+ }, onKeyDown: handleKeyDown, placeholder: placeholder, disabled: disabled, className: "pl-10 pr-10 [&::-webkit-search-cancel-button]:appearance-none" }), _jsx("div", { className: "absolute right-3 top-1/2 -translate-y-1/2", children: isLoading ? (_jsx(Spinner, { size: "xs", className: "text-muted-foreground" })) : value ? (_jsx("button", { type: "button", onClick: handleClear, className: "text-muted-foreground hover:text-foreground transition-colors", "aria-label": "Clear search", children: _jsx(X, { className: "h-4 w-4" }) })) : null })] }), isOpen && (_jsx("div", { id: panelId, className: "absolute z-50 w-full mt-1 bg-popover border rounded-md elevation-raised max-h-60 overflow-auto", children: failed ? (renderError(error)) : isLoading ? (_jsxs("div", { className: "px-3 py-2 text-sm text-muted-foreground flex items-center gap-2", children: [_jsx(Spinner, { size: "xs" }), "Searching..."] })) : results.length === 0 ? (_jsx("div", { className: "px-3 py-2 text-sm text-muted-foreground", children: emptyMessage })) : (_jsx("div", { role: "listbox", id: listboxId, "aria-labelledby": label ? labelId : undefined, "aria-label": label ? undefined : placeholder, children: results.map((result, index) => (_jsx("div", { id: optionId(index), role: "option", "aria-selected": index === activeIndex, onMouseDown: (e) => e.preventDefault(), onClick: () => select(result), onMouseEnter: () => setHighlight({ value, index }), className: cn('w-full text-left transition-colors cursor-pointer hover:bg-accent', index === activeIndex && 'bg-accent'), children: renderResult(result, index) }, index))) })) })), _jsx("div", { role: "status", "aria-live": "polite", className: "sr-only", children: announcement }), showHint && (_jsxs("p", { id: hintId, className: "text-xs text-muted-foreground mt-1", children: ["Type at least ", minChars, " characters to search"] }))] }));
137
103
  }
138
104
  //# sourceMappingURL=search-dropdown.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"search-dropdown.js","sourceRoot":"","sources":["../../../src/react/components/search-dropdown.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useState, useRef, useEffect, useId } from 'react';\nimport { Input } from './input.js';\nimport { Label } from './label.js';\nimport { X, Search } from 'lucide-react';\nimport { Spinner } from './spinner.js';\nimport { cn } from '../../lib/utils.js';\n\nfunction defaultResultsAnnouncement(count: number): string {\n return count === 1 ? '1 result' : `${count} results`;\n}\n\nexport interface SearchDropdownProps<T> {\n /** Current search value */\n value: string;\n /** Called when search value changes */\n onValueChange: (value: string) => void;\n /** Search results array */\n results: T[];\n /** Loading state */\n isLoading: boolean;\n /** The search's failure exactly as the data layer reported it; `null` or `undefined` when it did not fail. */\n error: unknown;\n /**\n * Renders a failed search inside the open dropdown. The consumer owns the failure's words and any\n * retry, so the dropdown never shows a failure state of its own.\n */\n renderError: (error: unknown) => React.ReactNode;\n /** Called when a result is selected */\n onSelect: (result: T) => void;\n /** Called when search is cleared (after the value has been reset). Optional notification hook. */\n onClear?: () => void;\n /** Placeholder text */\n placeholder?: string;\n /** Label for the input */\n label?: string;\n /** Custom className */\n className?: string;\n /** Disabled state */\n disabled?: boolean;\n /** Icon to show in input (default: Search) */\n icon?: React.ReactNode;\n /**\n * The search rule of the hook behind the dropdown, passed from that hook rather than restated:\n * `isSearchable(value)` decides whether the value is searched (and the dropdown opens), and\n * `minChars` is the minimum the below-minimum hint names.\n */\n minChars: number;\n isSearchable: (value: string) => boolean;\n /** Message to show when no results found */\n emptyMessage?: string;\n /** What assistive technology hears when results arrive (default: \"1 result\" / \"N results\"). */\n resultsAnnouncement?: (count: number) => string;\n /** Custom render function for each result */\n renderResult: (result: T, index: number) => React.ReactNode;\n}\n\n/**\n * A search box with a results dropdown, following the combobox pattern: the input keeps focus, the\n * results are a listbox, and the arrow-highlighted result is the input's active descendant. Every\n * instance carries its own ids, so several can share a page.\n *\n * Once the value is long enough the dropdown stays open through every settled state — results, an\n * empty answer, or a failure — until the user dismisses it (Escape, a click outside, a selection).\n * A dismissal holds for the value it was made on: re-rendered props never reopen it, while typing,\n * a new value, or ArrowDown does.\n *\n * @example\n * ```tsx\n * <SearchDropdown<User>\n * value={searchValue}\n * onValueChange={setSearchValue}\n * results={users}\n * isLoading={isLoading}\n * error={error}\n * renderError={(error) => <MyErrorState error={error} />}\n * minChars={SEARCH_MIN_LENGTH}\n * isSearchable={isSearchableText}\n * onSelect={(user) => console.log(user)}\n * placeholder=\"Search users...\"\n * renderResult={(user) => (\n * <div>{user.displayName}</div>\n * )}\n * />\n * ```\n */\nexport function SearchDropdown<T>({\n value,\n onValueChange,\n results,\n isLoading,\n error,\n renderError,\n onSelect,\n onClear,\n placeholder = 'Search...',\n label,\n className,\n disabled = false,\n icon = <Search className=\"h-4 w-4\" />,\n minChars,\n isSearchable,\n emptyMessage = 'No results found',\n resultsAnnouncement = defaultResultsAnnouncement,\n renderResult,\n}: SearchDropdownProps<T>) {\n const baseId = useId();\n const inputId = `${baseId}-input`;\n const labelId = `${baseId}-label`;\n const listboxId = `${baseId}-listbox`;\n const panelId = `${baseId}-panel`;\n const hintId = `${baseId}-hint`;\n const optionId = (index: number) => `${baseId}-option-${index}`;\n\n const [dismissedFor, setDismissedFor] = useState<string | null>(null);\n // The highlight belongs to the value it was made on, so a new query starts with none, while a\n // refreshed result list for the same query keeps it.\n const [highlight, setHighlight] = useState<{ value: string; index: number } | null>(null);\n const containerRef = useRef<HTMLDivElement>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n\n const searchable = isSearchable(value);\n const isOpen = searchable && dismissedFor !== value;\n const failed = error !== null && error !== undefined;\n const showOptions = isOpen && !failed && !isLoading && results.length > 0;\n const activeIndex =\n showOptions && highlight && highlight.value === value && highlight.index < results.length\n ? highlight.index\n : -1;\n const showHint = value.length > 0 && !searchable;\n const announcement =\n !isOpen || failed\n ? ''\n : isLoading\n ? 'Searching...'\n : results.length === 0\n ? emptyMessage\n : resultsAnnouncement(results.length);\n\n useEffect(() => {\n if (!isOpen) return;\n const handleClickOutside = (event: MouseEvent) => {\n if (containerRef.current && !containerRef.current.contains(event.target as Node)) {\n setDismissedFor(value);\n }\n };\n document.addEventListener('mousedown', handleClickOutside);\n return () => document.removeEventListener('mousedown', handleClickOutside);\n }, [isOpen, value]);\n\n const select = (result: T) => {\n onSelect(result);\n setDismissedFor(value);\n };\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n switch (e.key) {\n case 'ArrowDown':\n if (!searchable) return;\n e.preventDefault();\n if (!isOpen) {\n setDismissedFor(null);\n return;\n }\n if (!showOptions) return;\n setHighlight({\n value,\n index: activeIndex < results.length - 1 ? activeIndex + 1 : activeIndex,\n });\n break;\n case 'ArrowUp':\n if (!showOptions) return;\n e.preventDefault();\n setHighlight({ value, index: activeIndex > 0 ? activeIndex - 1 : 0 });\n break;\n case 'Enter':\n if (activeIndex < 0) return;\n e.preventDefault();\n select(results[activeIndex]);\n break;\n case 'Escape':\n // Escape never falls through to the search field's own clear, which would skip onClear:\n // it closes an open dropdown, and clears a closed one through handleClear.\n e.preventDefault();\n if (isOpen) setDismissedFor(value);\n else if (value) handleClear();\n break;\n }\n };\n\n const handleClear = () => {\n onValueChange('');\n onClear?.();\n setDismissedFor(null);\n inputRef.current?.focus();\n };\n\n return (\n <div ref={containerRef} className={cn('relative', className)}>\n {label && (\n <Label id={labelId} htmlFor={inputId} className=\"mb-2 block\">\n {label}\n </Label>\n )}\n\n <div className=\"relative\">\n <div\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground\"\n aria-hidden=\"true\"\n >\n {icon}\n </div>\n\n <Input\n ref={inputRef}\n id={inputId}\n type=\"search\"\n role=\"combobox\"\n aria-autocomplete=\"list\"\n aria-expanded={isOpen}\n aria-controls={isOpen ? (showOptions ? listboxId : panelId) : undefined}\n aria-activedescendant={activeIndex >= 0 ? optionId(activeIndex) : undefined}\n aria-describedby={showHint ? hintId : undefined}\n autoComplete=\"off\"\n value={value}\n onChange={(e) => {\n setDismissedFor(null);\n onValueChange(e.target.value);\n }}\n onKeyDown={handleKeyDown}\n placeholder={placeholder}\n disabled={disabled}\n className=\"pl-10 pr-10 [&::-webkit-search-cancel-button]:appearance-none\"\n />\n\n <div className=\"absolute right-3 top-1/2 -translate-y-1/2\">\n {isLoading ? (\n <Spinner size=\"xs\" className=\"text-muted-foreground\" />\n ) : value ? (\n <button\n type=\"button\"\n onClick={handleClear}\n className=\"text-muted-foreground hover:text-foreground transition-colors\"\n aria-label=\"Clear search\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n ) : null}\n </div>\n </div>\n\n {isOpen && (\n <div\n id={panelId}\n className=\"absolute z-50 w-full mt-1 bg-popover border rounded-md elevation-raised max-h-60 overflow-auto\"\n >\n {failed ? (\n renderError(error)\n ) : isLoading ? (\n <div className=\"px-3 py-2 text-sm text-muted-foreground flex items-center gap-2\">\n <Spinner size=\"xs\" />\n Searching...\n </div>\n ) : results.length === 0 ? (\n <div className=\"px-3 py-2 text-sm text-muted-foreground\">{emptyMessage}</div>\n ) : (\n <div\n role=\"listbox\"\n id={listboxId}\n aria-labelledby={label ? labelId : undefined}\n aria-label={label ? undefined : placeholder}\n >\n {results.map((result, index) => (\n <div\n key={index}\n id={optionId(index)}\n role=\"option\"\n aria-selected={index === activeIndex}\n // Keeps focus in the input, where the combobox's keyboard handling lives.\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => select(result)}\n onMouseEnter={() => setHighlight({ value, index })}\n className={cn(\n 'w-full text-left transition-colors cursor-pointer hover:bg-accent',\n index === activeIndex && 'bg-accent',\n )}\n >\n {renderResult(result, index)}\n </div>\n ))}\n </div>\n )}\n </div>\n )}\n\n {/* Always mounted, so every change of the open panel's state is announced. A failure is\n announced by the consumer's renderError. */}\n <div role=\"status\" aria-live=\"polite\" className=\"sr-only\">\n {announcement}\n </div>\n\n {showHint && (\n <p id={hintId} className=\"text-xs text-muted-foreground mt-1\">\n Type at least {minChars} characters to search\n </p>\n )}\n </div>\n );\n}\n"]}
1
+ {"version":3,"file":"search-dropdown.js","sourceRoot":"","sources":["../../../src/react/components/search-dropdown.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useState, useRef, useEffect, useId } from 'react';\nimport { Input } from './input.js';\nimport { Label } from './label.js';\nimport { X, Search } from 'lucide-react';\nimport { Spinner } from './spinner.js';\nimport { cn } from '../../lib/utils.js';\n\nfunction defaultResultsAnnouncement(count: number): string {\n return count === 1 ? '1 result' : `${count} results`;\n}\n\nexport interface SearchDropdownProps<T> {\n value: string;\n onValueChange: (value: string) => void;\n results: T[];\n isLoading: boolean;\n error: unknown;\n renderError: (error: unknown) => React.ReactNode;\n onSelect: (result: T) => void;\n onClear?: () => void;\n placeholder?: string;\n label?: string;\n className?: string;\n disabled?: boolean;\n icon?: React.ReactNode;\n minChars: number;\n isSearchable: (value: string) => boolean;\n emptyMessage?: string;\n resultsAnnouncement?: (count: number) => string;\n renderResult: (result: T, index: number) => React.ReactNode;\n}\n\nexport function SearchDropdown<T>({\n value,\n onValueChange,\n results,\n isLoading,\n error,\n renderError,\n onSelect,\n onClear,\n placeholder = 'Search...',\n label,\n className,\n disabled = false,\n icon = <Search className=\"h-4 w-4\" />,\n minChars,\n isSearchable,\n emptyMessage = 'No results found',\n resultsAnnouncement = defaultResultsAnnouncement,\n renderResult,\n}: SearchDropdownProps<T>) {\n const baseId = useId();\n const inputId = `${baseId}-input`;\n const labelId = `${baseId}-label`;\n const listboxId = `${baseId}-listbox`;\n const panelId = `${baseId}-panel`;\n const hintId = `${baseId}-hint`;\n const optionId = (index: number) => `${baseId}-option-${index}`;\n\n const [dismissedFor, setDismissedFor] = useState<string | null>(null);\n const [highlight, setHighlight] = useState<{ value: string; index: number } | null>(null);\n const containerRef = useRef<HTMLDivElement>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n\n const searchable = isSearchable(value);\n const isOpen = searchable && dismissedFor !== value;\n const failed = error !== null && error !== undefined;\n const showOptions = isOpen && !failed && !isLoading && results.length > 0;\n const activeIndex =\n showOptions && highlight && highlight.value === value && highlight.index < results.length\n ? highlight.index\n : -1;\n const showHint = value.length > 0 && !searchable;\n const announcement =\n !isOpen || failed\n ? ''\n : isLoading\n ? 'Searching...'\n : results.length === 0\n ? emptyMessage\n : resultsAnnouncement(results.length);\n\n useEffect(() => {\n if (!isOpen) return;\n const handleClickOutside = (event: MouseEvent) => {\n if (containerRef.current && !containerRef.current.contains(event.target as Node)) {\n setDismissedFor(value);\n }\n };\n document.addEventListener('mousedown', handleClickOutside);\n return () => document.removeEventListener('mousedown', handleClickOutside);\n }, [isOpen, value]);\n\n const select = (result: T) => {\n onSelect(result);\n setDismissedFor(value);\n };\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n switch (e.key) {\n case 'ArrowDown':\n if (!searchable) return;\n e.preventDefault();\n if (!isOpen) {\n setDismissedFor(null);\n return;\n }\n if (!showOptions) return;\n setHighlight({\n value,\n index: activeIndex < results.length - 1 ? activeIndex + 1 : activeIndex,\n });\n break;\n case 'ArrowUp':\n if (!showOptions) return;\n e.preventDefault();\n setHighlight({ value, index: activeIndex > 0 ? activeIndex - 1 : 0 });\n break;\n case 'Enter':\n if (activeIndex < 0) return;\n e.preventDefault();\n select(results[activeIndex]);\n break;\n case 'Escape':\n // Escape never falls through to the search field's own clear, which would skip onClear.\n e.preventDefault();\n if (isOpen) setDismissedFor(value);\n else if (value) handleClear();\n break;\n }\n };\n\n const handleClear = () => {\n onValueChange('');\n onClear?.();\n setDismissedFor(null);\n inputRef.current?.focus();\n };\n\n return (\n <div ref={containerRef} className={cn('relative', className)}>\n {label && (\n <Label id={labelId} htmlFor={inputId} className=\"mb-2 block\">\n {label}\n </Label>\n )}\n\n <div className=\"relative\">\n <div\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground\"\n aria-hidden=\"true\"\n >\n {icon}\n </div>\n\n <Input\n ref={inputRef}\n id={inputId}\n type=\"search\"\n role=\"combobox\"\n aria-autocomplete=\"list\"\n aria-expanded={isOpen}\n aria-controls={isOpen ? (showOptions ? listboxId : panelId) : undefined}\n aria-activedescendant={activeIndex >= 0 ? optionId(activeIndex) : undefined}\n aria-describedby={showHint ? hintId : undefined}\n autoComplete=\"off\"\n value={value}\n onChange={(e) => {\n setDismissedFor(null);\n onValueChange(e.target.value);\n }}\n onKeyDown={handleKeyDown}\n placeholder={placeholder}\n disabled={disabled}\n className=\"pl-10 pr-10 [&::-webkit-search-cancel-button]:appearance-none\"\n />\n\n <div className=\"absolute right-3 top-1/2 -translate-y-1/2\">\n {isLoading ? (\n <Spinner size=\"xs\" className=\"text-muted-foreground\" />\n ) : value ? (\n <button\n type=\"button\"\n onClick={handleClear}\n className=\"text-muted-foreground hover:text-foreground transition-colors\"\n aria-label=\"Clear search\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n ) : null}\n </div>\n </div>\n\n {isOpen && (\n <div\n id={panelId}\n className=\"absolute z-50 w-full mt-1 bg-popover border rounded-md elevation-raised max-h-60 overflow-auto\"\n >\n {failed ? (\n renderError(error)\n ) : isLoading ? (\n <div className=\"px-3 py-2 text-sm text-muted-foreground flex items-center gap-2\">\n <Spinner size=\"xs\" />\n Searching...\n </div>\n ) : results.length === 0 ? (\n <div className=\"px-3 py-2 text-sm text-muted-foreground\">{emptyMessage}</div>\n ) : (\n <div\n role=\"listbox\"\n id={listboxId}\n aria-labelledby={label ? labelId : undefined}\n aria-label={label ? undefined : placeholder}\n >\n {results.map((result, index) => (\n <div\n key={index}\n id={optionId(index)}\n role=\"option\"\n aria-selected={index === activeIndex}\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => select(result)}\n onMouseEnter={() => setHighlight({ value, index })}\n className={cn(\n 'w-full text-left transition-colors cursor-pointer hover:bg-accent',\n index === activeIndex && 'bg-accent',\n )}\n >\n {renderResult(result, index)}\n </div>\n ))}\n </div>\n )}\n </div>\n )}\n\n <div role=\"status\" aria-live=\"polite\" className=\"sr-only\">\n {announcement}\n </div>\n\n {showHint && (\n <p id={hintId} className=\"text-xs text-muted-foreground mt-1\">\n Type at least {minChars} characters to search\n </p>\n )}\n </div>\n );\n}\n"]}
@@ -4,13 +4,6 @@ declare const Select: React.FC<SelectPrimitive.SelectProps>;
4
4
  declare const SelectGroup: React.ForwardRefExoticComponent<SelectPrimitive.SelectGroupProps & React.RefAttributes<HTMLDivElement>>;
5
5
  declare const SelectValue: React.ForwardRefExoticComponent<SelectPrimitive.SelectValueProps & React.RefAttributes<HTMLSpanElement>>;
6
6
  declare const SelectTrigger: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectTriggerProps & React.RefAttributes<HTMLButtonElement>, "ref"> & {
7
- /**
8
- * The change this select started is in flight: the trigger ignores opening and typeahead,
9
- * sets `aria-disabled` and `aria-busy`, and shows the spinner in place of its chevron. It
10
- * is never natively disabled while pending (a `disabled` passed alongside applies once
11
- * pending ends), so it keeps focus (FRONTEND-203). It keeps showing
12
- * the COMMITTED value — the caller changes `value` only once the write lands.
13
- */
14
7
  pending?: boolean;
15
8
  } & React.RefAttributes<HTMLButtonElement>>;
16
9
  declare const SelectScrollUpButton: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectScrollUpButtonProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
@@ -1 +1 @@
1
- {"version":3,"file":"select.d.ts","sourceRoot":"","sources":["../../../src/react/components/select.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,eAAe,MAAM,wBAAwB,CAAC;AAO1D,QAAA,MAAM,MAAM,uCAAuB,CAAC;AACpC,QAAA,MAAM,WAAW,yGAAwB,CAAC;AAC1C,QAAA,MAAM,WAAW,0GAAwB,CAAC;AAI1C,QAAA,MAAM,aAAa;IAGf;;;;;;OAMG;cACO,OAAO;2CA+BnB,CAAC;AAGH,QAAA,MAAM,oBAAoB,qKAWxB,CAAC;AAGH,QAAA,MAAM,sBAAsB,uKAW1B,CAAC;AAGH,QAAA,MAAM,aAAa,8JA6BjB,CAAC;AAGH,QAAA,MAAM,WAAW,4JASf,CAAC;AAGH,QAAA,MAAM,UAAU,2JAoBd,CAAC;AAGH,QAAA,MAAM,eAAe,gKASnB,CAAC;AAGH,OAAO,EACL,MAAM,EACN,WAAW,EACX,WAAW,EACX,aAAa,EACb,aAAa,EACb,WAAW,EACX,UAAU,EACV,eAAe,EACf,oBAAoB,EACpB,sBAAsB,GACvB,CAAC"}
1
+ {"version":3,"file":"select.d.ts","sourceRoot":"","sources":["../../../src/react/components/select.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,eAAe,MAAM,wBAAwB,CAAC;AAO1D,QAAA,MAAM,MAAM,uCAAuB,CAAC;AACpC,QAAA,MAAM,WAAW,yGAAwB,CAAC;AAC1C,QAAA,MAAM,WAAW,0GAAwB,CAAC;AAE1C,QAAA,MAAM,aAAa;cAGL,OAAO;2CA+BnB,CAAC;AAGH,QAAA,MAAM,oBAAoB,qKAWxB,CAAC;AAGH,QAAA,MAAM,sBAAsB,uKAW1B,CAAC;AAGH,QAAA,MAAM,aAAa,8JA6BjB,CAAC;AAGH,QAAA,MAAM,WAAW,4JASf,CAAC;AAGH,QAAA,MAAM,UAAU,2JAoBd,CAAC;AAGH,QAAA,MAAM,eAAe,gKASnB,CAAC;AAGH,OAAO,EACL,MAAM,EACN,WAAW,EACX,WAAW,EACX,aAAa,EACb,aAAa,EACb,WAAW,EACX,UAAU,EACV,eAAe,EACf,oBAAoB,EACpB,sBAAsB,GACvB,CAAC"}
@@ -9,8 +9,6 @@ import { blockActivationKey, blockEvent } from './pending-guard.js';
9
9
  const Select = SelectPrimitive.Root;
10
10
  const SelectGroup = SelectPrimitive.Group;
11
11
  const SelectValue = SelectPrimitive.Value;
12
- // A pending trigger opens on nothing: it opens on a pointer press or a key, and a closed
13
- // trigger's typeahead changes the value, so both are stopped before the trigger's own handlers.
14
12
  const SelectTrigger = React.forwardRef(({ className, children, pending = false, disabled, ...props }, ref) => (_jsxs(SelectPrimitive.Trigger, { ref: ref, className: cn('flex h-10 w-full items-center justify-between rounded-md border-2 border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 aria-disabled:cursor-not-allowed aria-disabled:opacity-50 [&>span]:line-clamp-1', className), ...props, ...(pending
15
13
  ? {
16
14
  'aria-busy': true,
@@ -1 +1 @@
1
- {"version":3,"file":"select.js","sourceRoot":"","sources":["../../../src/react/components/select.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport * as SelectPrimitive from '@radix-ui/react-select';\nimport { Check, ChevronDown, ChevronUp } from 'lucide-react';\n\nimport { cn } from '../../lib/utils.js';\nimport { Spinner } from './spinner.js';\nimport { blockActivationKey, blockEvent } from './pending-guard.js';\n\nconst Select = SelectPrimitive.Root;\nconst SelectGroup = SelectPrimitive.Group;\nconst SelectValue = SelectPrimitive.Value;\n\n// A pending trigger opens on nothing: it opens on a pointer press or a key, and a closed\n// trigger's typeahead changes the value, so both are stopped before the trigger's own handlers.\nconst SelectTrigger = React.forwardRef<\n React.ElementRef<typeof SelectPrimitive.Trigger>,\n React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> & {\n /**\n * The change this select started is in flight: the trigger ignores opening and typeahead,\n * sets `aria-disabled` and `aria-busy`, and shows the spinner in place of its chevron. It\n * is never natively disabled while pending (a `disabled` passed alongside applies once\n * pending ends), so it keeps focus (FRONTEND-203). It keeps showing\n * the COMMITTED value — the caller changes `value` only once the write lands.\n */\n pending?: boolean;\n }\n>(({ className, children, pending = false, disabled, ...props }, ref) => (\n <SelectPrimitive.Trigger\n ref={ref}\n className={cn(\n 'flex h-10 w-full items-center justify-between rounded-md border-2 border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 aria-disabled:cursor-not-allowed aria-disabled:opacity-50 [&>span]:line-clamp-1',\n className,\n )}\n {...props}\n {...(pending\n ? {\n 'aria-busy': true,\n 'aria-disabled': true,\n 'data-pending': '',\n onPointerDownCapture: blockEvent,\n onClickCapture: blockEvent,\n onKeyDownCapture: blockActivationKey,\n }\n : {})}\n disabled={(disabled && !pending) || undefined}\n >\n {children}\n {pending ? (\n <Spinner size=\"xs\" />\n ) : (\n <SelectPrimitive.Icon asChild>\n <ChevronDown className=\"h-4 w-4 opacity-50\" />\n </SelectPrimitive.Icon>\n )}\n </SelectPrimitive.Trigger>\n));\nSelectTrigger.displayName = SelectPrimitive.Trigger.displayName;\n\nconst SelectScrollUpButton = React.forwardRef<\n React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,\n React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>\n>(({ className, ...props }, ref) => (\n <SelectPrimitive.ScrollUpButton\n ref={ref}\n className={cn('flex cursor-default items-center justify-center py-1', className)}\n {...props}\n >\n <ChevronUp className=\"h-4 w-4\" />\n </SelectPrimitive.ScrollUpButton>\n));\nSelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;\n\nconst SelectScrollDownButton = React.forwardRef<\n React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,\n React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>\n>(({ className, ...props }, ref) => (\n <SelectPrimitive.ScrollDownButton\n ref={ref}\n className={cn('flex cursor-default items-center justify-center py-1', className)}\n {...props}\n >\n <ChevronDown className=\"h-4 w-4\" />\n </SelectPrimitive.ScrollDownButton>\n));\nSelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;\n\nconst SelectContent = React.forwardRef<\n React.ElementRef<typeof SelectPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>\n>(({ className, children, position = 'popper', ...props }, ref) => (\n <SelectPrimitive.Portal>\n <SelectPrimitive.Content\n ref={ref}\n className={cn(\n 'relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground elevation-popover duration-[var(--motion-fast)] ease-[var(--motion-ease)] data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',\n position === 'popper' &&\n 'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',\n className,\n )}\n position={position}\n {...props}\n >\n <SelectScrollUpButton />\n <SelectPrimitive.Viewport\n className={cn(\n 'p-1',\n position === 'popper' &&\n 'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]',\n )}\n >\n {children}\n </SelectPrimitive.Viewport>\n <SelectScrollDownButton />\n </SelectPrimitive.Content>\n </SelectPrimitive.Portal>\n));\nSelectContent.displayName = SelectPrimitive.Content.displayName;\n\nconst SelectLabel = React.forwardRef<\n React.ElementRef<typeof SelectPrimitive.Label>,\n React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>\n>(({ className, ...props }, ref) => (\n <SelectPrimitive.Label\n ref={ref}\n className={cn('py-1.5 pl-8 pr-2 text-sm font-semibold', className)}\n {...props}\n />\n));\nSelectLabel.displayName = SelectPrimitive.Label.displayName;\n\nconst SelectItem = React.forwardRef<\n React.ElementRef<typeof SelectPrimitive.Item>,\n React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>\n>(({ className, children, ...props }, ref) => (\n <SelectPrimitive.Item\n ref={ref}\n className={cn(\n 'relative flex w-full cursor-default select-none items-center rounded-xs py-1.5 pl-8 pr-2 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',\n className,\n )}\n {...props}\n >\n <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n <SelectPrimitive.ItemIndicator>\n <Check className=\"h-4 w-4\" />\n </SelectPrimitive.ItemIndicator>\n </span>\n\n <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n </SelectPrimitive.Item>\n));\nSelectItem.displayName = SelectPrimitive.Item.displayName;\n\nconst SelectSeparator = React.forwardRef<\n React.ElementRef<typeof SelectPrimitive.Separator>,\n React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n <SelectPrimitive.Separator\n ref={ref}\n className={cn('-mx-1 my-1 h-px bg-muted', className)}\n {...props}\n />\n));\nSelectSeparator.displayName = SelectPrimitive.Separator.displayName;\n\nexport {\n Select,\n SelectGroup,\n SelectValue,\n SelectTrigger,\n SelectContent,\n SelectLabel,\n SelectItem,\n SelectSeparator,\n SelectScrollUpButton,\n SelectScrollDownButton,\n};\n"]}
1
+ {"version":3,"file":"select.js","sourceRoot":"","sources":["../../../src/react/components/select.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport * as SelectPrimitive from '@radix-ui/react-select';\nimport { Check, ChevronDown, ChevronUp } from 'lucide-react';\n\nimport { cn } from '../../lib/utils.js';\nimport { Spinner } from './spinner.js';\nimport { blockActivationKey, blockEvent } from './pending-guard.js';\n\nconst Select = SelectPrimitive.Root;\nconst SelectGroup = SelectPrimitive.Group;\nconst SelectValue = SelectPrimitive.Value;\n\nconst SelectTrigger = React.forwardRef<\n React.ElementRef<typeof SelectPrimitive.Trigger>,\n React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> & {\n pending?: boolean;\n }\n>(({ className, children, pending = false, disabled, ...props }, ref) => (\n <SelectPrimitive.Trigger\n ref={ref}\n className={cn(\n 'flex h-10 w-full items-center justify-between rounded-md border-2 border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 aria-disabled:cursor-not-allowed aria-disabled:opacity-50 [&>span]:line-clamp-1',\n className,\n )}\n {...props}\n {...(pending\n ? {\n 'aria-busy': true,\n 'aria-disabled': true,\n 'data-pending': '',\n onPointerDownCapture: blockEvent,\n onClickCapture: blockEvent,\n onKeyDownCapture: blockActivationKey,\n }\n : {})}\n disabled={(disabled && !pending) || undefined}\n >\n {children}\n {pending ? (\n <Spinner size=\"xs\" />\n ) : (\n <SelectPrimitive.Icon asChild>\n <ChevronDown className=\"h-4 w-4 opacity-50\" />\n </SelectPrimitive.Icon>\n )}\n </SelectPrimitive.Trigger>\n));\nSelectTrigger.displayName = SelectPrimitive.Trigger.displayName;\n\nconst SelectScrollUpButton = React.forwardRef<\n React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,\n React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>\n>(({ className, ...props }, ref) => (\n <SelectPrimitive.ScrollUpButton\n ref={ref}\n className={cn('flex cursor-default items-center justify-center py-1', className)}\n {...props}\n >\n <ChevronUp className=\"h-4 w-4\" />\n </SelectPrimitive.ScrollUpButton>\n));\nSelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;\n\nconst SelectScrollDownButton = React.forwardRef<\n React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,\n React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>\n>(({ className, ...props }, ref) => (\n <SelectPrimitive.ScrollDownButton\n ref={ref}\n className={cn('flex cursor-default items-center justify-center py-1', className)}\n {...props}\n >\n <ChevronDown className=\"h-4 w-4\" />\n </SelectPrimitive.ScrollDownButton>\n));\nSelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;\n\nconst SelectContent = React.forwardRef<\n React.ElementRef<typeof SelectPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>\n>(({ className, children, position = 'popper', ...props }, ref) => (\n <SelectPrimitive.Portal>\n <SelectPrimitive.Content\n ref={ref}\n className={cn(\n 'relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground elevation-popover duration-[var(--motion-fast)] ease-[var(--motion-ease)] data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',\n position === 'popper' &&\n 'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',\n className,\n )}\n position={position}\n {...props}\n >\n <SelectScrollUpButton />\n <SelectPrimitive.Viewport\n className={cn(\n 'p-1',\n position === 'popper' &&\n 'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]',\n )}\n >\n {children}\n </SelectPrimitive.Viewport>\n <SelectScrollDownButton />\n </SelectPrimitive.Content>\n </SelectPrimitive.Portal>\n));\nSelectContent.displayName = SelectPrimitive.Content.displayName;\n\nconst SelectLabel = React.forwardRef<\n React.ElementRef<typeof SelectPrimitive.Label>,\n React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>\n>(({ className, ...props }, ref) => (\n <SelectPrimitive.Label\n ref={ref}\n className={cn('py-1.5 pl-8 pr-2 text-sm font-semibold', className)}\n {...props}\n />\n));\nSelectLabel.displayName = SelectPrimitive.Label.displayName;\n\nconst SelectItem = React.forwardRef<\n React.ElementRef<typeof SelectPrimitive.Item>,\n React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>\n>(({ className, children, ...props }, ref) => (\n <SelectPrimitive.Item\n ref={ref}\n className={cn(\n 'relative flex w-full cursor-default select-none items-center rounded-xs py-1.5 pl-8 pr-2 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',\n className,\n )}\n {...props}\n >\n <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n <SelectPrimitive.ItemIndicator>\n <Check className=\"h-4 w-4\" />\n </SelectPrimitive.ItemIndicator>\n </span>\n\n <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n </SelectPrimitive.Item>\n));\nSelectItem.displayName = SelectPrimitive.Item.displayName;\n\nconst SelectSeparator = React.forwardRef<\n React.ElementRef<typeof SelectPrimitive.Separator>,\n React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n <SelectPrimitive.Separator\n ref={ref}\n className={cn('-mx-1 my-1 h-px bg-muted', className)}\n {...props}\n />\n));\nSelectSeparator.displayName = SelectPrimitive.Separator.displayName;\n\nexport {\n Select,\n SelectGroup,\n SelectValue,\n SelectTrigger,\n SelectContent,\n SelectLabel,\n SelectItem,\n SelectSeparator,\n SelectScrollUpButton,\n SelectScrollDownButton,\n};\n"]}
@@ -1,29 +1,13 @@
1
1
  import * as React from 'react';
2
- /**
3
- * Standard button DOM props forwarded to the rendered element, minus the props this component owns:
4
- * the label pair, the expanded state and its change callback, and the disclosure ARIA it derives
5
- * from them.
6
- */
7
2
  type ShowMoreTogglePassthrough = Omit<React.ComponentPropsWithoutRef<'button'>, 'className' | 'onClick' | 'children' | 'aria-expanded' | 'aria-controls'>;
8
3
  export interface ShowMoreToggleProps extends ShowMoreTogglePassthrough {
9
- /** Whether the content this toggle controls is shown. Controlled. */
10
4
  expanded: boolean;
11
- /** Called with the next expanded state when the toggle is pressed. */
12
5
  onExpandedChange: (expanded: boolean) => void;
13
- /** The label while the content is hidden (e.g. "Show the rumors"). */
14
6
  openLabel: React.ReactNode;
15
- /** The label while the content is shown (e.g. "Hide the rumors"). */
16
7
  closeLabel: React.ReactNode;
17
- /** The id of the element this toggle shows and hides, for `aria-controls`. */
18
8
  controls?: string;
19
- /** Extra classes, merged with the component's own. */
20
9
  className?: string;
21
10
  }
22
- /**
23
- * The one "show more" disclosure control: an outline button carrying the current label and a
24
- * chevron that turns over when the content is shown. Every expandable section uses it, so the
25
- * control looks and behaves the same everywhere and is changed in one place.
26
- */
27
11
  export declare function ShowMoreToggle({ expanded, onExpandedChange, openLabel, closeLabel, controls, className, type, ...rest }: ShowMoreToggleProps): React.JSX.Element;
28
12
  export {};
29
13
  //# sourceMappingURL=show-more-toggle.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"show-more-toggle.d.ts","sourceRoot":"","sources":["../../../src/react/components/show-more-toggle.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B;;;;GAIG;AACH,KAAK,yBAAyB,GAAG,IAAI,CACnC,KAAK,CAAC,wBAAwB,CAAC,QAAQ,CAAC,EACxC,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,eAAe,GAAG,eAAe,CACzE,CAAC;AAEF,MAAM,WAAW,mBAAoB,SAAQ,yBAAyB;IACpE,qEAAqE;IACrE,QAAQ,EAAE,OAAO,CAAC;IAClB,sEAAsE;IACtE,gBAAgB,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC;IAC9C,sEAAsE;IACtE,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,qEAAqE;IACrE,UAAU,EAAE,KAAK,CAAC,SAAS,CAAC;IAC5B,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,EAC7B,QAAQ,EACR,gBAAgB,EAChB,SAAS,EACT,UAAU,EACV,QAAQ,EACR,SAAS,EACT,IAAe,EACf,GAAG,IAAI,EACR,EAAE,mBAAmB,qBAuBrB"}
1
+ {"version":3,"file":"show-more-toggle.d.ts","sourceRoot":"","sources":["../../../src/react/components/show-more-toggle.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B,KAAK,yBAAyB,GAAG,IAAI,CACnC,KAAK,CAAC,wBAAwB,CAAC,QAAQ,CAAC,EACxC,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,eAAe,GAAG,eAAe,CACzE,CAAC;AAEF,MAAM,WAAW,mBAAoB,SAAQ,yBAAyB;IACpE,QAAQ,EAAE,OAAO,CAAC;IAClB,gBAAgB,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC;IAC9C,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,UAAU,EAAE,KAAK,CAAC,SAAS,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,cAAc,CAAC,EAC7B,QAAQ,EACR,gBAAgB,EAChB,SAAS,EACT,UAAU,EACV,QAAQ,EACR,SAAS,EACT,IAAe,EACf,GAAG,IAAI,EACR,EAAE,mBAAmB,qBAuBrB"}
@@ -3,11 +3,6 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { ChevronDown } from 'lucide-react';
4
4
  import { Button } from './button.js';
5
5
  import { cn } from '../../lib/utils.js';
6
- /**
7
- * The one "show more" disclosure control: an outline button carrying the current label and a
8
- * chevron that turns over when the content is shown. Every expandable section uses it, so the
9
- * control looks and behaves the same everywhere and is changed in one place.
10
- */
11
6
  export function ShowMoreToggle({ expanded, onExpandedChange, openLabel, closeLabel, controls, className, type = 'button', ...rest }) {
12
7
  return (_jsxs(Button, { ...rest, type: type, variant: "outline", size: "sm", className: className, "aria-expanded": expanded, "aria-controls": controls, onClick: () => onExpandedChange(!expanded), children: [expanded ? closeLabel : openLabel, _jsx(ChevronDown, { "aria-hidden": "true", "data-expanded": expanded ? 'true' : 'false', className: cn('ml-1 h-4 w-4 transition-transform motion-reduce:transition-none', expanded && 'rotate-180') })] }));
13
8
  }
@@ -1 +1 @@
1
- {"version":3,"file":"show-more-toggle.js","sourceRoot":"","sources":["../../../src/react/components/show-more-toggle.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAGb,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,EAAE,EAAE,MAAM,oBAAoB,CAAC;AA2BxC;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,EAC7B,QAAQ,EACR,gBAAgB,EAChB,SAAS,EACT,UAAU,EACV,QAAQ,EACR,SAAS,EACT,IAAI,GAAG,QAAQ,EACf,GAAG,IAAI,EACa;IACpB,OAAO,CACL,MAAC,MAAM,OACD,IAAI,EACR,IAAI,EAAE,IAAI,EACV,OAAO,EAAC,SAAS,EACjB,IAAI,EAAC,IAAI,EACT,SAAS,EAAE,SAAS,mBACL,QAAQ,mBACR,QAAQ,EACvB,OAAO,EAAE,GAAG,EAAE,CAAC,gBAAgB,CAAC,CAAC,QAAQ,CAAC,aAEzC,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,EAClC,KAAC,WAAW,mBACE,MAAM,mBACH,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,EAC1C,SAAS,EAAE,EAAE,CACX,iEAAiE,EACjE,QAAQ,IAAI,YAAY,CACzB,GACD,IACK,CACV,CAAC;AACJ,CAAC","sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { ChevronDown } from 'lucide-react';\nimport { Button } from './button.js';\nimport { cn } from '../../lib/utils.js';\n\n/**\n * Standard button DOM props forwarded to the rendered element, minus the props this component owns:\n * the label pair, the expanded state and its change callback, and the disclosure ARIA it derives\n * from them.\n */\ntype ShowMoreTogglePassthrough = Omit<\n React.ComponentPropsWithoutRef<'button'>,\n 'className' | 'onClick' | 'children' | 'aria-expanded' | 'aria-controls'\n>;\n\nexport interface ShowMoreToggleProps extends ShowMoreTogglePassthrough {\n /** Whether the content this toggle controls is shown. Controlled. */\n expanded: boolean;\n /** Called with the next expanded state when the toggle is pressed. */\n onExpandedChange: (expanded: boolean) => void;\n /** The label while the content is hidden (e.g. \"Show the rumors\"). */\n openLabel: React.ReactNode;\n /** The label while the content is shown (e.g. \"Hide the rumors\"). */\n closeLabel: React.ReactNode;\n /** The id of the element this toggle shows and hides, for `aria-controls`. */\n controls?: string;\n /** Extra classes, merged with the component's own. */\n className?: string;\n}\n\n/**\n * The one \"show more\" disclosure control: an outline button carrying the current label and a\n * chevron that turns over when the content is shown. Every expandable section uses it, so the\n * control looks and behaves the same everywhere and is changed in one place.\n */\nexport function ShowMoreToggle({\n expanded,\n onExpandedChange,\n openLabel,\n closeLabel,\n controls,\n className,\n type = 'button',\n ...rest\n}: ShowMoreToggleProps) {\n return (\n <Button\n {...rest}\n type={type}\n variant=\"outline\"\n size=\"sm\"\n className={className}\n aria-expanded={expanded}\n aria-controls={controls}\n onClick={() => onExpandedChange(!expanded)}\n >\n {expanded ? closeLabel : openLabel}\n <ChevronDown\n aria-hidden=\"true\"\n data-expanded={expanded ? 'true' : 'false'}\n className={cn(\n 'ml-1 h-4 w-4 transition-transform motion-reduce:transition-none',\n expanded && 'rotate-180',\n )}\n />\n </Button>\n );\n}\n"]}
1
+ {"version":3,"file":"show-more-toggle.js","sourceRoot":"","sources":["../../../src/react/components/show-more-toggle.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAGb,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,EAAE,EAAE,MAAM,oBAAoB,CAAC;AAgBxC,MAAM,UAAU,cAAc,CAAC,EAC7B,QAAQ,EACR,gBAAgB,EAChB,SAAS,EACT,UAAU,EACV,QAAQ,EACR,SAAS,EACT,IAAI,GAAG,QAAQ,EACf,GAAG,IAAI,EACa;IACpB,OAAO,CACL,MAAC,MAAM,OACD,IAAI,EACR,IAAI,EAAE,IAAI,EACV,OAAO,EAAC,SAAS,EACjB,IAAI,EAAC,IAAI,EACT,SAAS,EAAE,SAAS,mBACL,QAAQ,mBACR,QAAQ,EACvB,OAAO,EAAE,GAAG,EAAE,CAAC,gBAAgB,CAAC,CAAC,QAAQ,CAAC,aAEzC,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,EAClC,KAAC,WAAW,mBACE,MAAM,mBACH,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,EAC1C,SAAS,EAAE,EAAE,CACX,iEAAiE,EACjE,QAAQ,IAAI,YAAY,CACzB,GACD,IACK,CACV,CAAC;AACJ,CAAC","sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { ChevronDown } from 'lucide-react';\nimport { Button } from './button.js';\nimport { cn } from '../../lib/utils.js';\n\ntype ShowMoreTogglePassthrough = Omit<\n React.ComponentPropsWithoutRef<'button'>,\n 'className' | 'onClick' | 'children' | 'aria-expanded' | 'aria-controls'\n>;\n\nexport interface ShowMoreToggleProps extends ShowMoreTogglePassthrough {\n expanded: boolean;\n onExpandedChange: (expanded: boolean) => void;\n openLabel: React.ReactNode;\n closeLabel: React.ReactNode;\n controls?: string;\n className?: string;\n}\n\nexport function ShowMoreToggle({\n expanded,\n onExpandedChange,\n openLabel,\n closeLabel,\n controls,\n className,\n type = 'button',\n ...rest\n}: ShowMoreToggleProps) {\n return (\n <Button\n {...rest}\n type={type}\n variant=\"outline\"\n size=\"sm\"\n className={className}\n aria-expanded={expanded}\n aria-controls={controls}\n onClick={() => onExpandedChange(!expanded)}\n >\n {expanded ? closeLabel : openLabel}\n <ChevronDown\n aria-hidden=\"true\"\n data-expanded={expanded ? 'true' : 'false'}\n className={cn(\n 'ml-1 h-4 w-4 transition-transform motion-reduce:transition-none',\n expanded && 'rotate-180',\n )}\n />\n </Button>\n );\n}\n"]}
@@ -1,23 +1,9 @@
1
1
  export type SpinnerSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
2
2
  export interface SpinnerProps {
3
- /** Maps onto the theme-core `spinner-*` classes. Defaults to `xs` (the in-control size). */
4
3
  size?: SpinnerSize;
5
- /**
6
- * Screen-reader text. When set, the spinner becomes a `status` live region that
7
- * announces it. Omit it for a spinner inside a control that already carries its
8
- * own busy state (`aria-busy`) — the control speaks for it.
9
- */
10
4
  label?: string;
11
5
  className?: string;
12
6
  }
13
- /**
14
- * The ONE loading spinner. Every in-progress indicator renders through this
15
- * component (or through a control's `pending` prop, which renders it), so the
16
- * spinner's look, size scale, and color rule have one change point. The
17
- * theme-core `spinner-*` classes own size, animation, and color — including the
18
- * `button .spinner-*` rule that makes an in-button spinner inherit the button's
19
- * text color so it is never primary-on-primary.
20
- */
21
7
  declare function Spinner({ size, label, className }: SpinnerProps): import("react").JSX.Element;
22
8
  export { Spinner };
23
9
  //# sourceMappingURL=spinner.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"spinner.d.ts","sourceRoot":"","sources":["../../../src/react/components/spinner.tsx"],"names":[],"mappings":"AAMA,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE3D,MAAM,WAAW,YAAY;IAC3B,4FAA4F;IAC5F,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAUD;;;;;;;GAOG;AACH,iBAAS,OAAO,CAAC,EAAE,IAAW,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,YAAY,+BAU/D;AAED,OAAO,EAAE,OAAO,EAAE,CAAC"}
1
+ {"version":3,"file":"spinner.d.ts","sourceRoot":"","sources":["../../../src/react/components/spinner.tsx"],"names":[],"mappings":"AAMA,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE3D,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAUD,iBAAS,OAAO,CAAC,EAAE,IAAW,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,YAAY,+BAU/D;AAED,OAAO,EAAE,OAAO,EAAE,CAAC"}