@ahrowe/ui 0.29.0 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/dist/esm/common/accordion/accordion.mjs +1 -1
  2. package/dist/esm/common/accordion/accordion.mjs.map +1 -1
  3. package/dist/esm/common/accordion/accordion.module.mjs.map +1 -1
  4. package/dist/esm/common/accordion/accordion.plain.module.mjs.map +1 -1
  5. package/dist/esm/common/accordion/accordion.primary.module.mjs.map +1 -1
  6. package/dist/esm/common/alert/alert.module.mjs.map +1 -1
  7. package/dist/esm/common/badge/badge.module.mjs.map +1 -1
  8. package/dist/esm/common/bodyEnd/bodyEnd.mjs +1 -1
  9. package/dist/esm/common/bodyEnd/bodyEnd.mjs.map +1 -1
  10. package/dist/esm/common/button/button.module.mjs +1 -1
  11. package/dist/esm/common/button/button.module.mjs.map +1 -1
  12. package/dist/esm/common/card/Card.mjs +1 -1
  13. package/dist/esm/common/card/Card.mjs.map +1 -1
  14. package/dist/esm/common/card/card.module.mjs.map +1 -1
  15. package/dist/esm/common/checkbox/checkbox.mjs +1 -1
  16. package/dist/esm/common/checkbox/checkbox.mjs.map +1 -1
  17. package/dist/esm/common/checkbox/checkbox.module.mjs +1 -1
  18. package/dist/esm/common/checkbox/checkbox.module.mjs.map +1 -1
  19. package/dist/esm/common/chip/chip.mjs +1 -1
  20. package/dist/esm/common/chip/chip.mjs.map +1 -1
  21. package/dist/esm/common/chip/chip.module.mjs.map +1 -1
  22. package/dist/esm/common/colorPicker/colorPicker.mjs +1 -1
  23. package/dist/esm/common/colorPicker/colorPicker.mjs.map +1 -1
  24. package/dist/esm/common/colorPicker/colorPicker.module.mjs.map +1 -1
  25. package/dist/esm/common/colorPicker/toHex.mjs +2 -0
  26. package/dist/esm/common/colorPicker/toHex.mjs.map +1 -0
  27. package/dist/esm/common/datePicker/datePicker.mjs +1 -1
  28. package/dist/esm/common/datePicker/datePicker.mjs.map +1 -1
  29. package/dist/esm/common/dropZone/dropZone.mjs +1 -1
  30. package/dist/esm/common/dropZone/dropZone.mjs.map +1 -1
  31. package/dist/esm/common/fab/components/fabBase/fabBase.mjs +1 -1
  32. package/dist/esm/common/fab/components/fabBase/fabBase.mjs.map +1 -1
  33. package/dist/esm/common/fab/components/fabBase/fabBase.module.mjs.map +1 -1
  34. package/dist/esm/common/fab/fab.module.mjs.map +1 -1
  35. package/dist/esm/common/flip/flip.mjs +1 -1
  36. package/dist/esm/common/flip/flip.mjs.map +1 -1
  37. package/dist/esm/common/iconPicker/iconPicker.module.mjs.map +1 -1
  38. package/dist/esm/common/input/input.mjs +1 -1
  39. package/dist/esm/common/input/input.mjs.map +1 -1
  40. package/dist/esm/common/input/input.module.mjs +1 -1
  41. package/dist/esm/common/input/input.module.mjs.map +1 -1
  42. package/dist/esm/common/inputDropdown/inputDropdown.mjs +1 -1
  43. package/dist/esm/common/inputDropdown/inputDropdown.mjs.map +1 -1
  44. package/dist/esm/common/interactableDiv/interactableDiv.mjs +1 -1
  45. package/dist/esm/common/interactableDiv/interactableDiv.mjs.map +1 -1
  46. package/dist/esm/common/kanbanBoard/kanbanBoard.module.mjs.map +1 -1
  47. package/dist/esm/common/optionPicker/optionPicker.module.mjs.map +1 -1
  48. package/dist/esm/common/otpInput/otpInput.module.mjs.map +1 -1
  49. package/dist/esm/common/pagination/pagination.module.mjs.map +1 -1
  50. package/dist/esm/common/planCanvas/planCanvas.module.mjs +1 -1
  51. package/dist/esm/common/planCanvas/planCanvas.module.mjs.map +1 -1
  52. package/dist/esm/common/progressBar/progressBar.module.mjs +1 -1
  53. package/dist/esm/common/progressBar/progressBar.module.mjs.map +1 -1
  54. package/dist/esm/common/radioGroup/radioGroup.mjs +1 -1
  55. package/dist/esm/common/radioGroup/radioGroup.mjs.map +1 -1
  56. package/dist/esm/common/radioGroup/radioGroup.module.mjs.map +1 -1
  57. package/dist/esm/common/rating/rating.module.mjs.map +1 -1
  58. package/dist/esm/common/roomDrawer/roomDrawer.mjs +1 -1
  59. package/dist/esm/common/roomDrawer/roomDrawer.mjs.map +1 -1
  60. package/dist/esm/common/searchInput/searchInput.mjs +1 -1
  61. package/dist/esm/common/searchInput/searchInput.mjs.map +1 -1
  62. package/dist/esm/common/slider/slider.mjs +1 -1
  63. package/dist/esm/common/slider/slider.mjs.map +1 -1
  64. package/dist/esm/common/slider/slider.module.mjs.map +1 -1
  65. package/dist/esm/common/switch/switch.module.mjs.map +1 -1
  66. package/dist/esm/common/tabHeader/tabHeader.module.mjs.map +1 -1
  67. package/dist/esm/common/themeProvider/theme.module.mjs.map +1 -1
  68. package/dist/esm/common/themeProvider/themeProvider.mjs +1 -1
  69. package/dist/esm/common/themeProvider/themeProvider.mjs.map +1 -1
  70. package/dist/esm/common/themeProvider/themeVariablesContext.mjs +2 -0
  71. package/dist/esm/common/themeProvider/themeVariablesContext.mjs.map +1 -0
  72. package/dist/esm/common/timeInput/timeInput.mjs +1 -1
  73. package/dist/esm/common/timeInput/timeInput.mjs.map +1 -1
  74. package/dist/esm/common/timeline/timeline.mjs +1 -1
  75. package/dist/esm/common/timeline/timeline.mjs.map +1 -1
  76. package/dist/esm/common/timeline/timeline.module.mjs +1 -1
  77. package/dist/esm/common/timeline/timeline.module.mjs.map +1 -1
  78. package/dist/esm/common/timer/timer.mjs +1 -1
  79. package/dist/esm/common/timer/timer.mjs.map +1 -1
  80. package/dist/esm/common/timer/timer.module.mjs.map +1 -1
  81. package/dist/esm/common/toast/toast.module.mjs.map +1 -1
  82. package/dist/esm/common/tooltip/tooltip.module.mjs.map +1 -1
  83. package/dist/esm/common/utils/activationKey.mjs +2 -0
  84. package/dist/esm/common/utils/activationKey.mjs.map +1 -0
  85. package/dist/index.cjs +3 -3
  86. package/dist/index.cjs.map +1 -1
  87. package/dist/style.css +1 -1
  88. package/dist/types/common/colorPicker/toHex.d.ts +11 -0
  89. package/dist/types/common/themeProvider/theme.types.d.ts +105 -3
  90. package/dist/types/common/themeProvider/themeVariablesContext.d.ts +11 -0
  91. package/dist/types/common/utils/activationKey.d.ts +9 -0
  92. package/docs/Accordion.md +4 -0
  93. package/docs/Alert.md +4 -0
  94. package/docs/BodyEnd.md +3 -0
  95. package/docs/Button.md +6 -0
  96. package/docs/Card.md +6 -0
  97. package/docs/Checkbox.md +8 -0
  98. package/docs/ColorPicker.md +11 -1
  99. package/docs/Fab.md +16 -0
  100. package/docs/Flip.md +1 -1
  101. package/docs/IconPicker.md +8 -0
  102. package/docs/Input.md +12 -0
  103. package/docs/OtpInput.md +4 -0
  104. package/docs/RadioGroup.md +6 -0
  105. package/docs/Rating.md +5 -0
  106. package/docs/RoomDrawer.md +13 -5
  107. package/docs/RoomViewer.md +4 -0
  108. package/docs/Slider.md +6 -2
  109. package/docs/ThemeProvider.md +89 -1
  110. package/docs/Timer.md +7 -1
  111. package/docs/Toast.md +6 -0
  112. package/docs/Tooltip.md +15 -0
  113. package/package.json +2 -1
@@ -1 +1 @@
1
- {"version":3,"file":"timer.mjs","names":[],"sources":["../../../../package/common/timer/timer.tsx"],"sourcesContent":["import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';\nimport cx from 'classnames';\nimport { faPlus, faMinus, faPause, faPlay, faXmark } from '@fortawesome/free-solid-svg-icons';\n\nimport ActionIcon from '../actionIcon';\nimport Button from '../button';\nimport { ButtonSize } from '../button/button.types';\nimport { useComponentDefaults } from '../configProvider';\nimport { DEFAULT_CHIME_DATA_URI } from './timer.chime';\nimport type { TimerHandle, TimerProps, TimerStatus } from './timer.types';\nimport styles from './timer.module.pcss';\n\nconst TICK_MS = 200;\nconst RIPPLE_SPAWN_INTERVAL_MS = 1000;\n\nfunction prefersReducedMotion(): boolean {\n return typeof window !== 'undefined' && window.matchMedia?.('(prefers-reduced-motion: reduce)').matches === true;\n}\n\nfunction formatTimeDefault(remainingMs: number) {\n const totalSeconds = Math.ceil(remainingMs / 1000);\n const hours = Math.floor(totalSeconds / 3600);\n const minutes = Math.floor((totalSeconds % 3600) / 60);\n const seconds = totalSeconds % 60;\n const mm = String(minutes).padStart(2, '0');\n const ss = String(seconds).padStart(2, '0');\n return hours > 0 ? `${hours}:${mm}:${ss}` : `${mm}:${ss}`;\n}\n\nconst Timer = forwardRef<TimerHandle, TimerProps>(function Timer(props, ref) {\n const {\n durationSeconds,\n autoStart = true,\n resetOnDurationChange = true,\n\n onTick,\n onComplete,\n onStart,\n onPause,\n onResume,\n onCancel,\n onAddTime,\n onRemoveTime,\n\n showControls = true,\n hideAddButton = false,\n hideRemoveButton = false,\n hidePauseButton = false,\n hideCancelButton = false,\n adjustSeconds = 30,\n maxSeconds,\n\n label,\n formatTime = formatTimeDefault,\n size = '160px',\n strokeWidth = 10,\n counterClockwise = false,\n\n urgentThreshold = 0.1,\n urgentMinSeconds = 3,\n pulseOnUrgent = true,\n showRunningRipples = false,\n\n playSoundOnComplete = false,\n soundUrl,\n soundVolume = 0.5,\n notifyOnComplete = false,\n notificationTitle,\n notificationBody,\n\n className,\n style,\n classNames,\n styles: slotStyles,\n ...rest\n } = useComponentDefaults('Timer', props);\n\n const initialMs = durationSeconds * 1000;\n const [status, setStatusState] = useState<TimerStatus>(autoStart ? 'running' : 'idle');\n const [remainingMs, setRemainingMsState] = useState(initialMs);\n const [epoch, setEpoch] = useState(0);\n const [ripples, setRipples] = useState<number[]>([]);\n\n const remainingRef = useRef(remainingMs);\n const statusRef = useRef(status);\n const totalMsRef = useRef(initialMs);\n const completedFiredRef = useRef(false);\n const notifyPermissionRequestedRef = useRef(false);\n const mountedRef = useRef(false);\n const rippleIdRef = useRef(0);\n\n function setRemaining(next: number) {\n remainingRef.current = next;\n setRemainingMsState(next);\n }\n\n function setStatus(next: TimerStatus) {\n statusRef.current = next;\n setStatusState(next);\n }\n\n function bumpEpoch() {\n setEpoch((e) => e + 1);\n }\n\n // Ticking effect. Re-derives the remaining time from Date.now() every tick (rather than\n // accumulating setInterval's own drift), so it stays accurate over long countdowns. Any\n // external mutation of remainingRef (addTime/removeTime while already running, or a resume)\n // must bump `epoch` so this effect re-arms and re-captures a fresh start/startRemaining —\n // without that, a stale `startRemaining` captured before the mutation would silently\n // overwrite it on the next tick.\n useEffect(() => {\n if (status !== 'running') return undefined;\n const start = Date.now();\n const startRemaining = remainingRef.current;\n const id = setInterval(() => {\n const next = Math.max(0, startRemaining - (Date.now() - start));\n setRemaining(next);\n onTick?.(next);\n if (next <= 0) setStatus('completed');\n }, TICK_MS);\n return () => clearInterval(id);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [status, epoch]);\n\n // Fires onComplete exactly once per completion, plus the opt-in sound/notification.\n useEffect(() => {\n if (status === 'completed' && !completedFiredRef.current) {\n completedFiredRef.current = true;\n onComplete?.();\n if (playSoundOnComplete) {\n try {\n const audio = new Audio(soundUrl || DEFAULT_CHIME_DATA_URI);\n audio.volume = soundVolume;\n // play() can reject (autoplay policy) or, in some environments, return undefined\n // instead of a Promise — never let a blocked/unsupported playback surface as an error.\n audio.play()?.catch(() => {});\n } catch {\n // Audio unavailable/unsupported — fail silently.\n }\n }\n if (notifyOnComplete && typeof Notification !== 'undefined' && Notification.permission === 'granted') {\n try {\n new Notification(notificationTitle ?? 'Timer done', {\n body: notificationBody ?? (typeof label === 'string' ? label : undefined),\n });\n } catch {\n // Some browsers (e.g. certain mobile Safari versions) throw constructing\n // Notification outside specific contexts — fail silently, as documented.\n }\n }\n }\n if (status !== 'completed') completedFiredRef.current = false;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [status]);\n\n // durationSeconds prop change: reset (and optionally restart) at the new duration. Skips\n // the initial mount, since autoStart/initial state already cover that.\n useEffect(() => {\n if (!mountedRef.current) {\n mountedRef.current = true;\n return;\n }\n const nextMs = durationSeconds * 1000;\n totalMsRef.current = nextMs;\n setRemaining(nextMs);\n completedFiredRef.current = false;\n bumpEpoch();\n if (resetOnDurationChange) {\n setStatus(autoStart ? 'running' : 'idle');\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [durationSeconds]);\n\n // Running-ripple spawner: spawns a new ripple roughly every RIPPLE_SPAWN_INTERVAL_MS while\n // running, each animating once (via CSS, `forwards`) and removing itself on `onAnimationEnd`.\n // Pausing (or otherwise leaving `running`) only stops new spawns here — it doesn't touch\n // `ripples` — so whichever ripples are already mid-flight keep animating to completion\n // instead of vanishing mid-cycle.\n useEffect(() => {\n if (!showRunningRipples || status !== 'running' || prefersReducedMotion()) return undefined;\n function spawnRipple() {\n rippleIdRef.current += 1;\n setRipples((prev) => [...prev, rippleIdRef.current]);\n }\n spawnRipple();\n const id = setInterval(spawnRipple, RIPPLE_SPAWN_INTERVAL_MS);\n return () => clearInterval(id);\n }, [showRunningRipples, status]);\n\n function handleRippleAnimationEnd(id: number) {\n setRipples((prev) => prev.filter((existingId) => existingId !== id));\n }\n\n function requestNotificationPermissionIfNeeded() {\n if (!notifyOnComplete || notifyPermissionRequestedRef.current) return;\n if (typeof Notification === 'undefined' || Notification.permission !== 'default') return;\n notifyPermissionRequestedRef.current = true;\n Notification.requestPermission().catch(() => {});\n }\n\n function start() {\n requestNotificationPermissionIfNeeded();\n if (statusRef.current === 'completed' || statusRef.current === 'cancelled') {\n setRemaining(totalMsRef.current);\n completedFiredRef.current = false;\n }\n bumpEpoch();\n setStatus('running');\n onStart?.();\n }\n\n function pause() {\n if (statusRef.current !== 'running') return;\n setStatus('paused');\n onPause?.();\n }\n\n function resume() {\n if (statusRef.current !== 'paused') return;\n bumpEpoch();\n setStatus('running');\n onResume?.();\n }\n\n function addTime(seconds: number) {\n const capped = maxSeconds != null ? maxSeconds * 1000 : Infinity;\n const next = Math.min(remainingRef.current + seconds * 1000, capped);\n setRemaining(next);\n bumpEpoch();\n onAddTime?.(seconds, Math.round(next / 1000));\n if (statusRef.current === 'completed' && next > 0) {\n completedFiredRef.current = false;\n setStatus('running');\n }\n }\n\n function removeTime(seconds: number) {\n const next = Math.max(0, remainingRef.current - seconds * 1000);\n setRemaining(next);\n bumpEpoch();\n onRemoveTime?.(seconds, Math.round(next / 1000));\n if (next <= 0 && statusRef.current === 'running') setStatus('completed');\n }\n\n function cancel() {\n setRemaining(totalMsRef.current);\n setStatus('cancelled');\n onCancel?.();\n }\n\n function reset() {\n setRemaining(totalMsRef.current);\n completedFiredRef.current = false;\n setStatus('idle');\n }\n\n useImperativeHandle(ref, () => ({\n start,\n pause,\n resume,\n addTime,\n removeTime,\n cancel,\n reset,\n getStatus: () => statusRef.current,\n getRemainingMs: () => remainingRef.current,\n }));\n\n const radius = 50 - strokeWidth / 2;\n const circumference = useMemo(() => 2 * Math.PI * radius, [radius]);\n const clampedFraction = totalMsRef.current > 0 ? Math.min(1, remainingMs / totalMsRef.current) : 0;\n const dashOffset = circumference * (1 - clampedFraction);\n const urgentMs = Math.max(totalMsRef.current * urgentThreshold, urgentMinSeconds * 1000);\n const isUrgent = status === 'running' && remainingMs <= urgentMs;\n const isCritical = isUrgent && remainingMs <= Math.min(5000, urgentMs);\n const isPaused = status === 'paused';\n\n // The pause button doubles as a play/restart button whenever there's nothing to pause —\n // idle (autoStart={false}), completed, or cancelled — rather than hiding itself, so there's\n // always a way to (re)start the countdown from the built-in controls alone.\n const showAddButton = showControls && !hideAddButton;\n const showRemoveButton = showControls && !hideRemoveButton;\n const showPauseButton = showControls && !hidePauseButton;\n const pauseButtonIcon = status === 'running' ? faPause : faPlay;\n const pauseButtonTitle =\n status === 'running' ? 'Pause' : isPaused ? 'Resume' : status === 'idle' ? 'Start' : 'Restart';\n const handlePauseButtonClick = status === 'running' ? pause : isPaused ? resume : start;\n const showCancelButton = showControls && !hideCancelButton;\n\n return (\n <div\n {...rest}\n className={cx(\n styles.timer,\n pulseOnUrgent && isUrgent && styles.timerPulsing,\n isCritical && styles.timerCritical,\n className,\n classNames?.root,\n )}\n style={\n {\n '--timer-size': size,\n '--timer-stroke-width': `${strokeWidth}px`,\n ...style,\n ...slotStyles?.root,\n } as React.CSSProperties\n }\n >\n <div className={cx(styles.timerRingWrapper, classNames?.ringWrapper)} style={slotStyles?.ringWrapper}>\n {ripples.length > 0 && (\n <div className={cx(styles.timerRipples, classNames?.ripples)} style={slotStyles?.ripples}>\n {ripples.map((id) => (\n <span\n key={id}\n className={cx(styles.timerRipple, classNames?.ripple)}\n style={slotStyles?.ripple}\n onAnimationEnd={() => handleRippleAnimationEnd(id)}\n />\n ))}\n </div>\n )}\n <svg className={cx(styles.timerRingSvg, classNames?.ringSvg)} viewBox='0 0 100 100' style={slotStyles?.ringSvg}>\n <circle\n className={cx(styles.timerRingTrack, classNames?.ringTrack)}\n cx='50'\n cy='50'\n r={radius}\n style={slotStyles?.ringTrack}\n />\n <circle\n className={cx(\n styles.timerRingProgress,\n isUrgent && styles.timerRingProgressUrgent,\n classNames?.ringProgress,\n )}\n cx='50'\n cy='50'\n r={radius}\n strokeDasharray={circumference}\n strokeDashoffset={dashOffset}\n style={{\n transition: isPaused ? 'none' : undefined,\n transform: counterClockwise ? 'rotate(-90deg)' : undefined,\n ...slotStyles?.ringProgress,\n }}\n />\n </svg>\n <div className={cx(styles.timerDisplay, classNames?.display)} style={slotStyles?.display}>\n <span className={cx(styles.timerTime, classNames?.time)} style={slotStyles?.time}>\n {formatTime(remainingMs)}\n </span>\n {label && (\n <span className={cx(styles.timerLabel, classNames?.label)} style={slotStyles?.label}>\n {label}\n </span>\n )}\n </div>\n </div>\n\n {showControls && (\n <div className={cx(styles.timerControls, classNames?.controls)} style={slotStyles?.controls}>\n <div className={styles.timerControlsRow}>\n {showRemoveButton && (\n <ActionIcon\n icon={faMinus}\n title={`Remove ${adjustSeconds}s`}\n onClick={() => removeTime(adjustSeconds)}\n className={cx(styles.timerAdjustButton, classNames?.removeButton)}\n style={slotStyles?.removeButton}\n />\n )}\n {showPauseButton && (\n <Button\n size={ButtonSize.Small}\n icon={pauseButtonIcon}\n title={pauseButtonTitle}\n onClick={handlePauseButtonClick}\n className={classNames?.pauseButton}\n style={slotStyles?.pauseButton}\n />\n )}\n {showAddButton && (\n <ActionIcon\n icon={faPlus}\n title={`Add ${adjustSeconds}s`}\n onClick={() => addTime(adjustSeconds)}\n className={cx(styles.timerAdjustButton, classNames?.addButton)}\n style={slotStyles?.addButton}\n />\n )}\n </div>\n {showCancelButton && (\n <ActionIcon\n icon={faXmark}\n title='Cancel timer'\n onClick={cancel}\n className={cx(styles.timerCancelButton, classNames?.cancelButton)}\n style={slotStyles?.cancelButton}\n />\n )}\n </div>\n )}\n </div>\n );\n});\n\nTimer.displayName = 'Timer';\n\nexport default Timer;\n"],"mappings":"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"}
1
+ {"version":3,"file":"timer.mjs","names":[],"sources":["../../../../package/common/timer/timer.tsx"],"sourcesContent":["import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';\nimport cx from 'classnames';\nimport { faPlus, faMinus, faPause, faPlay, faXmark } from '@fortawesome/free-solid-svg-icons';\n\nimport ActionIcon from '../actionIcon';\nimport Button from '../button';\nimport { ButtonSize } from '../button/button.types';\nimport { useComponentDefaults } from '../configProvider';\nimport { DEFAULT_CHIME_DATA_URI } from './timer.chime';\nimport type { TimerHandle, TimerProps, TimerStatus } from './timer.types';\nimport styles from './timer.module.pcss';\n\nconst TICK_MS = 200;\nconst RIPPLE_SPAWN_INTERVAL_MS = 1000;\n\nfunction prefersReducedMotion(): boolean {\n return typeof window !== 'undefined' && window.matchMedia?.('(prefers-reduced-motion: reduce)').matches === true;\n}\n\nfunction formatTimeDefault(remainingMs: number) {\n const totalSeconds = Math.ceil(remainingMs / 1000);\n const hours = Math.floor(totalSeconds / 3600);\n const minutes = Math.floor((totalSeconds % 3600) / 60);\n const seconds = totalSeconds % 60;\n const mm = String(minutes).padStart(2, '0');\n const ss = String(seconds).padStart(2, '0');\n return hours > 0 ? `${hours}:${mm}:${ss}` : `${mm}:${ss}`;\n}\n\nconst Timer = forwardRef<TimerHandle, TimerProps>(function Timer(props, ref) {\n const {\n durationSeconds,\n autoStart = true,\n resetOnDurationChange = true,\n\n onTick,\n onComplete,\n onStart,\n onPause,\n onResume,\n onCancel,\n onAddTime,\n onRemoveTime,\n\n showControls = true,\n hideAddButton = false,\n hideRemoveButton = false,\n hidePauseButton = false,\n hideCancelButton = false,\n adjustSeconds = 30,\n maxSeconds,\n\n label,\n formatTime = formatTimeDefault,\n size,\n strokeWidth = 10,\n counterClockwise = false,\n\n urgentThreshold = 0.1,\n urgentMinSeconds = 3,\n pulseOnUrgent = true,\n showRunningRipples = false,\n\n playSoundOnComplete = false,\n soundUrl,\n soundVolume = 0.5,\n notifyOnComplete = false,\n notificationTitle,\n notificationBody,\n\n className,\n style,\n classNames,\n styles: slotStyles,\n ...rest\n } = useComponentDefaults('Timer', props);\n\n const initialMs = durationSeconds * 1000;\n const [status, setStatusState] = useState<TimerStatus>(autoStart ? 'running' : 'idle');\n const [remainingMs, setRemainingMsState] = useState(initialMs);\n const [epoch, setEpoch] = useState(0);\n const [ripples, setRipples] = useState<number[]>([]);\n\n const remainingRef = useRef(remainingMs);\n const statusRef = useRef(status);\n const totalMsRef = useRef(initialMs);\n const completedFiredRef = useRef(false);\n const notifyPermissionRequestedRef = useRef(false);\n const mountedRef = useRef(false);\n const rippleIdRef = useRef(0);\n\n function setRemaining(next: number) {\n remainingRef.current = next;\n setRemainingMsState(next);\n }\n\n function setStatus(next: TimerStatus) {\n statusRef.current = next;\n setStatusState(next);\n }\n\n function bumpEpoch() {\n setEpoch((e) => e + 1);\n }\n\n // Ticking effect. Re-derives the remaining time from Date.now() every tick (rather than\n // accumulating setInterval's own drift), so it stays accurate over long countdowns. Any\n // external mutation of remainingRef (addTime/removeTime while already running, or a resume)\n // must bump `epoch` so this effect re-arms and re-captures a fresh start/startRemaining —\n // without that, a stale `startRemaining` captured before the mutation would silently\n // overwrite it on the next tick.\n useEffect(() => {\n if (status !== 'running') return undefined;\n const start = Date.now();\n const startRemaining = remainingRef.current;\n const id = setInterval(() => {\n const next = Math.max(0, startRemaining - (Date.now() - start));\n setRemaining(next);\n onTick?.(next);\n if (next <= 0) setStatus('completed');\n }, TICK_MS);\n return () => clearInterval(id);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [status, epoch]);\n\n // Fires onComplete exactly once per completion, plus the opt-in sound/notification.\n useEffect(() => {\n if (status === 'completed' && !completedFiredRef.current) {\n completedFiredRef.current = true;\n onComplete?.();\n if (playSoundOnComplete) {\n try {\n const audio = new Audio(soundUrl || DEFAULT_CHIME_DATA_URI);\n audio.volume = soundVolume;\n // play() can reject (autoplay policy) or, in some environments, return undefined\n // instead of a Promise — never let a blocked/unsupported playback surface as an error.\n audio.play()?.catch(() => {});\n } catch {\n // Audio unavailable/unsupported — fail silently.\n }\n }\n if (notifyOnComplete && typeof Notification !== 'undefined' && Notification.permission === 'granted') {\n try {\n new Notification(notificationTitle ?? 'Timer done', {\n body: notificationBody ?? (typeof label === 'string' ? label : undefined),\n });\n } catch {\n // Some browsers (e.g. certain mobile Safari versions) throw constructing\n // Notification outside specific contexts — fail silently, as documented.\n }\n }\n }\n if (status !== 'completed') completedFiredRef.current = false;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [status]);\n\n // durationSeconds prop change: reset (and optionally restart) at the new duration. Skips\n // the initial mount, since autoStart/initial state already cover that.\n useEffect(() => {\n if (!mountedRef.current) {\n mountedRef.current = true;\n return;\n }\n const nextMs = durationSeconds * 1000;\n totalMsRef.current = nextMs;\n setRemaining(nextMs);\n completedFiredRef.current = false;\n bumpEpoch();\n if (resetOnDurationChange) {\n setStatus(autoStart ? 'running' : 'idle');\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [durationSeconds]);\n\n // Running-ripple spawner: spawns a new ripple roughly every RIPPLE_SPAWN_INTERVAL_MS while\n // running, each animating once (via CSS, `forwards`) and removing itself on `onAnimationEnd`.\n // Pausing (or otherwise leaving `running`) only stops new spawns here — it doesn't touch\n // `ripples` — so whichever ripples are already mid-flight keep animating to completion\n // instead of vanishing mid-cycle.\n useEffect(() => {\n if (!showRunningRipples || status !== 'running' || prefersReducedMotion()) return undefined;\n function spawnRipple() {\n rippleIdRef.current += 1;\n setRipples((prev) => [...prev, rippleIdRef.current]);\n }\n spawnRipple();\n const id = setInterval(spawnRipple, RIPPLE_SPAWN_INTERVAL_MS);\n return () => clearInterval(id);\n }, [showRunningRipples, status]);\n\n function handleRippleAnimationEnd(id: number) {\n setRipples((prev) => prev.filter((existingId) => existingId !== id));\n }\n\n function requestNotificationPermissionIfNeeded() {\n if (!notifyOnComplete || notifyPermissionRequestedRef.current) return;\n if (typeof Notification === 'undefined' || Notification.permission !== 'default') return;\n notifyPermissionRequestedRef.current = true;\n Notification.requestPermission().catch(() => {});\n }\n\n function start() {\n requestNotificationPermissionIfNeeded();\n if (statusRef.current === 'completed' || statusRef.current === 'cancelled') {\n setRemaining(totalMsRef.current);\n completedFiredRef.current = false;\n }\n bumpEpoch();\n setStatus('running');\n onStart?.();\n }\n\n function pause() {\n if (statusRef.current !== 'running') return;\n setStatus('paused');\n onPause?.();\n }\n\n function resume() {\n if (statusRef.current !== 'paused') return;\n bumpEpoch();\n setStatus('running');\n onResume?.();\n }\n\n function addTime(seconds: number) {\n const capped = maxSeconds != null ? maxSeconds * 1000 : Infinity;\n const next = Math.min(remainingRef.current + seconds * 1000, capped);\n setRemaining(next);\n bumpEpoch();\n onAddTime?.(seconds, Math.round(next / 1000));\n if (statusRef.current === 'completed' && next > 0) {\n completedFiredRef.current = false;\n setStatus('running');\n }\n }\n\n function removeTime(seconds: number) {\n const next = Math.max(0, remainingRef.current - seconds * 1000);\n setRemaining(next);\n bumpEpoch();\n onRemoveTime?.(seconds, Math.round(next / 1000));\n if (next <= 0 && statusRef.current === 'running') setStatus('completed');\n }\n\n function cancel() {\n setRemaining(totalMsRef.current);\n setStatus('cancelled');\n onCancel?.();\n }\n\n function reset() {\n setRemaining(totalMsRef.current);\n completedFiredRef.current = false;\n setStatus('idle');\n }\n\n useImperativeHandle(ref, () => ({\n start,\n pause,\n resume,\n addTime,\n removeTime,\n cancel,\n reset,\n getStatus: () => statusRef.current,\n getRemainingMs: () => remainingRef.current,\n }));\n\n const radius = 50 - strokeWidth / 2;\n const circumference = useMemo(() => 2 * Math.PI * radius, [radius]);\n const clampedFraction = totalMsRef.current > 0 ? Math.min(1, remainingMs / totalMsRef.current) : 0;\n const dashOffset = circumference * (1 - clampedFraction);\n const urgentMs = Math.max(totalMsRef.current * urgentThreshold, urgentMinSeconds * 1000);\n const isUrgent = status === 'running' && remainingMs <= urgentMs;\n const isCritical = isUrgent && remainingMs <= Math.min(5000, urgentMs);\n const isPaused = status === 'paused';\n\n // The pause button doubles as a play/restart button whenever there's nothing to pause —\n // idle (autoStart={false}), completed, or cancelled — rather than hiding itself, so there's\n // always a way to (re)start the countdown from the built-in controls alone.\n const showAddButton = showControls && !hideAddButton;\n const showRemoveButton = showControls && !hideRemoveButton;\n const showPauseButton = showControls && !hidePauseButton;\n const pauseButtonIcon = status === 'running' ? faPause : faPlay;\n const pauseButtonTitle =\n status === 'running' ? 'Pause' : isPaused ? 'Resume' : status === 'idle' ? 'Start' : 'Restart';\n const handlePauseButtonClick = status === 'running' ? pause : isPaused ? resume : start;\n const showCancelButton = showControls && !hideCancelButton;\n\n return (\n <div\n {...rest}\n className={cx(\n styles.timer,\n pulseOnUrgent && isUrgent && styles.timerPulsing,\n isCritical && styles.timerCritical,\n className,\n classNames?.root,\n )}\n style={\n {\n ...(size ? { '--timer-size': size } : null),\n '--timer-stroke-width': `${strokeWidth}px`,\n ...style,\n ...slotStyles?.root,\n } as React.CSSProperties\n }\n >\n <div className={cx(styles.timerRingWrapper, classNames?.ringWrapper)} style={slotStyles?.ringWrapper}>\n {ripples.length > 0 && (\n <div className={cx(styles.timerRipples, classNames?.ripples)} style={slotStyles?.ripples}>\n {ripples.map((id) => (\n <span\n key={id}\n className={cx(styles.timerRipple, classNames?.ripple)}\n style={slotStyles?.ripple}\n onAnimationEnd={() => handleRippleAnimationEnd(id)}\n />\n ))}\n </div>\n )}\n <svg className={cx(styles.timerRingSvg, classNames?.ringSvg)} viewBox='0 0 100 100' style={slotStyles?.ringSvg}>\n <circle\n className={cx(styles.timerRingTrack, classNames?.ringTrack)}\n cx='50'\n cy='50'\n r={radius}\n style={slotStyles?.ringTrack}\n />\n <circle\n className={cx(\n styles.timerRingProgress,\n isUrgent && styles.timerRingProgressUrgent,\n classNames?.ringProgress,\n )}\n cx='50'\n cy='50'\n r={radius}\n strokeDasharray={circumference}\n strokeDashoffset={dashOffset}\n style={{\n transition: isPaused ? 'none' : undefined,\n transform: counterClockwise ? 'rotate(-90deg)' : undefined,\n ...slotStyles?.ringProgress,\n }}\n />\n </svg>\n <div className={cx(styles.timerDisplay, classNames?.display)} style={slotStyles?.display}>\n <span className={cx(styles.timerTime, classNames?.time)} style={slotStyles?.time}>\n {formatTime(remainingMs)}\n </span>\n {label && (\n <span className={cx(styles.timerLabel, classNames?.label)} style={slotStyles?.label}>\n {label}\n </span>\n )}\n </div>\n </div>\n\n {showControls && (\n <div className={cx(styles.timerControls, classNames?.controls)} style={slotStyles?.controls}>\n <div className={styles.timerControlsRow}>\n {showRemoveButton && (\n <ActionIcon\n icon={faMinus}\n title={`Remove ${adjustSeconds}s`}\n onClick={() => removeTime(adjustSeconds)}\n className={cx(styles.timerAdjustButton, classNames?.removeButton)}\n style={slotStyles?.removeButton}\n />\n )}\n {showPauseButton && (\n <Button\n size={ButtonSize.Small}\n icon={pauseButtonIcon}\n title={pauseButtonTitle}\n onClick={handlePauseButtonClick}\n className={classNames?.pauseButton}\n style={slotStyles?.pauseButton}\n />\n )}\n {showAddButton && (\n <ActionIcon\n icon={faPlus}\n title={`Add ${adjustSeconds}s`}\n onClick={() => addTime(adjustSeconds)}\n className={cx(styles.timerAdjustButton, classNames?.addButton)}\n style={slotStyles?.addButton}\n />\n )}\n </div>\n {showCancelButton && (\n <ActionIcon\n icon={faXmark}\n title='Cancel timer'\n onClick={cancel}\n className={cx(styles.timerCancelButton, classNames?.cancelButton)}\n style={slotStyles?.cancelButton}\n />\n )}\n </div>\n )}\n </div>\n );\n});\n\nTimer.displayName = 'Timer';\n\nexport default Timer;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"timer.module.mjs","names":[],"sources":["../../../../package/common/timer/timer.module.pcss"],"sourcesContent":[".timer {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 16px;\n width: fit-content;\n\n &-ringWrapper {\n position: relative;\n width: var(--timer-size, 160px);\n height: var(--timer-size, 160px);\n transform-origin: center;\n }\n\n &-ringSvg {\n position: relative;\n width: 100%;\n height: 100%;\n overflow: visible;\n }\n\n &-ripples {\n position: absolute;\n inset: 0;\n pointer-events: none;\n }\n\n &-ripple {\n position: absolute;\n inset: 0;\n border-radius: 50%;\n border: 2px solid var(--timer-ripple-color, var(--timer-progress-color, var(--primary-color)));\n opacity: 0;\n transform: scale(0.94);\n\n /* One-shot per spawned instance (see the ripple spawner in timer.tsx), not an infinite\n loop — so pausing simply stops new spawns while already-mid-flight ripples keep\n animating to completion via `forwards` instead of vanishing mid-cycle. */\n animation: timerRippleHalo 3s ease-out forwards;\n }\n\n &-ringTrack {\n fill: none;\n stroke: var(--timer-track-color, var(--background-accent-light));\n stroke-width: var(--timer-stroke-width, 10px);\n }\n\n &-ringProgress {\n fill: none;\n stroke: var(--timer-progress-color, var(--primary-color));\n stroke-width: var(--timer-stroke-width, 10px);\n stroke-linecap: round;\n\n /* Single combined transform, not split across an SVG `transform` attribute on this element\n plus a separate CSS transform on an ancestor — that split combination, together with an\n actively-transitioning stroke-dashoffset, is a known trigger for SVG dash-rendering glitches\n in some engines (the visible arc appearing to jump/drift independent of the actual\n dashoffset value). scaleX(-1) mirrors so the depleting boundary sweeps clockwise (matching\n a clock face) instead of the counter-clockwise direction a <circle>'s implicit path winds\n by default; rotate(-90deg) moves the fixed anchor from 3 o'clock to 12 o'clock. Both applied\n around the circle's own center, which coincides with the viewBox center here. `counterClockwise`\n overrides this inline (timer.tsx) by dropping the scaleX(-1), keeping just the rotate — that\n default winding direction is exactly counter-clockwise, so no mirroring is needed for it. */\n transform: scaleX(-1) rotate(-90deg);\n transform-origin: 50% 50%;\n\n /* Matches (slightly exceeds) the JS tick interval (TICK_MS in timer.tsx) so the ring\n tracks the real remaining time closely instead of perpetually chasing a target that's\n always a full second stale — a much longer transition here reads as sluggish since the\n rendered ring never catches up to the true value while ticking continues. */\n transition:\n stroke-dashoffset 220ms linear,\n stroke 0.3s ease;\n\n &-urgent {\n stroke: var(--timer-urgent-color, var(--error-color));\n }\n }\n\n &-display {\n position: absolute;\n inset: 0;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n text-align: center;\n padding: 0 12px;\n }\n\n &-time {\n font-size: 1.4em;\n font-weight: 600;\n font-variant-numeric: tabular-nums;\n color: var(--timer-text-color, var(--text-color));\n }\n\n &-label {\n margin-top: 4px;\n font-size: 0.8em;\n color: var(--timer-label-color, var(--text-dark));\n }\n\n &-controls {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 8px;\n }\n\n &-controlsRow {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n &-adjustButton {\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n }\n\n &-cancelButton {\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n opacity: 0.6;\n\n &:hover {\n opacity: 1;\n }\n }\n\n &-pulsing &-ringWrapper {\n animation: timerHeartbeat 1s ease-in-out infinite;\n }\n\n &-critical &-ringWrapper {\n animation-duration: 0.6s;\n }\n}\n\n@keyframes timerHeartbeat {\n 0%,\n 100% {\n transform: scale(1);\n }\n\n 50% {\n transform: scale(1.045);\n }\n}\n\n@keyframes timerRippleHalo {\n 0% {\n transform: scale(0.94);\n opacity: 0.5;\n }\n\n 100% {\n transform: scale(1.35);\n opacity: 0;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .timer-ringProgress {\n transition: stroke 0.3s ease;\n }\n\n .timer-pulsing .timer-ringWrapper {\n animation: none;\n }\n\n .timer-ripple {\n animation: none;\n opacity: 0;\n }\n}\n"],"mappings":""}
1
+ {"version":3,"file":"timer.module.mjs","names":[],"sources":["../../../../package/common/timer/timer.module.pcss"],"sourcesContent":[".timer {\n /* em, because the readout inside the ring is em too: a fixed ring stayed put while its own\n digits grew with the theme until they filled it. */\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 1em;\n width: fit-content;\n\n &-ringWrapper {\n position: relative;\n width: var(--timer-size, 10em);\n height: var(--timer-size, 10em);\n transform-origin: center;\n }\n\n &-ringSvg {\n position: relative;\n width: 100%;\n height: 100%;\n overflow: visible;\n }\n\n &-ripples {\n position: absolute;\n inset: 0;\n pointer-events: none;\n }\n\n &-ripple {\n position: absolute;\n inset: 0;\n border-radius: 50%;\n border: 0.125em solid var(--timer-ripple-color, var(--timer-progress-color, var(--primary-color)));\n opacity: 0;\n transform: scale(0.94);\n\n /* One-shot per spawned instance (see the ripple spawner in timer.tsx), not an infinite\n loop — so pausing simply stops new spawns while already-mid-flight ripples keep\n animating to completion via `forwards` instead of vanishing mid-cycle. */\n animation: timerRippleHalo 3s ease-out forwards;\n }\n\n /* Not em, unlike everything else here: inside the svg this is a viewBox user unit, so it\n already scales with the ring, and it has to stay equal to the `strokeWidth` prop that\n timer.tsx subtracts from the circle's radius. */\n &-ringTrack {\n fill: none;\n stroke: var(--timer-track-color, var(--background-accent-light));\n stroke-width: var(--timer-stroke-width, 10px);\n }\n\n &-ringProgress {\n fill: none;\n stroke: var(--timer-progress-color, var(--primary-color));\n stroke-width: var(--timer-stroke-width, 10px);\n stroke-linecap: round;\n\n /* Single combined transform, not split across an SVG `transform` attribute on this element\n plus a separate CSS transform on an ancestor — that split combination, together with an\n actively-transitioning stroke-dashoffset, is a known trigger for SVG dash-rendering glitches\n in some engines (the visible arc appearing to jump/drift independent of the actual\n dashoffset value). scaleX(-1) mirrors so the depleting boundary sweeps clockwise (matching\n a clock face) instead of the counter-clockwise direction a <circle>'s implicit path winds\n by default; rotate(-90deg) moves the fixed anchor from 3 o'clock to 12 o'clock. Both applied\n around the circle's own center, which coincides with the viewBox center here. `counterClockwise`\n overrides this inline (timer.tsx) by dropping the scaleX(-1), keeping just the rotate — that\n default winding direction is exactly counter-clockwise, so no mirroring is needed for it. */\n transform: scaleX(-1) rotate(-90deg);\n transform-origin: 50% 50%;\n\n /* Matches (slightly exceeds) the JS tick interval (TICK_MS in timer.tsx) so the ring\n tracks the real remaining time closely instead of perpetually chasing a target that's\n always a full second stale — a much longer transition here reads as sluggish since the\n rendered ring never catches up to the true value while ticking continues. */\n transition:\n stroke-dashoffset 220ms linear,\n stroke 0.3s ease;\n\n &-urgent {\n stroke: var(--timer-urgent-color, var(--error-color));\n }\n }\n\n &-display {\n position: absolute;\n inset: 0;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n text-align: center;\n padding: 0 0.75em;\n }\n\n &-time {\n font-size: 1.4em;\n font-weight: 600;\n font-variant-numeric: tabular-nums;\n color: var(--timer-text-color, var(--text-color));\n }\n\n &-label {\n margin-top: 0.25em;\n font-size: 0.8em;\n color: var(--timer-label-color, var(--text-dark));\n }\n\n &-controls {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 0.5em;\n }\n\n &-controlsRow {\n display: flex;\n align-items: center;\n gap: 0.5em;\n }\n\n &-adjustButton {\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n }\n\n &-cancelButton {\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n opacity: 0.6;\n\n &:hover {\n opacity: 1;\n }\n }\n\n &-pulsing &-ringWrapper {\n animation: timerHeartbeat 1s ease-in-out infinite;\n }\n\n &-critical &-ringWrapper {\n animation-duration: 0.6s;\n }\n}\n\n@keyframes timerHeartbeat {\n 0%,\n 100% {\n transform: scale(1);\n }\n\n 50% {\n transform: scale(1.045);\n }\n}\n\n@keyframes timerRippleHalo {\n 0% {\n transform: scale(0.94);\n opacity: 0.5;\n }\n\n 100% {\n transform: scale(1.35);\n opacity: 0;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .timer-ringProgress {\n transition: stroke 0.3s ease;\n }\n\n .timer-pulsing .timer-ringWrapper {\n animation: none;\n }\n\n .timer-ripple {\n animation: none;\n opacity: 0;\n }\n}\n"],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"toast.module.mjs","names":[],"sources":["../../../../package/common/toast/toast.module.pcss"],"sourcesContent":[".container {\n position: fixed;\n z-index: 9999;\n display: flex;\n flex-direction: column;\n gap: 8px;\n pointer-events: none;\n padding: 16px;\n box-sizing: border-box;\n\n &-top-left {\n top: 0;\n left: 0;\n align-items: flex-start;\n }\n\n &-top-center {\n top: 0;\n left: 50%;\n transform: translateX(-50%);\n align-items: center;\n }\n\n &-top-right {\n top: 0;\n right: 0;\n align-items: flex-end;\n }\n\n &-bottom-left {\n bottom: 0;\n left: 0;\n align-items: flex-start;\n flex-direction: column-reverse;\n }\n\n &-bottom-center {\n bottom: 0;\n left: 50%;\n transform: translateX(-50%);\n align-items: center;\n flex-direction: column-reverse;\n }\n\n &-bottom-right {\n bottom: 0;\n right: 0;\n align-items: flex-end;\n flex-direction: column-reverse;\n }\n}\n\n.toast {\n pointer-events: all;\n display: flex;\n align-items: flex-start;\n gap: 10px;\n padding: 12px 14px;\n border-radius: var(--default-border-radius);\n background: var(--background-modal);\n color: var(--text-color);\n font-family: var(--default-font);\n box-shadow: var(--card-shadow);\n min-width: 240px;\n max-width: 380px;\n box-sizing: border-box;\n position: relative;\n\n /* Tells the browser vertical panning is native (so the page can still scroll behind the toast)\n while horizontal movement is left for useSwipeDismiss's own pointer handling to decide. */\n touch-action: pan-y;\n overflow: hidden;\n animation: toastSlideDown 0.25s ease forwards;\n\n &-bottom {\n animation: toastSlideUp 0.25s ease forwards;\n }\n\n &-dismissing {\n animation: toastFadeOut 0.2s ease forwards;\n }\n\n &-paused .toastProgressBar {\n animation-play-state: paused;\n }\n\n &-info {\n border-left: 3px solid var(--info-color, #3b82f6);\n\n .toastIcon {\n color: var(--info-color, #3b82f6);\n }\n }\n\n &-success {\n border-left: 3px solid var(--success-color);\n\n .toastIcon {\n color: var(--success-color);\n }\n }\n\n &-error {\n border-left: 3px solid var(--error-color, #ef4444);\n\n .toastIcon {\n color: var(--error-color, #ef4444);\n }\n }\n\n &-warn {\n border-left: 3px solid var(--warn-color);\n\n .toastIcon {\n color: var(--warn-color);\n }\n }\n\n &-default {\n border-left: 3px solid var(--primary-color);\n\n .toastIcon {\n color: var(--primary-color);\n }\n }\n}\n\n.toastIcon {\n flex-shrink: 0;\n margin-top: 1px;\n font-size: 15px;\n}\n\n.toastContent {\n flex: 1;\n font-size: 14px;\n line-height: 1.4;\n}\n\n.toastCloseButton {\n flex-shrink: 0;\n background: none;\n border: none;\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n padding: 0;\n color: var(--text-dark);\n font-size: 13px;\n opacity: 0.6;\n transition: opacity 0.15s;\n margin-top: 1px;\n\n &:hover {\n opacity: 1;\n }\n}\n\n.toastProgressBar {\n position: absolute;\n bottom: 0;\n left: 0;\n height: 3px;\n width: 100%;\n background: currentcolor;\n opacity: 0.35;\n transform-origin: left;\n animation: toastShrink var(--toast-duration) linear forwards;\n}\n\n@keyframes toastSlideDown {\n from {\n opacity: 0;\n transform: translateY(-12px);\n }\n\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n@keyframes toastSlideUp {\n from {\n opacity: 0;\n transform: translateY(12px);\n }\n\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n@keyframes toastFadeOut {\n from {\n opacity: 1;\n transform: scale(1);\n }\n\n to {\n opacity: 0;\n transform: scale(0.92);\n }\n}\n\n@keyframes toastShrink {\n from {\n transform: scaleX(1);\n }\n\n to {\n transform: scaleX(0);\n }\n}\n"],"mappings":""}
1
+ {"version":3,"file":"toast.module.mjs","names":[],"sources":["../../../../package/common/toast/toast.module.pcss"],"sourcesContent":[".container {\n position: fixed;\n z-index: 9999;\n display: flex;\n flex-direction: column;\n gap: 0.5em;\n pointer-events: none;\n padding: 1em;\n box-sizing: border-box;\n\n &-top-left {\n top: 0;\n left: 0;\n align-items: flex-start;\n }\n\n &-top-center {\n top: 0;\n left: 50%;\n transform: translateX(-50%);\n align-items: center;\n }\n\n &-top-right {\n top: 0;\n right: 0;\n align-items: flex-end;\n }\n\n &-bottom-left {\n bottom: 0;\n left: 0;\n align-items: flex-start;\n flex-direction: column-reverse;\n }\n\n &-bottom-center {\n bottom: 0;\n left: 50%;\n transform: translateX(-50%);\n align-items: center;\n flex-direction: column-reverse;\n }\n\n &-bottom-right {\n bottom: 0;\n right: 0;\n align-items: flex-end;\n flex-direction: column-reverse;\n }\n}\n\n.toast {\n pointer-events: all;\n display: flex;\n align-items: flex-start;\n gap: 0.625em;\n padding: 0.75em 0.875em;\n border-radius: var(--default-border-radius);\n background: var(--background-modal);\n color: var(--text-color);\n font-family: var(--default-font);\n box-shadow: var(--card-shadow);\n\n /* em, so the line length stays a measure rather than a pixel count. */\n min-width: 15em;\n max-width: 23.75em;\n box-sizing: border-box;\n position: relative;\n\n /* Tells the browser vertical panning is native (so the page can still scroll behind the toast)\n while horizontal movement is left for useSwipeDismiss's own pointer handling to decide. */\n touch-action: pan-y;\n overflow: hidden;\n animation: toastSlideDown 0.25s ease forwards;\n\n &-bottom {\n animation: toastSlideUp 0.25s ease forwards;\n }\n\n &-dismissing {\n animation: toastFadeOut 0.2s ease forwards;\n }\n\n &-paused .toastProgressBar {\n animation-play-state: paused;\n }\n\n &-info {\n border-left: 0.1875em solid var(--info-color, #3b82f6);\n\n .toastIcon {\n color: var(--info-color, #3b82f6);\n }\n }\n\n &-success {\n border-left: 0.1875em solid var(--success-color);\n\n .toastIcon {\n color: var(--success-color);\n }\n }\n\n &-error {\n border-left: 0.1875em solid var(--error-color, #ef4444);\n\n .toastIcon {\n color: var(--error-color, #ef4444);\n }\n }\n\n &-warn {\n border-left: 0.1875em solid var(--warn-color);\n\n .toastIcon {\n color: var(--warn-color);\n }\n }\n\n &-default {\n border-left: 0.1875em solid var(--primary-color);\n\n /* Paints the gradient exactly over border-left, which a background layer only reaches\n because its origin is the border box. The border stays as the no-gradient fallback. */\n background-image: var(--primary-gradient, none);\n background-repeat: no-repeat;\n background-size: 0.1875em 100%;\n background-origin: border-box;\n\n .toastIcon {\n color: var(--primary-color);\n }\n }\n}\n\n.toastIcon {\n flex-shrink: 0;\n margin-top: 0.0667em; /* 1px against this element's own 0.9375em */\n font-size: 0.9375em;\n}\n\n.toastContent {\n flex: 1;\n font-size: 0.875em;\n line-height: 1.4;\n}\n\n.toastCloseButton {\n flex-shrink: 0;\n background: none;\n border: none;\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n padding: 0;\n color: var(--text-dark);\n font-size: 0.8125em;\n opacity: 0.6;\n transition: opacity 0.15s;\n margin-top: 0.0769em; /* 1px against this element's own 0.8125em */\n\n &:hover {\n opacity: 1;\n }\n}\n\n.toastProgressBar {\n position: absolute;\n bottom: 0;\n left: 0;\n height: 0.1875em;\n width: 100%;\n background: currentcolor;\n opacity: 0.35;\n transform-origin: left;\n animation: toastShrink var(--toast-duration) linear forwards;\n}\n\n@keyframes toastSlideDown {\n from {\n opacity: 0;\n transform: translateY(-0.75em);\n }\n\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n@keyframes toastSlideUp {\n from {\n opacity: 0;\n transform: translateY(0.75em);\n }\n\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n@keyframes toastFadeOut {\n from {\n opacity: 1;\n transform: scale(1);\n }\n\n to {\n opacity: 0;\n transform: scale(0.92);\n }\n}\n\n@keyframes toastShrink {\n from {\n transform: scaleX(1);\n }\n\n to {\n transform: scaleX(0);\n }\n}\n"],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"tooltip.module.mjs","names":[],"sources":["../../../../package/common/tooltip/tooltip.module.pcss"],"sourcesContent":[".tooltipAnchor {\n position: fixed;\n z-index: 10;\n}\n\n.tooltipBubble {\n position: absolute;\n bottom: 0;\n margin-bottom: 6px;\n left: 0;\n border-radius: 3px;\n width: max-content;\n max-width: 200px;\n font-size: 12px;\n background-color: #454953;\n padding: 6px 9px;\n color: white;\n pointer-events: none;\n\n .tooltip-anchor-flip & {\n bottom: auto;\n top: 0;\n margin-bottom: 0;\n margin-top: 6px;\n }\n\n &::after {\n content: '';\n position: absolute;\n top: 100%;\n left: calc(var(--tooltip-trigger-width, 10px) / 2 - 5px);\n border-width: 5px;\n border-style: solid;\n border-color: #454953 transparent transparent;\n }\n\n .tooltip-anchor-flip &::after {\n top: auto;\n bottom: 100%;\n border-color: transparent transparent #454953;\n }\n}\n\n.tooltipDetails {\n position: relative;\n display: inline-flex;\n align-items: center;\n\n &-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n font-size: 11px;\n font-weight: 700;\n background-color: #454953;\n color: white;\n cursor: default;\n user-select: none;\n }\n}\n\n.tooltipError {\n position: absolute;\n bottom: 0;\n margin-bottom: 6px;\n right: 0;\n width: max-content;\n border-radius: 3px;\n background-color: var(--error-lighter);\n color: var(--red);\n font-size: 10px;\n padding: 6px 9px;\n\n .tooltip-anchor-flip & {\n bottom: auto;\n top: 0;\n margin-bottom: 0;\n margin-top: 6px;\n }\n\n &::after {\n content: '';\n position: absolute;\n top: 100%;\n right: 12px;\n border-width: 5px;\n border-style: solid;\n border-color: var(--error-border-color) transparent transparent transparent;\n }\n\n .tooltip-anchor-flip &::after {\n top: auto;\n bottom: 100%;\n border-color: transparent transparent var(--error-border-color) transparent;\n }\n}\n"],"mappings":""}
1
+ {"version":3,"file":"tooltip.module.mjs","names":[],"sources":["../../../../package/common/tooltip/tooltip.module.pcss"],"sourcesContent":[".tooltipAnchor {\n /* Resolved once per root so the bubble, its arrow and the badge cannot drift apart. The\n default is an inverse of the theme, not a fixed slate: a tooltip has to separate from\n the page it floats over, and a dark bubble sat almost invisibly on a dark theme. */\n --custom-tooltip-background: var(--tooltip-background, color-mix(in oklab, var(--text-color) 82%, var(--background)));\n --custom-tooltip-color: var(--tooltip-color, var(--background));\n\n position: fixed;\n z-index: 10;\n}\n\n.tooltipBubble {\n position: absolute;\n bottom: 0;\n\n /* The lengths here are em against this bubble's own 0.75em, so the ratios are of 12px,\n not 16, and max-width is a line measure rather than a pixel cap. */\n margin-bottom: 0.5em;\n left: 0;\n border-radius: 0.25em;\n width: max-content;\n max-width: 16.667em;\n font-size: 0.75em;\n background-color: var(--custom-tooltip-background);\n padding: 0.5em 0.75em;\n color: var(--custom-tooltip-color);\n pointer-events: none;\n\n .tooltip-anchor-flip & {\n bottom: auto;\n top: 0;\n margin-bottom: 0;\n margin-top: 0.5em;\n }\n\n &::after {\n content: '';\n position: absolute;\n top: 100%;\n\n /* The offset must stay half the arrow's own width, or it stops pointing at the trigger. */\n left: calc(var(--tooltip-trigger-width, 10px) / 2 - 0.4167em);\n border-width: 0.4167em;\n border-style: solid;\n border-color: var(--custom-tooltip-background) transparent transparent;\n }\n\n .tooltip-anchor-flip &::after {\n top: auto;\n bottom: 100%;\n border-color: transparent transparent var(--custom-tooltip-background);\n }\n}\n\n.tooltipDetails {\n /* Repeated from .tooltipAnchor above: the badge is a second root, rendered without one. */\n --custom-tooltip-background: var(--tooltip-background, color-mix(in oklab, var(--text-color) 82%, var(--background)));\n --custom-tooltip-color: var(--tooltip-color, var(--background));\n\n position: relative;\n display: inline-flex;\n align-items: center;\n\n &-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n\n /* The theme's base, not a multiple of the font below: that landed a fraction of a pixel short. */\n width: var(--default-font-size);\n height: var(--default-font-size);\n border-radius: 50%;\n\n /* calc, not em: this badge renders inline wherever the consumer puts it, so em would\n measure that text. The bubble above can use em, being portaled to #bodyEnd. */\n font-size: calc(var(--default-font-size) * 0.6875);\n font-weight: 700;\n background-color: var(--custom-tooltip-background);\n color: var(--custom-tooltip-color);\n cursor: default;\n user-select: none;\n }\n}\n\n.tooltipError {\n position: absolute;\n bottom: 0;\n margin-bottom: 0.6em;\n right: 0;\n width: max-content;\n border-radius: 0.3em;\n background-color: var(--error-lighter);\n color: var(--error-color);\n font-size: 0.625em;\n padding: 0.6em 0.9em;\n\n .tooltip-anchor-flip & {\n bottom: auto;\n top: 0;\n margin-bottom: 0;\n margin-top: 0.6em;\n }\n\n &::after {\n content: '';\n position: absolute;\n top: 100%;\n right: 1.2em;\n border-width: 0.5em;\n border-style: solid;\n border-color: var(--error-border-color) transparent transparent transparent;\n }\n\n .tooltip-anchor-flip &::after {\n top: auto;\n bottom: 100%;\n border-color: transparent transparent var(--error-border-color) transparent;\n }\n}\n"],"mappings":""}
@@ -0,0 +1,2 @@
1
+ function e(e){return e.target===e.currentTarget&&(e.key===`Enter`||e.key===` `)}export{e as isSelfActivation};
2
+ //# sourceMappingURL=activationKey.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activationKey.mjs","names":[],"sources":["../../../../package/common/utils/activationKey.ts"],"sourcesContent":["import type React from 'react';\n\n/**\n * Whether a keydown is a click-equivalent activation of the element the handler sits on.\n *\n * The target check is the important half: keydown bubbles, so without it a Space typed into a\n * nested input activates the container and, once the container calls `preventDefault()`, never\n * reaches the input at all.\n */\nexport function isSelfActivation(event: React.KeyboardEvent): boolean {\n return event.target === event.currentTarget && (event.key === 'Enter' || event.key === ' ');\n}\n"],"mappings":"AASA,SAAgB,EAAiB,EAAqC,CACpE,OAAO,EAAM,SAAW,EAAM,gBAAkB,EAAM,MAAQ,SAAW,EAAM,MAAQ,IACzF"}