@weasel-js/ui 0.7.2 → 1.0.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.
- package/dist/chunks/{ActionBar-CiKvEvXi.js → ActionBar-GThu74LC.js} +4 -4
- package/dist/chunks/ActionBar-GThu74LC.js.map +1 -0
- package/dist/chunks/{Badge-DU8ihE7Z.js → Badge-DZQqAV4d.js} +5 -5
- package/dist/chunks/Badge-DZQqAV4d.js.map +1 -0
- package/dist/chunks/{Checkbox-CtK8H_OQ.js → Checkbox-Cf6YHVWV.js} +5 -5
- package/dist/chunks/Checkbox-Cf6YHVWV.js.map +1 -0
- package/dist/chunks/{ComboBox-CotoRgNi.js → ComboBox-EnV1IytN.js} +8 -8
- package/dist/chunks/ComboBox-EnV1IytN.js.map +1 -0
- package/dist/chunks/{DataGrid-mUq38k0Q.js → DataGrid-BQklXGRl.js} +10 -10
- package/dist/chunks/DataGrid-BQklXGRl.js.map +1 -0
- package/dist/chunks/GradientEditor-Co9aGyRe.js +260 -0
- package/dist/chunks/GradientEditor-Co9aGyRe.js.map +1 -0
- package/dist/chunks/{Input-D_Xzgr2b.js → Input-qk44ESKC.js} +4 -4
- package/dist/chunks/Input-qk44ESKC.js.map +1 -0
- package/dist/chunks/{Keycaps-B9dJMYx_.js → Keycaps-CFX7UAj8.js} +5 -5
- package/dist/chunks/Keycaps-CFX7UAj8.js.map +1 -0
- package/dist/chunks/{NumberField-BQmELz32.js → NumberField-CYhQeWoM.js} +5 -5
- package/dist/chunks/NumberField-CYhQeWoM.js.map +1 -0
- package/dist/chunks/{Powerline-C8rddbbK.js → Powerline-Bg9M5oBK.js} +2 -2
- package/dist/chunks/{Powerline-C8rddbbK.js.map → Powerline-Bg9M5oBK.js.map} +1 -1
- package/dist/chunks/{Prefs-BkDJ5Cdz.js → Prefs-DZjXp2Ln.js} +23 -23
- package/dist/chunks/Prefs-DZjXp2Ln.js.map +1 -0
- package/dist/chunks/{RadioGroup-Ci-I9NEx.js → RadioGroup-rL2i3afr.js} +5 -5
- package/dist/chunks/RadioGroup-rL2i3afr.js.map +1 -0
- package/dist/chunks/{RangeSlider-CW-edtNx.js → RangeSlider-DPldYd4O.js} +8 -8
- package/dist/chunks/RangeSlider-DPldYd4O.js.map +1 -0
- package/dist/chunks/{ResizeHandle-C2OBMqTs.js → ResizeHandle-DR8X229y.js} +3 -3
- package/dist/chunks/ResizeHandle-DR8X229y.js.map +1 -0
- package/dist/chunks/{Select-CLyBisCS.js → Select-vSeAbspK.js} +9 -9
- package/dist/chunks/Select-vSeAbspK.js.map +1 -0
- package/dist/chunks/{SelectionPanel-DBdhAKIK.js → SelectionPanel-1mmALQba.js} +19 -19
- package/dist/chunks/SelectionPanel-1mmALQba.js.map +1 -0
- package/dist/chunks/{Sidebar-doLqiTZH.js → Sidebar-CigrFvpL.js} +4 -4
- package/dist/chunks/Sidebar-CigrFvpL.js.map +1 -0
- package/dist/chunks/{SidebarPanel-DjTfOzB9.js → SidebarPanel-C0w16BrF.js} +10 -10
- package/dist/chunks/SidebarPanel-C0w16BrF.js.map +1 -0
- package/dist/chunks/{Slider-sUJGgmKR.js → Slider-Di2kNqVy.js} +11 -11
- package/dist/chunks/Slider-Di2kNqVy.js.map +1 -0
- package/dist/chunks/{StatusBar-OOjfqCD8.js → StatusBar-lt_Ok6WK.js} +6 -6
- package/dist/chunks/StatusBar-lt_Ok6WK.js.map +1 -0
- package/dist/chunks/{Switch-DxJLMYki.js → Switch-BYj762hm.js} +5 -5
- package/dist/chunks/Switch-BYj762hm.js.map +1 -0
- package/dist/chunks/{ToolButton-Dv6NdZKh.js → ToolButton-5L1sAlJC.js} +6 -6
- package/dist/chunks/ToolButton-5L1sAlJC.js.map +1 -0
- package/dist/chunks/{ToolOptionsBar-ChiL-HVA.js → ToolOptionsBar-D4xmXa_a.js} +4 -4
- package/dist/chunks/ToolOptionsBar-D4xmXa_a.js.map +1 -0
- package/dist/chunks/{ToolPalette-DXVIEO0w.js → ToolPalette-BDkvV3pJ.js} +7 -7
- package/dist/chunks/ToolPalette-BDkvV3pJ.js.map +1 -0
- package/dist/components/ActionBar/index.js +1 -1
- package/dist/components/Badge/index.js +1 -1
- package/dist/components/Checkbox/index.js +1 -1
- package/dist/components/ComboBox/index.js +1 -1
- package/dist/components/DataGrid/index.js +1 -1
- package/dist/components/GradientEditor/GradientEditor.d.ts +34 -0
- package/dist/components/GradientEditor/GradientEditor.d.ts.map +1 -0
- package/dist/components/GradientEditor/GradientHandles.d.ts +53 -0
- package/dist/components/GradientEditor/GradientHandles.d.ts.map +1 -0
- package/dist/components/GradientEditor/index.d.ts +3 -0
- package/dist/components/GradientEditor/index.d.ts.map +1 -0
- package/dist/components/GradientEditor/index.js +2 -0
- package/dist/components/Input/index.js +1 -1
- package/dist/components/Keycaps/index.js +1 -1
- package/dist/components/NumberField/index.js +1 -1
- package/dist/components/Powerline/index.js +2 -2
- package/dist/components/Prefs/index.js +1 -1
- package/dist/components/RadioGroup/index.js +1 -1
- package/dist/components/RangeSlider/index.js +1 -1
- package/dist/components/ResizeHandle/index.js +1 -1
- package/dist/components/Select/index.js +1 -1
- package/dist/components/SelectionPanel/index.js +1 -1
- package/dist/components/Sidebar/index.js +1 -1
- package/dist/components/SidebarPanel/index.js +1 -1
- package/dist/components/Slider/index.js +1 -1
- package/dist/components/StatusBar/index.js +1 -1
- package/dist/components/Switch/index.js +1 -1
- package/dist/components/ToolButton/index.js +1 -1
- package/dist/components/ToolOptionsBar/index.js +1 -1
- package/dist/components/ToolPalette/ToolPalette.d.ts.map +1 -1
- package/dist/components/ToolPalette/index.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +36 -70
- package/dist/index.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +2 -2
- package/dist/chunks/ActionBar-CiKvEvXi.js.map +0 -1
- package/dist/chunks/Badge-DU8ihE7Z.js.map +0 -1
- package/dist/chunks/Checkbox-CtK8H_OQ.js.map +0 -1
- package/dist/chunks/ComboBox-CotoRgNi.js.map +0 -1
- package/dist/chunks/DataGrid-mUq38k0Q.js.map +0 -1
- package/dist/chunks/Input-D_Xzgr2b.js.map +0 -1
- package/dist/chunks/Keycaps-B9dJMYx_.js.map +0 -1
- package/dist/chunks/NumberField-BQmELz32.js.map +0 -1
- package/dist/chunks/Prefs-BkDJ5Cdz.js.map +0 -1
- package/dist/chunks/RadioGroup-Ci-I9NEx.js.map +0 -1
- package/dist/chunks/RangeSlider-CW-edtNx.js.map +0 -1
- package/dist/chunks/ResizeHandle-C2OBMqTs.js.map +0 -1
- package/dist/chunks/Select-CLyBisCS.js.map +0 -1
- package/dist/chunks/SelectionPanel-DBdhAKIK.js.map +0 -1
- package/dist/chunks/Sidebar-doLqiTZH.js.map +0 -1
- package/dist/chunks/SidebarPanel-DjTfOzB9.js.map +0 -1
- package/dist/chunks/Slider-sUJGgmKR.js.map +0 -1
- package/dist/chunks/StatusBar-OOjfqCD8.js.map +0 -1
- package/dist/chunks/Switch-DxJLMYki.js.map +0 -1
- package/dist/chunks/ToolButton-Dv6NdZKh.js.map +0 -1
- package/dist/chunks/ToolOptionsBar-ChiL-HVA.js.map +0 -1
- package/dist/chunks/ToolPalette-DXVIEO0w.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"DataGrid-mUq38k0Q.js","names":[],"sources":["../../src/useReorderDragList.ts","../../src/components/DataGrid/DataGrid.module.css","../../src/components/DataGrid/DataGrid.tsx"],"sourcesContent":["import { useCallback, useRef, useState } from 'react';\nimport type { ReactNode, PointerEvent as ReactPointerEvent, RefCallback } from 'react';\n\nexport interface LayerListItem {\n id: string;\n label: ReactNode;\n /** Locked rows cannot be dragged, cannot be crossed by drops, and\n * never combine with other rows in a multi-selection. */\n locked?: boolean;\n /** Optional color swatch rendered before the label. Any CSS color string. */\n swatch?: string;\n}\n\nexport interface UseReorderDragListOptions {\n items: LayerListItem[];\n selectedIds: string[];\n onReorder(ids: string[], targetIndex: number): void;\n /** Pointer-move distance (px) before pending drag engages. Default 4. */\n threshold?: number;\n}\n\nexport interface ReorderDragState {\n draggedIds: string[] | null;\n targetIndex: number | null;\n}\n\nexport interface ReorderDragHandlers {\n rowProps(id: string, index: number): { onPointerDown(e: ReactPointerEvent): void };\n containerProps: {\n ref: RefCallback<HTMLElement>;\n onPointerMove(e: ReactPointerEvent): void;\n onPointerUp(e: ReactPointerEvent): void;\n onPointerCancel(e: ReactPointerEvent): void;\n };\n state: ReorderDragState;\n}\n\ninterface PendingState {\n id: string;\n sourceIndex: number;\n startX: number;\n startY: number;\n pointerId: number;\n}\n\ninterface ActiveState extends PendingState {\n draggedIds: string[];\n targetIndex: number;\n}\n\nexport function useReorderDragList(opts: UseReorderDragListOptions): ReorderDragHandlers {\n const { threshold = 4 } = opts;\n const optsRef = useRef(opts);\n optsRef.current = opts;\n const containerRef = useRef<HTMLElement | null>(null);\n const pendingRef = useRef<PendingState | null>(null);\n const activeRef = useRef<ActiveState | null>(null);\n const [state, setState] = useState<ReorderDragState>({ draggedIds: null, targetIndex: null });\n\n const computeTargetIndex = useCallback((clientY: number): number => {\n const items = optsRef.current.items;\n // Locked rows act as walls — drops cannot cross them. Cap at the\n // first locked row's index (or items.length if none are locked).\n const firstLocked = items.findIndex((it) => it.locked);\n const cap = firstLocked === -1 ? items.length : firstLocked;\n const c = containerRef.current;\n if (!c) return 0;\n const rows = Array.from(c.children) as HTMLElement[];\n let raw = rows.length;\n for (let i = 0; i < rows.length; i++) {\n const row = rows[i];\n if (!row) continue;\n const r = row.getBoundingClientRect();\n if (clientY < r.bottom) { raw = i; break; }\n }\n return Math.min(raw, cap);\n }, []);\n\n const refCb = useCallback<RefCallback<HTMLElement>>((el) => {\n containerRef.current = el;\n }, []);\n\n const onPointerDownRow = useCallback((id: string, index: number, e: ReactPointerEvent) => {\n // Locked items cannot be dragged — skip recording the pending state so\n // pointer-move cannot engage. Plain click still works because LayerList\n // tracks click intent in its own ref, separate from drag candidacy.\n const item = optsRef.current.items[index];\n if (item?.locked) return;\n // Capture the pointer to the row so subsequent move/up events keep firing\n // (on the row, bubbling to the container) even when the user drags\n // outside the list's bounding box. Without capture, releasing outside\n // the container leaves the drag state stuck because pointerup fires on\n // the document, not the list.\n try { (e.currentTarget as Element).setPointerCapture?.(e.pointerId); } catch { /* unsupported / already captured */ }\n pendingRef.current = {\n id,\n sourceIndex: index,\n startX: e.clientX,\n startY: e.clientY,\n pointerId: e.pointerId,\n };\n }, []);\n\n const reset = useCallback(() => {\n pendingRef.current = null;\n activeRef.current = null;\n setState({ draggedIds: null, targetIndex: null });\n }, []);\n\n const onPointerMove = useCallback((e: ReactPointerEvent) => {\n const active = activeRef.current;\n if (active) {\n const targetIndex = computeTargetIndex(e.clientY);\n if (targetIndex !== active.targetIndex) {\n active.targetIndex = targetIndex;\n setState({ draggedIds: active.draggedIds, targetIndex });\n }\n return;\n }\n const pending = pendingRef.current;\n if (!pending) return;\n const dx = e.clientX - pending.startX;\n const dy = e.clientY - pending.startY;\n if (Math.hypot(dx, dy) < threshold) return;\n // Engage.\n const selected = optsRef.current.selectedIds;\n const inSelection = selected.includes(pending.id);\n const draggedIds = inSelection ? [...selected] : [pending.id];\n const targetIndex = computeTargetIndex(e.clientY);\n activeRef.current = { ...pending, draggedIds, targetIndex };\n setState({ draggedIds, targetIndex });\n }, [computeTargetIndex, threshold]);\n\n const onPointerUp = useCallback((e: ReactPointerEvent) => {\n const active = activeRef.current;\n if (!active) {\n reset();\n return;\n }\n const targetIndex = computeTargetIndex(e.clientY);\n // Skip no-op: target lands inside the dragged-ids contiguous block.\n const items = optsRef.current.items;\n const indices = active.draggedIds\n .map((id) => items.findIndex((it) => it.id === id))\n .filter((i) => i >= 0)\n .sort((a, b) => a - b);\n const isContiguous =\n indices.length > 0 &&\n indices.every((v, i) => {\n if (i === 0) return true;\n const prev = indices[i - 1];\n return prev !== undefined && v === prev + 1;\n });\n const first = indices[0];\n const last = indices[indices.length - 1];\n const wouldBeNoop =\n isContiguous &&\n first !== undefined &&\n last !== undefined &&\n targetIndex >= first &&\n targetIndex <= last + 1;\n if (!wouldBeNoop) {\n optsRef.current.onReorder(active.draggedIds, targetIndex);\n }\n reset();\n }, [computeTargetIndex, reset]);\n\n const onPointerCancel = useCallback(() => {\n reset();\n }, [reset]);\n\n return {\n rowProps: (id, index) => ({\n onPointerDown: (e) => onPointerDownRow(id, index, e),\n }),\n containerProps: {\n ref: refCb,\n onPointerMove,\n onPointerUp,\n onPointerCancel,\n },\n state,\n };\n}\n",".grid {\n position: relative;\n width: 100%;\n}\n\n.table {\n width: 100%;\n border-collapse: collapse;\n font-size: 11px;\n}\n\n.table th,\n.table td {\n text-align: left;\n padding: 3px 6px;\n vertical-align: middle;\n}\n.table tbody tr {\n /* Subtle row separator — was a full-weight border on every cell.\n Drastically toned down per visual-weight feedback. */\n border-bottom: 1px solid rgba(255, 255, 255, 0.04);\n}\n\n.table th {\n font-weight: 600;\n color: var(--wzl-muted, #9ea1a8);\n position: sticky;\n top: 0;\n background: var(--wzl-surface, #2a2018);\n z-index: 1;\n border-bottom: 1px solid var(--wzl-panel-border, #25272c);\n}\n\n.sortable {\n cursor: pointer;\n user-select: none;\n}\n.sortable:hover { color: var(--wzl-fg, #e6e7e9); }\n.sortActive { color: var(--wzl-fg, #e6e7e9); }\n\n.empty {\n color: var(--wzl-muted, #9ea1a8);\n font-style: italic;\n padding: 8px 0;\n text-align: center;\n}\n\n/* Drag-handle column. */\n.handleCol { width: 14px; }\n.handleCell {\n width: 14px;\n cursor: grab;\n color: var(--wzl-muted, #9ea1a8);\n user-select: none;\n text-align: center;\n}\n.handleCell:active { cursor: grabbing; }\n\n.dragging { opacity: 0.4; }\n\n.dropIndicator {\n position: absolute;\n left: 0;\n right: 0;\n height: 2px;\n background: var(--wzl-accent, #7fb069);\n pointer-events: none;\n}\n","/**\n * Lightweight sortable data grid. Use for inspector tables — action\n * registries, route maps, telemetry rows. Intentionally narrow:\n *\n * - Sortable columns (click header to cycle asc → desc → none)\n * - Custom cell render via per-column `render`\n * - Optional drag handles for row reordering (when `onReorder` is set)\n *\n * Not: virtual scrolling, column resizing, multi-column sort, filtering,\n * inline editing. Reach for a real grid library when you need those.\n */\nimport { Fragment, useMemo, useState } from 'react';\nimport type { ReactNode } from 'react';\nimport { useReorderDragList } from '../../useReorderDragList';\nimport s from './DataGrid.module.css';\n\nexport interface DataGridColumn<Row> {\n id: string;\n header: ReactNode;\n /** Pull a sortable value from the row. Defaults to `(row as any)[column.id]`. */\n accessor?: (row: Row) => string | number | null | undefined;\n /** Render the cell. Defaults to `String(accessor(row) ?? '')`. */\n render?: (row: Row) => ReactNode;\n /** Default true. Set false to suppress the sort affordance on this column. */\n sortable?: boolean;\n /** Optional CSS class on every <td> in this column. */\n className?: string;\n}\n\nexport interface DataGridProps<Row extends { id: string }> {\n rows: readonly Row[];\n columns: readonly DataGridColumn<Row>[];\n /** Initial sort. Omit for unsorted (rows render in input order). */\n defaultSort?: { columnId: string; direction: 'asc' | 'desc' };\n /** Enable drag handles in a leading column. Receives the reorder spec\n * (the existing `useReorderDragList` semantics — see weasel-ui). */\n onReorder?: (ids: string[], targetIndex: number) => void;\n /** Empty-state row text. Default `'—'`. */\n empty?: ReactNode;\n className?: string;\n}\n\ntype SortState = { columnId: string; direction: 'asc' | 'desc' } | null;\n\nexport function DataGrid<Row extends { id: string }>(props: DataGridProps<Row>) {\n const { rows, columns, defaultSort, onReorder, empty = '—', className } = props;\n const [sort, setSort] = useState<SortState>(defaultSort ?? null);\n\n const sortedRows = useMemo(() => {\n if (!sort) return [...rows];\n const col = columns.find((c) => c.id === sort.columnId);\n if (!col) return [...rows];\n const get = col.accessor ?? ((row: Row) => (row as unknown as Record<string, unknown>)[col.id] as string | number | null | undefined);\n const dir = sort.direction === 'asc' ? 1 : -1;\n return [...rows].sort((a, b) => {\n const av = get(a);\n const bv = get(b);\n if (av == null && bv == null) return 0;\n if (av == null) return 1;\n if (bv == null) return -1;\n if (av < bv) return -1 * dir;\n if (av > bv) return 1 * dir;\n return 0;\n });\n }, [rows, columns, sort]);\n\n const cycleSort = (columnId: string) => {\n setSort((cur) => {\n if (!cur || cur.columnId !== columnId) return { columnId, direction: 'asc' };\n if (cur.direction === 'asc') return { columnId, direction: 'desc' };\n return null;\n });\n };\n\n // Drag handles via useReorderDragList. Adapter: the hook expects\n // `{ id, label }`-shaped items; we map our rows to that shape (label\n // unused — the hook only uses id and locked).\n const drag = useReorderDragList({\n items: sortedRows.map((r) => ({ id: r.id, label: '' })),\n selectedIds: [],\n onReorder: onReorder ?? (() => {}),\n });\n const dragEnabled = !!onReorder;\n\n const cls = [s.grid, className].filter(Boolean).join(' ');\n\n return (\n <div\n className={cls}\n ref={dragEnabled ? (drag.containerProps.ref as React.RefCallback<HTMLDivElement>) : undefined}\n onPointerMove={dragEnabled ? drag.containerProps.onPointerMove : undefined}\n onPointerUp={dragEnabled ? drag.containerProps.onPointerUp : undefined}\n onPointerCancel={dragEnabled ? drag.containerProps.onPointerCancel : undefined}\n >\n <table className={s.table}>\n <thead>\n <tr>\n {dragEnabled && <th className={s.handleCol} aria-label=\"drag\" />}\n {columns.map((col) => {\n const sortable = col.sortable !== false;\n const active = sort?.columnId === col.id;\n const indicator = active ? (sort.direction === 'asc' ? ' ↑' : ' ↓') : '';\n return (\n <th\n key={col.id}\n className={[col.className, sortable ? s.sortable : '', active ? s.sortActive : '']\n .filter(Boolean).join(' ')}\n onClick={sortable ? () => cycleSort(col.id) : undefined}\n >\n {col.header}{indicator}\n </th>\n );\n })}\n </tr>\n </thead>\n <tbody>\n {sortedRows.length === 0 && (\n <tr>\n <td colSpan={columns.length + (dragEnabled ? 1 : 0)} className={s.empty}>\n {empty}\n </td>\n </tr>\n )}\n {sortedRows.map((row, i) => {\n const isDragging = drag.state.draggedIds?.includes(row.id) ?? false;\n return (\n <Fragment key={row.id}>\n <tr className={isDragging ? s.dragging : undefined}>\n {dragEnabled && (\n <td\n className={s.handleCell}\n onPointerDown={(e) => drag.rowProps(row.id, i).onPointerDown(e)}\n >\n <span aria-hidden=\"true\">⋮⋮</span>\n </td>\n )}\n {columns.map((col) => {\n const get = col.accessor ?? ((r: Row) => (r as unknown as Record<string, unknown>)[col.id] as string | number | null | undefined);\n const content = col.render ? col.render(row) : String(get(row) ?? '');\n return (\n <td key={col.id} className={col.className}>\n {content}\n </td>\n );\n })}\n </tr>\n </Fragment>\n );\n })}\n </tbody>\n </table>\n {dragEnabled && drag.state.targetIndex !== null && (\n <div\n className={s.dropIndicator}\n style={{ top: `${drag.state.targetIndex * 28}px` }}\n />\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Input-D_Xzgr2b.js","names":[],"sources":["../../src/components/Input/Input.module.css","../../src/components/Input/Input.tsx"],"sourcesContent":[".field {\n display: flex;\n flex-direction: column;\n width: 100%;\n min-width: 0;\n gap: 4px;\n}\n\n.frame {\n display: flex;\n align-items: center;\n gap: 6px;\n height: 24px;\n padding: 0 8px;\n background: var(--wzl-input-bg);\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n color: var(--wzl-fg);\n font-family: var(--wzl-font-ui);\n font-size: 13px;\n line-height: 1;\n transition: border-color var(--wzl-motion-fast), box-shadow var(--wzl-motion-fast);\n}\n\n.frame:focus-within {\n border-color: var(--wzl-focus-ring);\n box-shadow: 0 0 0 1px var(--wzl-focus-ring);\n}\n\n.field[data-invalid] .frame {\n border-color: var(--wzl-danger, #ff5b5b);\n}\n\n.field[data-disabled] .frame {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.frame input {\n flex: 1;\n min-width: 0;\n background: transparent;\n border: none;\n outline: none;\n color: inherit;\n font: inherit;\n padding: 0;\n}\n\n.frame input::placeholder {\n color: var(--wzl-fg-subtle);\n}\n\n.adornment {\n display: inline-flex;\n align-items: center;\n color: var(--wzl-fg-subtle);\n font-size: 12px;\n pointer-events: none;\n}\n","import { forwardRef, type ReactNode, type Ref } from 'react';\nimport {\n TextField,\n Label,\n Input as RACInput,\n Text,\n FieldError,\n type TextFieldProps as RACTextFieldProps,\n type ValidationResult,\n} from 'react-aria-components';\nimport { fieldClasses } from '../Field/Field';\nimport s from './Input.module.css';\n\nexport type InputProps = Omit<RACTextFieldProps, 'children' | 'className'> & {\n label?: ReactNode;\n description?: ReactNode;\n errorMessage?: ReactNode | ((v: ValidationResult) => ReactNode);\n placeholder?: string;\n leadingAdornment?: ReactNode;\n trailingAdornment?: ReactNode;\n className?: string;\n};\n\n/**\n * Single-line text input wrapping React Aria's TextField. Supplies a\n * default skin against the `--wzl-*` token system, and exposes the\n * familiar label / description / errorMessage / adornment slot shape.\n *\n * `ref` forwards to the underlying `<input>`.\n */\nexport const Input = forwardRef(function Input(\n props: InputProps,\n ref: Ref<HTMLInputElement>,\n) {\n const {\n label,\n description,\n errorMessage,\n placeholder,\n leadingAdornment,\n trailingAdornment,\n className,\n ...textFieldProps\n } = props;\n\n return (\n <TextField {...textFieldProps} className={[s.field, fieldClasses.root, className].filter(Boolean).join(' ')}>\n {label !== undefined && <Label className={fieldClasses.label}>{label}</Label>}\n <div className={s.frame}>\n {leadingAdornment !== undefined && <span className={s.adornment}>{leadingAdornment}</span>}\n <RACInput ref={ref} placeholder={placeholder} />\n {trailingAdornment !== undefined && <span className={s.adornment}>{trailingAdornment}</span>}\n </div>\n {description !== undefined && (\n <Text slot=\"description\" className={fieldClasses.hint}>\n {description}\n </Text>\n )}\n <FieldError className={fieldClasses.error}>{errorMessage}</FieldError>\n </TextField>\n );\n});\n"],"mappings":";;;;;;;;GC8Ba,IAAQ,EAAW,SAC9B,GACA,GACA;CACA,IAAM,EACJ,UACA,gBACA,iBACA,gBACA,qBACA,sBACA,cACA,GAAG,MACD;AAEJ,QACE,kBAAC,GAAD;EAAW,GAAI;EAAgB,WAAW;GAAC,EAAE;GAAO,EAAa;GAAM;GAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;YAA3G;GACG,MAAU,KAAA,KAAa,kBAAC,GAAD;IAAO,WAAW,EAAa;cAAQ;IAAc,CAAA;GAC7E,kBAAC,OAAD;IAAK,WAAW,EAAE;cAAlB;KACG,MAAqB,KAAA,KAAa,kBAAC,QAAD;MAAM,WAAW,EAAE;gBAAY;MAAwB,CAAA;KAC1F,kBAAC,GAAD;MAAe;MAAkB;MAAe,CAAA;KAC/C,MAAsB,KAAA,KAAa,kBAAC,QAAD;MAAM,WAAW,EAAE;gBAAY;MAAyB,CAAA;KACxF;;GACL,MAAgB,KAAA,KACf,kBAAC,GAAD;IAAM,MAAK;IAAc,WAAW,EAAa;cAC9C;IACI,CAAA;GAET,kBAAC,GAAD;IAAY,WAAW,EAAa;cAAQ;IAA0B,CAAA;GAC5D;;EAEd"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Keycaps-B9dJMYx_.js","names":[],"sources":["../../src/components/Keycaps/Keycaps.module.css","../../src/components/Keycaps/Keycap.tsx","../../src/components/Keycaps/Keycaps.tsx","../../src/components/Keycaps/keySpecsFromMods.ts"],"sourcesContent":["/* Keystroke \"key cap\" styling — each modifier and key in its own bordered chip. */\n.keys {\n display: inline-flex;\n gap: 3px;\n /* Bottom-align so chips with text labels and chips with icon glyphs\n (arrows, modifier symbols) line up at the same bottom edge. Baseline\n drifts because text baselines sit above icon centers. */\n align-items: flex-end;\n /* When this whole sequence sits inline in surrounding text (e.g.\n \"Press <KeySequence> to confirm.\"), the row anchors to the\n surrounding line's bottom edge, not its baseline. */\n vertical-align: bottom;\n flex-wrap: wrap;\n}\n\n.keysEmpty {\n color: var(--wzl-muted, #9ea1a8);\n}\n\n.sep {\n color: var(--wzl-muted, #9ea1a8);\n font-size: 11px;\n line-height: 1;\n}\n\n.key {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n height: 18px;\n padding: 0 4px;\n /* Light face, slightly-darker-than-face border, dark glyph — reads as\n a real keycap rather than a recessed slot in the panel. */\n background: var(--wzl-fg, #e6e7e9);\n border: 1px solid #25272c;\n border-bottom-width: 2px;\n border-radius: 3px;\n color: var(--wzl-bg, #181a1e);\n /* Keycaps are a deliberate exception to the site theme — the design-\n system UI font (often a display face like Oswald) is wrong for\n keyboard chips. Use a neutral sans stack so labels read like real\n key legends. Consumers wanting a specific face per chip pass the\n `font` prop on KeyCap. */\n font-family: ui-sans-serif, system-ui, sans-serif;\n font-size: 11px;\n font-weight: 700;\n line-height: 1;\n}\n\n/* Default chip is square — letters, digits, and single glyphs (⎋ ⌫ ↑ etc.)\n sit in a uniform key-cap box. */\n.key[data-kind='square'] {\n width: 18px;\n padding: 0;\n}\n\n/* Modifiers (⌘ ⇪ ⌥ ⌃) are wider to match the visual weight of how they\n appear on a real keyboard — and to differentiate them from the keys\n they modify in a stack. */\n.key[data-kind='modifier'] {\n min-width: 26px;\n padding: 0 6px;\n}\n\n/* Tab / Enter / Space — actual wide keys on a real keyboard. */\n.key[data-kind='wide'] {\n min-width: 32px;\n padding: 0 8px;\n}\n\n/* Optional/inverted (default variant): face flips to dark; legend drops\n to 60% opacity; dotted border drops to 40% opacity. Three soft cues\n stacked for \"may be held but isn't required.\" */\n.key[data-inverted] {\n background: var(--wzl-bg, #181a1e);\n color: color-mix(in srgb, var(--wzl-fg, #e6e7e9) 60%, transparent);\n border-style: dotted;\n border-color: color-mix(in srgb, var(--wzl-fg, #e6e7e9) 40%, transparent);\n border-width: 1.5px;\n border-bottom-width: 1.5px;\n}\n\n/* Minimal variant — no fill, borders and legend follow currentColor.\n Useful inline in colored prose / next to colored badges where the\n chip should take the surrounding text color. */\n.key[data-variant='minimal'] {\n background: transparent;\n border-color: currentColor;\n color: currentColor;\n}\n\n/* Optional + minimal: dotted border at full currentColor, legend at full\n currentColor. The dotted pattern alone carries the \"optional\" cue here\n — minimal's whole point is to defer color to the surrounding context,\n so dimming would fight that. (Default variant uses graded opacity as a\n stacked cue; minimal keeps it crisp.) */\n.key[data-variant='minimal'][data-inverted] {\n border-style: dotted;\n border-color: currentColor;\n color: currentColor;\n border-width: 1.5px;\n border-bottom-width: 1.5px;\n}\n","import type { CSSProperties } from 'react';\nimport s from './Keycaps.module.css';\n\nconst MODIFIER_GLYPHS = new Set(['⌘', '⇪', '⌥', '⌃', '⇧']);\nconst WIDE_GLYPHS = new Set(['⇥', '↵', '␣']);\n\nexport type KeycapKind = 'modifier' | 'wide' | 'square';\n\nexport function inferKeycapKind(label: string): KeycapKind {\n if (MODIFIER_GLYPHS.has(label)) return 'modifier';\n if (WIDE_GLYPHS.has(label)) return 'wide';\n if (label.length > 1) return 'wide';\n return 'square';\n}\n\nexport type KeyCapVariant = 'default' | 'minimal';\n\nexport interface KeyCapProps {\n /** Glyph rendered in the chip (modifier or key). */\n label: string;\n /** Marks the chip as not required to trigger the action. In the default\n * variant the chip face inverts (dark face / light glyph) for\n * emphasis. In the `'minimal'` variant the chip's border becomes\n * dotted to read as \"may be held, but isn't required.\" */\n inverted?: boolean;\n /** Visual style. `'default'` (default) renders a filled chip; `'minimal'`\n * renders an unfilled chip whose border and legend are `currentColor`\n * — useful inline in colored prose / next to colored badges where the\n * chip should take the surrounding text color. */\n variant?: KeyCapVariant;\n className?: string;\n /** Optional inline style. Useful for one-off width overrides (e.g.\n * building a keyboard-layout where the space bar spans several\n * standard-key widths). The component sets no inline styles of its\n * own — everything else comes from the CSS module. */\n style?: CSSProperties;\n /** Override the chip's font-family. Accepts any CSS font-family\n * string. When omitted, the chip inherits from the design-system\n * UI font token (`--wzl-font-ui`). Useful for one-off cases where\n * a different face is desired — though the canonical move is to\n * override the CSS variable at the consumer's scope, not the prop. */\n font?: string;\n}\n\n/** Single bordered keycap chip for one glyph (modifier or key). Use\n * `KeySequence` to render a full shortcut. */\nexport function KeyCap({ label, inverted = false, variant = 'default', className, style, font }: KeyCapProps) {\n const resolvedStyle = font ? { ...style, fontFamily: font } : style;\n return (\n <kbd\n className={[s.key, className].filter(Boolean).join(' ')}\n data-kind={inferKeycapKind(label)}\n data-variant={variant === 'default' ? undefined : variant}\n data-inverted={inverted || undefined}\n style={resolvedStyle}\n >\n {label}\n </kbd>\n );\n}\n","import { Fragment } from 'react';\nimport s from './Keycaps.module.css';\nimport { KeyCap, inferKeycapKind, type KeyCapVariant } from './Keycap';\n\nexport interface KeySpec {\n /** Glyph rendered in the chip (modifier or key). */\n label: string;\n /** When true, the chip renders inverted to mark it as not required to\n * trigger the action (e.g. an optional modifier). Defaults to false. */\n optional?: boolean;\n}\n\nexport interface KeySequenceProps {\n /** Keys to render. `undefined` or empty renders a muted em-dash.\n * Modifiers are always rendered first regardless of input order; relative\n * order within each group is preserved. */\n keys: readonly KeySpec[] | undefined;\n /** Character inserted between the trailing modifier chip and the first\n * non-modifier chip (e.g. `'+'` renders `⌘ + K`). `null` or `''`\n * suppresses it. Defaults to `'+'`. */\n separator?: string | null;\n /** Forwarded to every `KeyCap` in the sequence. `'minimal'` renders\n * unfilled chips whose border + legend are `currentColor`. */\n variant?: KeyCapVariant;\n /** Forwarded to every `KeyCap` in the sequence. Overrides the design-\n * system UI font for that chip only. */\n font?: string;\n className?: string;\n}\n\n/** Renders a shortcut as a row of `KeyCap` chips, one per key. Optional\n * keys render inverted to distinguish them from required ones. */\nexport function KeySequence({ keys, separator = '+', variant = 'default', font, className }: KeySequenceProps) {\n if (!keys || keys.length === 0) {\n return <span className={[s.keysEmpty, className].filter(Boolean).join(' ')}>—</span>;\n }\n const ordered = keys\n .map((k, i) => ({ k, i }))\n .sort((a, b) => {\n const am = inferKeycapKind(a.k.label) === 'modifier' ? 0 : 1;\n const bm = inferKeycapKind(b.k.label) === 'modifier' ? 0 : 1;\n return am - bm || a.i - b.i;\n })\n .map(({ k }) => k);\n const sepIdx = ordered.findIndex((k) => inferKeycapKind(k.label) !== 'modifier');\n const showSep = !!separator && sepIdx > 0 && sepIdx < ordered.length;\n return (\n <span className={[s.keys, className].filter(Boolean).join(' ')}>\n {ordered.map((k, i) => (\n <Fragment key={i}>\n {showSep && i === sepIdx ? <span className={s.sep}>{separator}</span> : null}\n <KeyCap label={k.label} inverted={k.optional ?? false} variant={variant} font={font} />\n </Fragment>\n ))}\n </span>\n );\n}\n","import type { KeySpec } from './Keycaps';\n\n/** Logical modifier name. Matches the kit's `ModifierKey` in shape — kept as a\n * local type so `weasel-ui` stays independent of `@weasel-js/core`. */\nexport type LogicalMod = 'mod' | 'shift' | 'alt' | 'ctrl' | 'meta';\n\n/** Detected (or overridden) platform. `'mod'` resolves to ⌘ on macOS,\n * Ctrl on Windows / Linux. */\nexport type Platform = 'macos' | 'windows' | 'linux';\n\n/** Visual form of a modifier or named-key label.\n *\n * - `'auto'` (default): the per-entry, per-platform default — whatever\n * label is actually printed on the physical key. macOS keys show the\n * Apple glyphs (⌘ ⌥ ⌃ ⇧ ⎋ ↵ ⇥). Windows keys mostly show text\n * (Ctrl, Alt, Shift) BUT the Win key shows its logo (⊞) and the\n * context-menu key shows ▤. Linux mirrors Windows with Super (⊞).\n * - `'symbol'` forces the Apple-style glyph everywhere a symbolic form\n * exists. Entries without a widely-recognized symbol still fall\n * back to text.\n * - `'text'` always spells the label out (Cmd / Option / Esc / Enter). */\nexport type LegendStyle = 'auto' | 'symbol' | 'text';\n\nexport interface LogicalModSpec {\n name: LogicalMod;\n /** Marks this modifier as optional (may be held but isn't required).\n * Forwarded as the resulting `KeySpec.optional`. */\n optional?: boolean;\n}\n\nexport interface KeySpecsFromModsOptions {\n /** Override OS detection. Defaults to the detected platform, or\n * `'macos'` when detection is unavailable (e.g. in tests, or when\n * `navigator` is missing). */\n platform?: Platform;\n /** Defaults to `'auto'` (symbol on macOS, text everywhere else). Pass\n * `'symbol'` or `'text'` to force a specific style regardless of\n * platform. */\n legend?: LegendStyle;\n}\n\nconst MOD_BY_PLATFORM: Record<LogicalMod, Record<Platform, Record<LegendStyle, string>>> = {\n mod: {\n macos: { auto: '⌘', symbol: '⌘', text: 'Cmd' },\n windows: { auto: 'Ctrl', symbol: 'Ctrl', text: 'Ctrl' },\n linux: { auto: 'Ctrl', symbol: 'Ctrl', text: 'Ctrl' },\n },\n shift: {\n macos: { auto: '⇧', symbol: '⇧', text: 'Shift' },\n windows: { auto: '⇧', symbol: '⇧', text: 'Shift' },\n linux: { auto: '⇧', symbol: '⇧', text: 'Shift' },\n },\n alt: {\n macos: { auto: '⌥', symbol: '⌥', text: 'Option' },\n windows: { auto: 'Alt', symbol: 'Alt', text: 'Alt' },\n linux: { auto: 'Alt', symbol: 'Alt', text: 'Alt' },\n },\n ctrl: {\n macos: { auto: '⌃', symbol: '⌃', text: 'Control' },\n windows: { auto: 'Ctrl', symbol: 'Ctrl', text: 'Ctrl' },\n linux: { auto: 'Ctrl', symbol: 'Ctrl', text: 'Ctrl' },\n },\n // Windows/Linux Meta has a recognized symbol on the physical key (⊞ Win\n // logo / Super logo), so `auto` shows it. Use `text` for Win / Super.\n meta: {\n macos: { auto: '⌘', symbol: '⌘', text: 'Cmd' },\n windows: { auto: '⊞', symbol: '⊞', text: 'Win' },\n linux: { auto: '⊞', symbol: '⊞', text: 'Super' },\n },\n};\n\n/** Detect the user's OS from the browser environment. UA sniffing — not\n * 100% reliable, but adequate for picking a modifier-glyph convention.\n * Falls back to `'macos'` (matches the kit's docs convention) when\n * `navigator` is unavailable. */\nexport function detectPlatform(): Platform {\n if (typeof navigator === 'undefined') return 'macos';\n // Prefer the modern userAgentData when available (Chromium-only today).\n const uaData = (navigator as Navigator & { userAgentData?: { platform?: string } }).userAgentData;\n const raw = (uaData?.platform ?? navigator.userAgent ?? '').toLowerCase();\n if (raw.includes('mac') || raw.includes('darwin') || raw.includes('iphone') || raw.includes('ipad')) return 'macos';\n if (raw.includes('win')) return 'windows';\n if (raw.includes('linux') || raw.includes('x11')) return 'linux';\n return 'macos';\n}\n\n/**\n * Map a list of **logical** modifiers (`'mod'`, `'shift'`, …) to **visual**\n * `KeySpec`s appropriate for the current platform.\n *\n * The kit-side gesture grammar uses logical names so the same route reads\n * correctly on every OS — `'mod'` means \"Cmd on Mac, Ctrl on Win/Linux\".\n * `KeySequence` is purely visual and renders the labels you give it; this\n * helper is the bridge.\n *\n * ```ts\n * <KeySequence\n * keys={keySpecsFromMods([{ name: 'mod' }, { name: 'shift', optional: true }])}\n * />\n * ```\n *\n * On a Mac: renders `⌘ ⇧`. On Windows: renders `Ctrl Shift`. Pass an\n * explicit `platform` to override detection (e.g. for docs that always\n * show the macOS form). Pass `legend: 'text'` to force \"Cmd\" over \"⌘\"\n * even on macOS.\n */\nexport function keySpecsFromMods(\n mods: readonly LogicalModSpec[],\n opts: KeySpecsFromModsOptions = {},\n): readonly KeySpec[] {\n const platform = opts.platform ?? detectPlatform();\n const legend = opts.legend ?? 'auto';\n return mods.map((m) => {\n const label = MOD_BY_PLATFORM[m.name][platform][legend];\n return m.optional ? { label, optional: true } : { label };\n });\n}\n\n/** Per-platform × per-legend table for named, non-modifier keys. `auto`\n * is whatever's printed on the physical key for that platform. Arrows\n * are universal symbols on every platform. */\nconst NAMED_KEY: Record<string, Record<Platform, Record<LegendStyle, string>>> = {\n Escape: { macos: { auto: '⎋', symbol: '⎋', text: 'Esc' }, windows: { auto: 'Esc', symbol: 'Esc', text: 'Esc' }, linux: { auto: 'Esc', symbol: 'Esc', text: 'Esc' } },\n Enter: { macos: { auto: '↵', symbol: '↵', text: 'Return' }, windows: { auto: 'Enter', symbol: 'Enter', text: 'Enter' }, linux: { auto: 'Enter', symbol: 'Enter', text: 'Enter' } },\n Return: { macos: { auto: '↵', symbol: '↵', text: 'Return' }, windows: { auto: 'Enter', symbol: 'Enter', text: 'Enter' }, linux: { auto: 'Enter', symbol: 'Enter', text: 'Enter' } },\n Tab: { macos: { auto: '⇥', symbol: '⇥', text: 'Tab' }, windows: { auto: 'Tab', symbol: 'Tab', text: 'Tab' }, linux: { auto: 'Tab', symbol: 'Tab', text: 'Tab' } },\n Space: { macos: { auto: '␣', symbol: '␣', text: 'Space' }, windows: { auto: 'Space', symbol: 'Space', text: 'Space' }, linux: { auto: 'Space', symbol: 'Space', text: 'Space' } },\n ' ': { macos: { auto: '␣', symbol: '␣', text: 'Space' }, windows: { auto: 'Space', symbol: 'Space', text: 'Space' }, linux: { auto: 'Space', symbol: 'Space', text: 'Space' } },\n Backspace: { macos: { auto: '⌫', symbol: '⌫', text: 'Backspace' }, windows: { auto: 'Backspace', symbol: 'Backspace', text: 'Backspace' }, linux: { auto: 'Backspace', symbol: 'Backspace', text: 'Backspace' } },\n Delete: { macos: { auto: '⌦', symbol: '⌦', text: 'Delete' }, windows: { auto: 'Delete', symbol: 'Delete', text: 'Delete' }, linux: { auto: 'Delete', symbol: 'Delete', text: 'Delete' } },\n // Context-menu key (the right-Ctrl-area key on Windows / Linux keyboards\n // with a ▤ icon). Mac has no equivalent; falls back to text 'Menu'.\n ContextMenu: { macos: { auto: 'Menu', symbol: 'Menu', text: 'Menu' }, windows: { auto: '▤', symbol: '▤', text: 'Menu' }, linux: { auto: '▤', symbol: '▤', text: 'Menu' } },\n ArrowUp: { macos: { auto: '↑', symbol: '↑', text: '↑' }, windows: { auto: '↑', symbol: '↑', text: '↑' }, linux: { auto: '↑', symbol: '↑', text: '↑' } },\n ArrowDown: { macos: { auto: '↓', symbol: '↓', text: '↓' }, windows: { auto: '↓', symbol: '↓', text: '↓' }, linux: { auto: '↓', symbol: '↓', text: '↓' } },\n ArrowLeft: { macos: { auto: '←', symbol: '←', text: '←' }, windows: { auto: '←', symbol: '←', text: '←' }, linux: { auto: '←', symbol: '←', text: '←' } },\n ArrowRight: { macos: { auto: '→', symbol: '→', text: '→' }, windows: { auto: '→', symbol: '→', text: '→' }, linux: { auto: '→', symbol: '→', text: '→' } },\n};\n\n/**\n * Render a raw `KeyboardEvent.key` value as a platform-appropriate\n * `KeySpec`. Named non-modifier keys (Esc, Enter, Tab, Space, Backspace,\n * Delete, arrows) get the platform's conventional form. Letter / digit /\n * other keys pass through upper-cased.\n *\n * ```ts\n * keySpecFromKey('Escape', { platform: 'macos' }) // { label: '⎋' }\n * keySpecFromKey('Escape', { platform: 'windows' }) // { label: 'Esc' }\n * keySpecFromKey('p') // { label: 'P' }\n * ```\n *\n * Symbol mode is only meaningful on macOS — Windows / Linux keyboards\n * print the text form on the physical key, so symbol mode there falls\n * back to text.\n */\nexport function keySpecFromKey(\n raw: string,\n opts: KeySpecsFromModsOptions & { optional?: boolean } = {},\n): KeySpec {\n const platform = opts.platform ?? detectPlatform();\n const legend = opts.legend ?? 'auto';\n const entry = NAMED_KEY[raw];\n const label = entry ? entry[platform][legend] : raw.length === 1 ? raw.toUpperCase() : raw;\n return opts.optional ? { label, optional: true } : { label };\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"NumberField-BQmELz32.js","names":[],"sources":["../../src/components/NumberField/NumberField.module.css","../../src/components/NumberField/NumberField.tsx"],"sourcesContent":[".field {\n display: flex;\n flex-direction: column;\n width: 100%;\n min-width: 0;\n gap: 4px;\n}\n\n.frame {\n display: flex;\n align-items: stretch;\n height: 24px;\n background: var(--wzl-input-bg);\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n color: var(--wzl-fg);\n font-family: var(--wzl-font-ui);\n font-size: 13px;\n line-height: 1;\n overflow: hidden;\n transition: border-color var(--wzl-motion-fast), box-shadow var(--wzl-motion-fast);\n}\n\n.frame:focus-within {\n border-color: var(--wzl-focus-ring);\n box-shadow: 0 0 0 1px var(--wzl-focus-ring);\n}\n\n.field[data-invalid] .frame {\n border-color: var(--wzl-danger, #ff5b5b);\n}\n\n.field[data-disabled] .frame {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.frame input {\n flex: 1;\n min-width: 0;\n background: transparent;\n border: none;\n outline: none;\n color: inherit;\n font: inherit;\n padding: 0 8px;\n text-align: right;\n -moz-appearance: textfield;\n}\n\n.frame input::-webkit-outer-spin-button,\n.frame input::-webkit-inner-spin-button {\n -webkit-appearance: none;\n margin: 0;\n}\n\n.steppers {\n display: flex;\n flex-direction: column;\n border-left: 1px solid var(--wzl-border);\n}\n\n.stepper {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 18px;\n flex: 1;\n background: transparent;\n border: none;\n color: var(--wzl-fg-muted);\n cursor: pointer;\n padding: 0;\n font-size: 9px;\n line-height: 1;\n}\n\n.stepper:not(:last-child) {\n border-bottom: 1px solid var(--wzl-border);\n}\n\n.stepper[data-hovered]:not([data-disabled]) {\n background: color-mix(in srgb, var(--wzl-fg) 10%, transparent);\n color: var(--wzl-fg);\n}\n\n.stepper[data-pressed]:not([data-disabled]) {\n background: color-mix(in srgb, var(--wzl-fg) 18%, transparent);\n}\n\n.stepper[data-disabled] {\n opacity: 0.4;\n cursor: not-allowed;\n}\n","import { forwardRef, type ReactNode, type Ref } from 'react';\nimport {\n NumberField as RACNumberField,\n Label,\n Input as RACInput,\n Group,\n Button as RACButton,\n Text,\n FieldError,\n type NumberFieldProps as RACNumberFieldProps,\n type ValidationResult,\n} from 'react-aria-components';\nimport { fieldClasses } from '../Field/Field';\nimport s from './NumberField.module.css';\n\nexport type NumberFieldProps = Omit<RACNumberFieldProps, 'children' | 'className'> & {\n label?: ReactNode;\n description?: ReactNode;\n errorMessage?: ReactNode | ((v: ValidationResult) => ReactNode);\n /** Hide the up/down stepper buttons. Defaults to false. */\n hideSteppers?: boolean;\n /** Native input placeholder — e.g. `'Mixed'` for a multi-selection\n * editor with no shared value. */\n placeholder?: string;\n className?: string;\n};\n\nexport const NumberField = forwardRef(function NumberField(\n props: NumberFieldProps,\n ref: Ref<HTMLInputElement>,\n) {\n const { label, description, errorMessage, hideSteppers, placeholder, className, ...rest } = props;\n return (\n <RACNumberField\n {...rest}\n className={[s.field, fieldClasses.root, className].filter(Boolean).join(' ')}\n >\n {label !== undefined && <Label className={fieldClasses.label}>{label}</Label>}\n <Group className={s.frame}>\n <RACInput ref={ref} placeholder={placeholder} />\n {!hideSteppers && (\n <div className={s.steppers}>\n <RACButton slot=\"increment\" className={s.stepper} aria-label=\"Increment\">▲</RACButton>\n <RACButton slot=\"decrement\" className={s.stepper} aria-label=\"Decrement\">▼</RACButton>\n </div>\n )}\n </Group>\n {description !== undefined && (\n <Text slot=\"description\" className={fieldClasses.hint}>\n {description}\n </Text>\n )}\n <FieldError className={fieldClasses.error}>{errorMessage}</FieldError>\n </RACNumberField>\n );\n});\n"],"mappings":";;;;;;;;;GC2Ba,IAAc,EAAW,SACpC,GACA,GACA;CACA,IAAM,EAAE,UAAO,gBAAa,iBAAc,iBAAc,gBAAa,cAAW,GAAG,MAAS;AAC5F,QACE,kBAAC,GAAD;EACE,GAAI;EACJ,WAAW;GAAC,EAAE;GAAO,EAAa;GAAM;GAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;YAF9E;GAIG,MAAU,KAAA,KAAa,kBAAC,GAAD;IAAO,WAAW,EAAa;cAAQ;IAAc,CAAA;GAC7E,kBAAC,GAAD;IAAO,WAAW,EAAE;cAApB,CACE,kBAAC,GAAD;KAAe;KAAkB;KAAe,CAAA,EAC/C,CAAC,KACA,kBAAC,OAAD;KAAK,WAAW,EAAE;eAAlB,CACE,kBAAC,GAAD;MAAW,MAAK;MAAY,WAAW,EAAE;MAAS,cAAW;gBAAY;MAAa,CAAA,EACtF,kBAAC,GAAD;MAAW,MAAK;MAAY,WAAW,EAAE;MAAS,cAAW;gBAAY;MAAa,CAAA,CAClF;OAEF;;GACP,MAAgB,KAAA,KACf,kBAAC,GAAD;IAAM,MAAK;IAAc,WAAW,EAAa;cAC9C;IACI,CAAA;GAET,kBAAC,GAAD;IAAY,WAAW,EAAa;cAAQ;IAA0B,CAAA;GACvD;;EAEnB"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Prefs-BkDJ5Cdz.js","names":[],"sources":["../../src/components/Prefs/schema.ts","../../src/components/Prefs/Prefs.module.css","../../src/components/Prefs/PrefsForm.tsx","../../src/components/Prefs/PrefsDialog.tsx"],"sourcesContent":["// Preference-schema vocabulary for PrefsForm / PrefsDialog.\n//\n// Deliberately field-compatible with core's `ToolPref*` family\n// (src/tools/prefs.ts): this schema module deliberately avoids importing\n// @weasel-js/core, so the contract is structural — a `ToolPrefGroup` (and\n// any host-app superset, like WeaselDraw's registry) assigns into\n// `PrefGroup` with no import and no cast. Keep the two in sync\n// field-for-field.\n\n/** Control-presentation hints per kind — visually-equivalent renderings\n * of the same value type. The persisted value is unchanged either way. */\nexport type PrefNumberControl = 'input' | 'slider';\nexport type PrefBooleanControl = 'checkbox' | 'switch';\nexport type PrefStringControl = 'input' | 'textarea';\nexport type PrefEnumControl = 'select' | 'radio';\n\ninterface PrefBase<K extends string, Value> {\n kind: K;\n /** Human-readable label, e.g. \"Show grid\". */\n name: string;\n /** Longer help text — surfaces as a tooltip on the row label. */\n description: string;\n /** Fallback when `values` holds nothing at this leaf's path. */\n default: Value;\n /** Omitted from the form unless `showHidden` is set. */\n hidden?: boolean;\n /** Render the control full-width with no label/tooltip row — for\n * controls that supply their own chrome (embedded sub-panel editors).\n * The `description` still applies wherever the renderer surfaces it. */\n block?: boolean;\n /** Row-pairing hint for compact property UIs (`SelectionPanel`):\n * leaves sharing a `pair` id render side-by-side on one row labeled\n * with the `pair` string. Purely presentational. */\n pair?: string;\n}\n\n/** Display-unit conversion for number leaves whose stored value uses a\n * canonical unit the user shouldn't see (radians → degrees). Mirrors\n * core's `ToolPrefNumberUnit`. */\nexport interface PrefNumberUnit {\n toDisplay: (stored: number) => number;\n fromDisplay: (display: number) => number;\n suffix?: string;\n}\n\nexport interface PrefNumber extends PrefBase<'number', number> {\n min?: number;\n max?: number;\n step?: number;\n control?: PrefNumberControl;\n unit?: PrefNumberUnit;\n}\nexport interface PrefBoolean extends PrefBase<'boolean', boolean> {\n control?: PrefBooleanControl;\n}\nexport interface PrefString extends PrefBase<'string', string> {\n control?: PrefStringControl;\n}\nexport interface PrefEnum<T extends string = string> extends PrefBase<'enum', T> {\n options: readonly { value: T; label: string }[];\n control?: PrefEnumControl;\n}\n\nexport interface PrefColor extends PrefBase<'color', string> {\n /** Value is `#rrggbb`, or `#rrggbbaa` when `alpha` is set. */\n alpha?: boolean;\n}\n\nexport type BuiltinPref = PrefNumber | PrefBoolean | PrefString | PrefEnum | PrefColor;\n\n/**\n * Open leaf: any node with a `kind` the form doesn't know renders via the\n * `renderers` map (`renderers[kind]`). App schemas keep their extra fields\n * (e.g. a registry-enum's `source`/`filter`) — renderers receive the node\n * and narrow it themselves. Deliberately NOT index-signatured so concrete\n * app interfaces stay assignable.\n */\nexport interface PrefCustom extends PrefBase<string, unknown> {}\n\nexport type PrefLeaf = BuiltinPref | PrefCustom;\n\n/** Nestable group: branch nodes in the schema tree. */\nexport interface PrefGroup {\n name: string;\n description?: string;\n children: Record<string, PrefLeaf | PrefGroup>;\n}\n\nexport function isPrefLeaf(node: PrefLeaf | PrefGroup): node is PrefLeaf {\n return 'kind' in node;\n}\n\n/** Get the value at a dotted path inside a nested value tree. Returns\n * `undefined` when a segment is missing or hits a non-object. */\nexport function prefValueAtPath(values: unknown, path: string): unknown {\n let cur: unknown = values;\n for (const seg of path.split('.')) {\n if (cur == null || typeof cur !== 'object') return undefined;\n cur = (cur as Record<string, unknown>)[seg];\n }\n return cur;\n}\n\n/**\n * Recursively drop `hidden` leaves (unless `showHidden`), pruning groups\n * that end up empty. Returns null when the entire subtree is hidden.\n */\nexport function visiblePrefSubtree<T extends PrefLeaf | PrefGroup>(\n node: T,\n showHidden: boolean,\n): T | null {\n if (isPrefLeaf(node)) return node.hidden && !showHidden ? null : node;\n const group = node as PrefGroup;\n const children: Record<string, PrefLeaf | PrefGroup> = {};\n for (const [key, child] of Object.entries(group.children)) {\n const kept = visiblePrefSubtree(child, showHidden);\n if (kept) children[key] = kept;\n }\n if (Object.keys(children).length === 0) return null;\n return { ...node, children };\n}\n","/* Ported from WeaselDraw's wd-prefs-* rules (apps/draw/src/app.css),\n * generalized onto theme tokens. Column width is overridable via\n * --wzl-prefs-column-width at any ancestor scope. */\n\n.columns {\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n align-items: flex-start;\n gap: 12px;\n overflow-y: auto;\n padding-right: 4px;\n}\n\n.column {\n flex: 0 0 var(--wzl-prefs-column-width, 300px);\n width: var(--wzl-prefs-column-width, 300px);\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.panel {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 10px;\n background: var(--wzl-surface-sunken);\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n}\n\n.subpanel {\n display: flex;\n flex-direction: column;\n gap: 6px;\n margin-top: 6px;\n padding: 8px 8px 8px 10px;\n background: var(--wzl-surface-sunken);\n border-left: 2px solid var(--wzl-border);\n border-radius: 2px;\n}\n\n.groupHeader {\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding-bottom: 4px;\n border-bottom: 1px solid var(--wzl-border);\n}\n\n.subpanel .groupHeader {\n border-bottom: none;\n padding-bottom: 2px;\n}\n\n.groupTitle {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--wzl-fg);\n letter-spacing: 0.04em;\n text-transform: uppercase;\n}\n\n.subpanel .groupTitle {\n font-size: 11px;\n text-transform: none;\n letter-spacing: 0;\n}\n\n.groupDesc {\n margin: 0;\n font-size: 11px;\n color: var(--wzl-fg-subtle);\n line-height: 1.35;\n}\n\n.rows {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n font-size: 12px;\n color: var(--wzl-fg);\n cursor: default;\n}\n\n.rowLabel {\n flex: 1 1 auto;\n min-width: 0;\n display: inline-flex;\n align-items: center;\n gap: 4px;\n}\n\n/* Description affordance — quiet until hovered/focused. */\n.help {\n flex: 0 0 auto;\n padding: 0;\n border: none;\n background: none;\n color: var(--wzl-fg-subtle);\n font-size: 11px;\n line-height: 1;\n cursor: help;\n border-radius: var(--wzl-radius-sm);\n}\n\n.help:hover {\n color: var(--wzl-fg);\n}\n\n.help:focus-visible {\n outline: 2px solid var(--wzl-focus-ring);\n outline-offset: 1px;\n color: var(--wzl-fg);\n}\n\n/* Control slot — fixed width so the label can take the rest of the row.\n * Kit form controls render as block-level flex columns that fill it. */\n.rowControl {\n flex: 0 0 110px;\n min-width: 0;\n display: flex;\n align-items: center;\n}\n\n.textarea {\n width: 100%;\n padding: 4px 8px;\n background: var(--wzl-input-bg);\n color: var(--wzl-fg);\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n font: inherit;\n font-size: 12px;\n resize: vertical;\n}\n\n.unrenderable {\n font-family: var(--wzl-font-mono);\n font-size: 11px;\n color: var(--wzl-fg-subtle);\n}\n\n/* PrefsDialog header row: title text + optional headerExtra chrome. */\n.titleRow {\n display: flex;\n align-items: center;\n gap: 12px;\n flex: 1;\n min-width: 0;\n}\n\n.titleRow > span:first-child {\n flex: 1;\n min-width: 0;\n}\n","import { useMemo, type ReactNode } from 'react';\nimport { Focusable } from 'react-aria-components';\nimport { Checkbox } from '../Checkbox';\nimport { ColorField } from '../ColorField';\nimport { Input } from '../Input';\nimport { NumberField } from '../NumberField';\nimport { RadioGroup, Radio } from '../RadioGroup';\nimport { RangeSlider } from '../RangeSlider';\nimport { Select } from '../Select';\nimport { Switch } from '../Switch';\nimport { Tooltip, TooltipTrigger } from '../Tooltip';\nimport {\n isPrefLeaf,\n prefValueAtPath,\n visiblePrefSubtree,\n type PrefBoolean,\n type PrefColor,\n type PrefEnum,\n type PrefGroup,\n type PrefLeaf,\n type PrefNumber,\n type PrefString,\n} from './schema';\nimport s from './Prefs.module.css';\n\nexport interface PrefRenderContext {\n /** Dotted path of the leaf within the schema root. */\n path: string;\n /** The schema node. App renderers narrow this to their own kind shape. */\n pref: PrefLeaf;\n /** Current value — `values` at `path`, falling back to `pref.default`. */\n value: unknown;\n setValue: (value: unknown) => void;\n}\n\nexport type PrefRenderer = (ctx: PrefRenderContext) => ReactNode;\n\nexport interface PrefsFormProps {\n /** Root of the schema tree. Core `ToolPrefGroup`s assign structurally. */\n schema: PrefGroup;\n /** Nested value tree (shape mirrors the schema). Sparse is fine —\n * missing leaves fall back to their schema `default`. */\n values?: unknown;\n /** Change callback with the leaf's dotted path. Values apply live;\n * there is no dirty/commit state. */\n onChange: (path: string, value: unknown) => void;\n /**\n * Per-kind renderers for app-defined kinds. Entries also override the\n * built-in kinds when keys collide. A renderer owns the control cell\n * (the label/tooltip row chrome stays with the form, unless the leaf\n * sets `block`); returning `null` collapses the row entirely. Unknown\n * kinds with no renderer show a labeled placeholder instead of\n * crashing.\n */\n renderers?: Record<string, PrefRenderer>;\n /** Reveal `hidden` leaves (dev tooling). Default false. */\n showHidden?: boolean;\n className?: string;\n}\n\n/**\n * Schema-driven preferences form. Top-level groups render as columns,\n * nested groups as indented sub-panels, leaves as label + control rows.\n * Storage-agnostic: pair with `PrefsDialog` for the modal composition,\n * and persist however the app likes via `onChange`.\n */\nexport function PrefsForm(props: PrefsFormProps) {\n const { schema, values, onChange, renderers, showHidden = false, className } = props;\n const visibleRoot = useMemo(\n () => visiblePrefSubtree(schema, showHidden),\n [schema, showHidden],\n );\n const ctx: WalkCtx = { values, onChange, renderers };\n return (\n <div className={[s.columns, className].filter(Boolean).join(' ')}>\n {Object.entries(visibleRoot?.children ?? {}).map(([key, child]) => (\n <div key={key} className={s.column}>\n {isPrefLeaf(child) ? (\n // Top-level leaves are unusual but legal — give each its own\n // column for symmetry with grouped siblings.\n <PrefRow ctx={ctx} path={key} pref={child} />\n ) : (\n <GroupBody ctx={ctx} group={child} path={key} depth={0} />\n )}\n </div>\n ))}\n </div>\n );\n}\n\ninterface WalkCtx {\n values: unknown;\n onChange: (path: string, value: unknown) => void;\n renderers?: Record<string, PrefRenderer>;\n}\n\nfunction GroupBody({ ctx, group, path, depth }: {\n ctx: WalkCtx;\n group: PrefGroup;\n path: string;\n depth: number;\n}) {\n return (\n <div className={depth === 0 ? s.panel : s.subpanel}>\n <div className={s.groupHeader}>\n <h3 className={s.groupTitle}>{group.name}</h3>\n {group.description !== undefined && (\n <p className={s.groupDesc}>{group.description}</p>\n )}\n </div>\n <div className={s.rows}>\n {Object.entries(group.children).map(([key, child]) => {\n const childPath = `${path}.${key}`;\n return isPrefLeaf(child) ? (\n <PrefRow key={key} ctx={ctx} path={childPath} pref={child} />\n ) : (\n <GroupBody key={key} ctx={ctx} group={child} path={childPath} depth={depth + 1} />\n );\n })}\n </div>\n </div>\n );\n}\n\nfunction PrefRow({ ctx, path, pref }: { ctx: WalkCtx; path: string; pref: PrefLeaf }) {\n const stored = prefValueAtPath(ctx.values, path);\n const renderCtx: PrefRenderContext = {\n path,\n pref,\n value: stored !== undefined ? stored : pref.default,\n setValue: (v) => ctx.onChange(path, v),\n };\n\n const custom = ctx.renderers?.[pref.kind];\n const control = custom ? custom(renderCtx) : renderBuiltin(renderCtx);\n if (custom && control === null) return null;\n\n // `block` leaves own their chrome (embedded editors with their own\n // header) — no label/tooltip row.\n if (pref.block) return <>{control}</>;\n\n return (\n <label className={s.row}>\n <span className={s.rowLabel}>\n {pref.name}\n {pref.description ? (\n // Help affordance carries the description tooltip. A tooltip\n // trigger must be interactive (keyboard-reachable), so this is\n // a real button — a bare label span would be neither focusable\n // nor announced.\n <TooltipTrigger>\n <Focusable>\n <button type=\"button\" className={s.help} aria-label={`About ${pref.name}`}>\n ⓘ\n </button>\n </Focusable>\n <Tooltip>{pref.description}</Tooltip>\n </TooltipTrigger>\n ) : null}\n </span>\n <span className={s.rowControl}>{control}</span>\n </label>\n );\n}\n\nfunction renderBuiltin(ctx: PrefRenderContext): ReactNode {\n const { pref, value, setValue } = ctx;\n switch (pref.kind) {\n case 'boolean': {\n const p = pref as PrefBoolean;\n const checked = Boolean(value);\n return p.control === 'switch' ? (\n <Switch isSelected={checked} onChange={setValue} aria-label={p.name} />\n ) : (\n <Checkbox isSelected={checked} onChange={setValue} aria-label={p.name} />\n );\n }\n case 'number': {\n const p = pref as PrefNumber;\n const n = typeof value === 'number' && Number.isFinite(value) ? value : 0;\n if (p.control === 'slider') {\n return (\n <RangeSlider\n value={n}\n onChange={(v) => setValue(typeof v === 'number' ? v : v[0])}\n minValue={p.min}\n maxValue={p.max}\n step={p.step ?? 1}\n aria-label={p.name}\n />\n );\n }\n return (\n <NumberField\n value={n}\n onChange={setValue}\n minValue={p.min}\n maxValue={p.max}\n step={p.step ?? 1}\n aria-label={p.name}\n />\n );\n }\n case 'string': {\n const p = pref as PrefString;\n const text = typeof value === 'string' ? value : '';\n if (p.control === 'textarea') {\n return (\n <textarea\n className={s.textarea}\n value={text}\n onChange={(e) => setValue(e.target.value)}\n aria-label={p.name}\n rows={3}\n />\n );\n }\n return <Input value={text} onChange={setValue} aria-label={p.name} />;\n }\n case 'enum': {\n const p = pref as PrefEnum;\n const selected = typeof value === 'string' ? value : p.default;\n if (p.control === 'radio') {\n return (\n <RadioGroup\n value={selected}\n onChange={(v) => setValue(v)}\n aria-label={p.name}\n >\n {p.options.map((o) => (\n <Radio key={o.value} value={o.value}>{o.label}</Radio>\n ))}\n </RadioGroup>\n );\n }\n return (\n <Select<string>\n options={p.options.map((o) => ({ value: o.value, label: o.label }))}\n selectedKey={selected}\n onSelectionChange={(v) => setValue(v)}\n aria-label={p.name}\n />\n );\n }\n case 'color': {\n const p = pref as PrefColor;\n const hex = typeof value === 'string' ? value : p.default;\n return (\n <ColorField\n value={hex}\n alpha={p.alpha}\n onChange={setValue}\n aria-label={p.name}\n />\n );\n }\n default:\n // Unknown kind with no renderer entry: labeled placeholder, not a\n // crash — a missing wiring should be visible and recoverable.\n return <span className={s.unrenderable}>({pref.kind}: no renderer)</span>;\n }\n}\n","import type { ReactNode } from 'react';\nimport { Dialog } from '../Dialog';\nimport { PrefsForm, type PrefsFormProps } from './PrefsForm';\nimport s from './Prefs.module.css';\n\nexport interface PrefsDialogProps extends PrefsFormProps {\n isOpen: boolean;\n onOpenChange: (open: boolean) => void;\n /** Dialog heading. Defaults to the schema root's `name`. */\n title?: ReactNode;\n /** Extra chrome rendered inline with the title (e.g. a dev-mode\n * \"Show hidden\" switch). */\n headerExtra?: ReactNode;\n /** Class applied to the dialog's modal box. */\n dialogClassName?: string;\n}\n\n/**\n * The kit's preferences dialog: `Dialog` + `PrefsForm`. Changes apply\n * live through `onChange` — there is no OK/Cancel staging. For custom\n * placement (sidebar, popover, inline page), compose `PrefsForm`\n * directly; this wrapper is intentionally thin.\n */\nexport function PrefsDialog(props: PrefsDialogProps) {\n const { isOpen, onOpenChange, title, headerExtra, dialogClassName, ...form } = props;\n return (\n <Dialog\n isOpen={isOpen}\n onOpenChange={onOpenChange}\n aria-label={typeof title === 'string' ? title : form.schema.name}\n className={dialogClassName}\n title={\n headerExtra !== undefined ? (\n <span className={s.titleRow}>\n <span>{title ?? form.schema.name}</span>\n {headerExtra}\n </span>\n ) : (\n title ?? form.schema.name\n )\n }\n >\n <PrefsForm {...form} />\n </Dialog>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"RadioGroup-Ci-I9NEx.js","names":[],"sources":["../../src/components/RadioGroup/RadioGroup.module.css","../../src/components/RadioGroup/RadioGroup.tsx"],"sourcesContent":[".group {\n display: flex;\n flex-direction: column;\n gap: 6px;\n font-family: var(--wzl-font-ui);\n color: var(--wzl-fg);\n}\n\n.group[data-orientation=\"horizontal\"] {\n flex-direction: row;\n gap: 16px;\n}\n\n.legend {\n font-size: 12px;\n font-weight: 500;\n color: var(--wzl-fg);\n}\n\n.radio {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n cursor: pointer;\n font-size: 13px;\n color: var(--wzl-fg);\n user-select: none;\n -webkit-user-select: none;\n}\n\n.radio[data-disabled] {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.dot {\n position: relative;\n flex: 0 0 auto;\n width: 14px;\n height: 14px;\n background: var(--wzl-input-bg);\n border: 1px solid var(--wzl-border-strong);\n border-radius: 50%;\n transition: border-color var(--wzl-motion-fast), box-shadow var(--wzl-motion-fast);\n}\n\n.radio[data-focus-visible] .dot {\n box-shadow: 0 0 0 2px var(--wzl-focus-ring);\n}\n\n.radio[data-hovered]:not([data-disabled]) .dot {\n border-color: var(--wzl-fg-muted);\n}\n\n.radio[data-selected] .dot {\n border-color: var(--wzl-accent-strong);\n}\n\n.dot::after {\n content: '';\n position: absolute;\n inset: 3px;\n border-radius: 50%;\n background: var(--wzl-accent-strong);\n transform: scale(0);\n transition: transform var(--wzl-motion-fast);\n}\n\n.radio[data-selected] .dot::after {\n transform: scale(1);\n}\n","import type { ReactNode } from 'react';\nimport {\n RadioGroup as RACRadioGroup,\n Radio as RACRadio,\n Label,\n Text,\n FieldError,\n type RadioGroupProps as RACRadioGroupProps,\n type RadioProps as RACRadioProps,\n type ValidationResult,\n} from 'react-aria-components';\nimport { fieldClasses } from '../Field/Field';\nimport s from './RadioGroup.module.css';\n\nexport type RadioGroupProps = Omit<RACRadioGroupProps, 'children' | 'className'> & {\n label?: ReactNode;\n description?: ReactNode;\n errorMessage?: ReactNode | ((v: ValidationResult) => ReactNode);\n children?: ReactNode;\n className?: string;\n};\n\nexport function RadioGroup(props: RadioGroupProps) {\n const { label, description, errorMessage, children, className, ...rest } = props;\n return (\n <RACRadioGroup\n {...rest}\n className={[s.group, fieldClasses.root, className].filter(Boolean).join(' ')}\n >\n {label !== undefined && <Label className={s.legend}>{label}</Label>}\n {children}\n {description !== undefined && (\n <Text slot=\"description\" className={fieldClasses.hint}>\n {description}\n </Text>\n )}\n <FieldError className={fieldClasses.error}>{errorMessage}</FieldError>\n </RACRadioGroup>\n );\n}\n\nexport type RadioProps = Omit<RACRadioProps, 'className' | 'children'> & {\n children?: ReactNode;\n className?: string;\n};\n\nexport function Radio({ children, className, ...rest }: RadioProps) {\n return (\n <RACRadio {...rest} className={[s.radio, className].filter(Boolean).join(' ')}>\n <span className={s.dot} />\n {children !== undefined && <span>{children}</span>}\n </RACRadio>\n );\n}\n"],"mappings":";;;;;;;;;;;ACsBA,SAAgB,EAAW,GAAwB;CACjD,IAAM,EAAE,UAAO,gBAAa,iBAAc,aAAU,cAAW,GAAG,MAAS;AAC3E,QACE,kBAAC,GAAD;EACE,GAAI;EACJ,WAAW;GAAC,EAAE;GAAO,EAAa;GAAM;GAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;YAF9E;GAIG,MAAU,KAAA,KAAa,kBAAC,GAAD;IAAO,WAAW,EAAE;cAAS;IAAc,CAAA;GAClE;GACA,MAAgB,KAAA,KACf,kBAAC,GAAD;IAAM,MAAK;IAAc,WAAW,EAAa;cAC9C;IACI,CAAA;GAET,kBAAC,GAAD;IAAY,WAAW,EAAa;cAAQ;IAA0B,CAAA;GACxD;;;AASpB,SAAgB,EAAM,EAAE,aAAU,cAAW,GAAG,KAAoB;AAClE,QACE,kBAAC,GAAD;EAAU,GAAI;EAAM,WAAW,CAAC,EAAE,OAAO,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;YAA7E,CACE,kBAAC,QAAD,EAAM,WAAW,EAAE,KAAO,CAAA,EACzB,MAAa,KAAA,KAAa,kBAAC,QAAD,EAAO,aAAgB,CAAA,CACzC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"RangeSlider-CW-edtNx.js","names":[],"sources":["../../src/components/RangeSlider/RangeSlider.module.css","../../src/components/RangeSlider/RangeSlider.tsx"],"sourcesContent":[".slider {\n display: flex;\n flex-direction: column;\n width: 100%;\n gap: 4px;\n font-family: var(--wzl-font-ui);\n color: var(--wzl-fg);\n}\n\n.slider[data-orientation=\"vertical\"] {\n flex-direction: row;\n align-items: stretch;\n width: 28px;\n height: 160px;\n}\n\n.head {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 8px;\n}\n\n.label {\n font-size: 12px;\n font-weight: 500;\n color: var(--wzl-fg);\n}\n\n.output {\n font-size: 12px;\n color: var(--wzl-fg-subtle);\n font-variant-numeric: tabular-nums;\n}\n\n.track {\n position: relative;\n height: 6px;\n min-width: 80px;\n background: var(--wzl-track-bg);\n border: 1px solid var(--wzl-track-border);\n border-radius: 3px;\n}\n\n.slider[data-orientation=\"vertical\"] .track {\n width: 6px;\n height: 100%;\n}\n\n.fill {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n background: var(--wzl-accent-strong);\n border-radius: 3px;\n}\n\n.thumb {\n width: 14px;\n height: 14px;\n background: var(--wzl-thumb-fill);\n border: 1px solid var(--wzl-thumb-border);\n border-radius: 50%;\n cursor: ew-resize;\n outline: none;\n transition: box-shadow var(--wzl-motion-fast);\n}\n\n.slider[data-orientation=\"vertical\"] .thumb {\n cursor: ns-resize;\n}\n\n.thumb[data-focus-visible] {\n box-shadow: 0 0 0 2px var(--wzl-focus-ring);\n}\n\n.thumb[data-dragging] {\n background: var(--wzl-accent-strong);\n}\n\n.slider[data-disabled] {\n opacity: 0.5;\n cursor: not-allowed;\n}\n","import type { ReactNode } from 'react';\nimport {\n Slider as RACSlider,\n SliderOutput,\n SliderTrack,\n SliderThumb,\n Label,\n type SliderProps as RACSliderProps,\n} from 'react-aria-components';\nimport s from './RangeSlider.module.css';\n\nexport type RangeSliderProps = Omit<RACSliderProps, 'children' | 'className'> & {\n label?: ReactNode;\n /** Show the current value next to the label. Defaults to true when a\n * label is supplied. */\n showOutput?: boolean;\n /** Format the rendered value(s). Useful for units (`v => `${v}%``). */\n formatOutput?: (v: number | number[]) => ReactNode;\n className?: string;\n};\n\n/**\n * Linear range slider on React Aria. Single-value by default; pass an array\n * to `value` / `defaultValue` for multi-thumb. Distinct from the kit's\n * canvas-scrub `Slider`, which is shaped for tool-canvas interactions.\n */\nexport function RangeSlider(props: RangeSliderProps) {\n const { label, showOutput, formatOutput, className, ...rest } = props;\n const wantOutput = showOutput ?? label !== undefined;\n return (\n <RACSlider {...rest} className={[s.slider, className].filter(Boolean).join(' ')}>\n {(label !== undefined || wantOutput) && (\n <div className={s.head}>\n {label !== undefined && <Label className={s.label}>{label}</Label>}\n {wantOutput && (\n <SliderOutput className={s.output}>\n {({ state }) => {\n const v = state.values.length === 1 ? state.values[0] : state.values;\n return formatOutput ? formatOutput(v) : Array.isArray(v) ? v.join(' – ') : String(v);\n }}\n </SliderOutput>\n )}\n </div>\n )}\n <SliderTrack className={s.track}>\n {({ state }) => {\n // Single-thumb: fill from start to thumb. Multi-thumb: fill\n // between adjacent thumbs (simplest = between first and last).\n const fillStart = state.values.length > 1 ? state.getThumbPercent(0) * 100 : 0;\n const fillEnd = state.getThumbPercent(state.values.length - 1) * 100;\n return (\n <>\n <div\n className={s.fill}\n style={{ left: `${fillStart}%`, right: `${100 - fillEnd}%` }}\n />\n {state.values.map((_, i) => (\n <SliderThumb key={i} index={i} className={s.thumb} />\n ))}\n </>\n );\n }}\n </SliderTrack>\n </RACSlider>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AC0BA,SAAgB,EAAY,GAAyB;CACnD,IAAM,EAAE,UAAO,eAAY,iBAAc,cAAW,GAAG,MAAS,GAC1D,IAAa,KAAc,MAAU,KAAA;AAC3C,QACE,kBAAC,GAAD;EAAW,GAAI;EAAM,WAAW,CAAC,EAAE,QAAQ,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;YAA/E,EACI,MAAU,KAAA,KAAa,MACvB,kBAAC,OAAD;GAAK,WAAW,EAAE;aAAlB,CACG,MAAU,KAAA,KAAa,kBAAC,GAAD;IAAO,WAAW,EAAE;cAAQ;IAAc,CAAA,EACjE,KACC,kBAAC,GAAD;IAAc,WAAW,EAAE;eACvB,EAAE,eAAY;KACd,IAAM,IAAI,EAAM,OAAO,WAAW,IAAI,EAAM,OAAO,KAAK,EAAM;AAC9D,YAAO,IAAe,EAAa,EAAE,GAAG,MAAM,QAAQ,EAAE,GAAG,EAAE,KAAK,MAAM,GAAG,OAAO,EAAE;;IAEzE,CAAA,CAEb;MAER,kBAAC,GAAD;GAAa,WAAW,EAAE;cACtB,EAAE,eAAY;IAGd,IAAM,IAAY,EAAM,OAAO,SAAS,IAAI,EAAM,gBAAgB,EAAE,GAAG,MAAM,GACvE,IAAU,EAAM,gBAAgB,EAAM,OAAO,SAAS,EAAE,GAAG;AACjE,WACE,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,OAAD;KACE,WAAW,EAAE;KACb,OAAO;MAAE,MAAM,GAAG,EAAU;MAAI,OAAO,GAAG,MAAM,EAAQ;MAAI;KAC5D,CAAA,EACD,EAAM,OAAO,KAAK,GAAG,MACpB,kBAAC,GAAD;KAAqB,OAAO;KAAG,WAAW,EAAE;KAAS,EAAnC,EAAmC,CACrD,CACD,EAAA,CAAA;;GAGK,CAAA,CACJ"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ResizeHandle-C2OBMqTs.js","names":[],"sources":["../../src/components/ResizeHandle/ResizeHandle.module.css","../../src/components/ResizeHandle/ResizeHandle.tsx"],"sourcesContent":[".handle {\n flex-shrink: 0;\n width: 4px;\n align-self: stretch;\n cursor: ew-resize;\n background: transparent;\n border-left: 1px solid var(--wzl-panel-border, var(--wzl-border));\n /* The bar is 4px of hit area — thin enough to read as a seam, wide\n enough to grab. `touch-action: none` keeps a touch drag from being\n stolen by the scroll container it usually sits inside. */\n touch-action: none;\n}\n\n.handle.horizontal {\n width: auto;\n height: 4px;\n align-self: auto;\n cursor: ns-resize;\n border-left: none;\n border-top: 1px solid var(--wzl-panel-border, var(--wzl-border));\n}\n\n.handle:hover {\n background: var(--wzl-border);\n}\n\n.handle:focus-visible {\n outline: 2px solid var(--wzl-focus-ring, var(--wzl-accent));\n outline-offset: -1px;\n}\n","import { useCallback, useRef, type KeyboardEvent, type PointerEvent } from 'react';\nimport s from './ResizeHandle.module.css';\n\nexport interface ResizeHandleProps {\n /**\n * Which way the handle *runs*, matching `aria-orientation` on the\n * separator role: a `vertical` handle is a vertical bar between two\n * side-by-side panes and drags horizontally. `horizontal` is the\n * stacked case (a bar between rows, dragged up and down).\n */\n orientation?: 'vertical' | 'horizontal';\n /** Current size of the pane being resized, in pixels. */\n value: number;\n min: number;\n max: number;\n /**\n * Flip the sign of the drag. Panes docked to the *trailing* edge (a\n * right sidebar, a bottom drawer) grow as the pointer moves toward the\n * start of the axis, so they want `invert`. Leading-edge panes don't.\n */\n invert?: boolean;\n /**\n * Fires continuously while dragging and on each keyboard step, always\n * clamped to [min, max]. Drive the live size from this.\n */\n onChange(next: number): void;\n /**\n * Fires once when a drag ends or a keyboard step settles, with the final\n * size. Persist here rather than in `onChange` — one write per gesture\n * instead of one per pointer sample.\n */\n onChangeEnd?(next: number): void;\n /**\n * The granularity of `value`. Arrow keys move by one step (Shift by\n * eight), and pointer drags snap to the same grid — a pointer reports\n * fractional coordinates on a scaled display, and nobody wants\n * `362.9453125` in their persisted layout. Set a fractional step for a\n * pane measured in something finer than whole pixels.\n */\n step?: number;\n ariaLabel?: string;\n className?: string;\n}\n\nconst clamp = (n: number, min: number, max: number): number => Math.min(max, Math.max(min, n));\n\n/**\n * Snap to the step grid, then clamp. Bounds win over the grid: `min`/`max`\n * are hard limits, so a range that isn't a whole number of steps clips to\n * the bound rather than overshooting it.\n */\nfunction settle(n: number, min: number, max: number, step: number): number {\n const snapped = step > 0 ? Math.round(n / step) * step : n;\n // Re-round to kill the float dust `x / 0.1 * 0.1` leaves behind.\n const precise = Number.isInteger(step) ? snapped : Number(snapped.toFixed(10));\n return clamp(precise, min, max);\n}\n\n/**\n * Draggable divider for resizing a pane — the \"window splitter\" pattern.\n * Owns no size of its own: the consumer holds `value` and applies it\n * (typically as a CSS custom property on the pane), which keeps the handle\n * usable for flex, grid, and absolutely-positioned layouts alike.\n *\n * Keyboard-operable as a matter of course: it's focusable, arrows step by\n * `step` (Shift for a coarse step), Home/End jump to the bounds. A splitter\n * that only responds to a pointer drag strands anyone who can't make one.\n */\nexport function ResizeHandle(props: ResizeHandleProps) {\n const {\n orientation = 'vertical', value, min, max, invert, onChange, onChangeEnd,\n step = 1, ariaLabel, className,\n } = props;\n\n // Drag origin. Captured on pointerdown so every move is measured against\n // the gesture's start rather than the previous sample — accumulating\n // per-sample deltas drifts once the pointer leaves the clamped range.\n const drag = useRef<{ origin: number; startValue: number } | null>(null);\n\n const axisOf = useCallback(\n (e: PointerEvent<HTMLDivElement>) => (orientation === 'vertical' ? e.clientX : e.clientY),\n [orientation],\n );\n\n const onPointerDown = useCallback((e: PointerEvent<HTMLDivElement>) => {\n if (e.button !== 0) return;\n drag.current = { origin: axisOf(e), startValue: value };\n // jsdom has no pointer capture; the guard keeps tests on the real path.\n e.currentTarget.setPointerCapture?.(e.pointerId);\n e.preventDefault();\n }, [axisOf, value]);\n\n const onPointerMove = useCallback((e: PointerEvent<HTMLDivElement>) => {\n const d = drag.current;\n if (!d) return;\n const delta = axisOf(e) - d.origin;\n onChange(settle(d.startValue + (invert ? -delta : delta), min, max, step));\n }, [axisOf, invert, max, min, onChange, step]);\n\n const endDrag = useCallback((e: PointerEvent<HTMLDivElement>) => {\n const d = drag.current;\n if (!d) return;\n drag.current = null;\n e.currentTarget.releasePointerCapture?.(e.pointerId);\n const delta = axisOf(e) - d.origin;\n onChangeEnd?.(settle(d.startValue + (invert ? -delta : delta), min, max, step));\n }, [axisOf, invert, max, min, onChangeEnd, step]);\n\n const onKeyDown = useCallback((e: KeyboardEvent<HTMLDivElement>) => {\n const grow = orientation === 'vertical' ? 'ArrowRight' : 'ArrowDown';\n const shrink = orientation === 'vertical' ? 'ArrowLeft' : 'ArrowUp';\n const amount = step * (e.shiftKey ? 8 : 1) * (invert ? -1 : 1);\n let next: number | undefined;\n if (e.key === grow) next = value + amount;\n else if (e.key === shrink) next = value - amount;\n else if (e.key === 'Home') next = invert ? max : min;\n else if (e.key === 'End') next = invert ? min : max;\n if (next === undefined) return;\n e.preventDefault();\n const settled = settle(next, min, max, step);\n onChange(settled);\n onChangeEnd?.(settled);\n }, [invert, max, min, onChange, onChangeEnd, orientation, step, value]);\n\n const cls = [s.handle, orientation === 'horizontal' && s.horizontal, className]\n .filter(Boolean).join(' ');\n\n return (\n <div\n className={cls}\n role=\"separator\"\n tabIndex={0}\n aria-label={ariaLabel}\n aria-orientation={orientation}\n aria-valuenow={value}\n aria-valuemin={min}\n aria-valuemax={max}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={endDrag}\n onPointerCancel={endDrag}\n onKeyDown={onKeyDown}\n />\n );\n}\n"],"mappings":";;;;;GC4CM,KAAS,GAAW,GAAa,MAAwB,KAAK,IAAI,GAAK,KAAK,IAAI,GAAK,EAAE,CAAC;AAO9F,SAAS,EAAO,GAAW,GAAa,GAAa,GAAsB;CACzE,IAAM,IAAU,IAAO,IAAI,KAAK,MAAM,IAAI,EAAK,GAAG,IAAO;AAGzD,QAAO,EADS,OAAO,UAAU,EAAK,GAAG,IAAU,OAAO,EAAQ,QAAQ,GAAG,CAAC,EACxD,GAAK,EAAI;;AAajC,SAAgB,EAAa,GAA0B;CACrD,IAAM,EACJ,iBAAc,YAAY,UAAO,QAAK,QAAK,WAAQ,aAAU,gBAC7D,UAAO,GAAG,cAAW,iBACnB,GAKE,IAAO,EAAsD,KAAK,EAElE,IAAS,GACZ,MAAqC,MAAgB,aAAa,EAAE,UAAU,EAAE,SACjF,CAAC,EAAY,CACd,EAEK,IAAgB,GAAa,MAAoC;AACjE,IAAE,WAAW,MACjB,EAAK,UAAU;GAAE,QAAQ,EAAO,EAAE;GAAE,YAAY;GAAO,EAEvD,EAAE,cAAc,oBAAoB,EAAE,UAAU,EAChD,EAAE,gBAAgB;IACjB,CAAC,GAAQ,EAAM,CAAC,EAEb,IAAgB,GAAa,MAAoC;EACrE,IAAM,IAAI,EAAK;AACf,MAAI,CAAC,EAAG;EACR,IAAM,IAAQ,EAAO,EAAE,GAAG,EAAE;AAC5B,IAAS,EAAO,EAAE,cAAc,IAAS,CAAC,IAAQ,IAAQ,GAAK,GAAK,EAAK,CAAC;IACzE;EAAC;EAAQ;EAAQ;EAAK;EAAK;EAAU;EAAK,CAAC,EAExC,IAAU,GAAa,MAAoC;EAC/D,IAAM,IAAI,EAAK;AACf,MAAI,CAAC,EAAG;AAER,EADA,EAAK,UAAU,MACf,EAAE,cAAc,wBAAwB,EAAE,UAAU;EACpD,IAAM,IAAQ,EAAO,EAAE,GAAG,EAAE;AAC5B,MAAc,EAAO,EAAE,cAAc,IAAS,CAAC,IAAQ,IAAQ,GAAK,GAAK,EAAK,CAAC;IAC9E;EAAC;EAAQ;EAAQ;EAAK;EAAK;EAAa;EAAK,CAAC,EAE3C,IAAY,GAAa,MAAqC;EAClE,IAAM,IAAO,MAAgB,aAAa,eAAe,aACnD,IAAS,MAAgB,aAAa,cAAc,WACpD,IAAS,KAAQ,EAAE,WAAW,IAAI,MAAM,IAAS,KAAK,IACxD;AAKJ,MAJI,EAAE,QAAQ,IAAM,IAAO,IAAQ,IAC1B,EAAE,QAAQ,IAAQ,IAAO,IAAQ,IACjC,EAAE,QAAQ,SAAQ,IAAO,IAAS,IAAM,IACxC,EAAE,QAAQ,UAAO,IAAO,IAAS,IAAM,IAC5C,MAAS,KAAA,EAAW;AACxB,IAAE,gBAAgB;EAClB,IAAM,IAAU,EAAO,GAAM,GAAK,GAAK,EAAK;AAE5C,EADA,EAAS,EAAQ,EACjB,IAAc,EAAQ;IACrB;EAAC;EAAQ;EAAK;EAAK;EAAU;EAAa;EAAa;EAAM;EAAM,CAAC;AAKvE,QACE,kBAAC,OAAD;EACE,WALQ;GAAC,EAAE;GAAQ,MAAgB,gBAAgB,EAAE;GAAY;GAAU,CAC5E,OAAO,QAAQ,CAAC,KAAK,IAIT;EACX,MAAK;EACL,UAAU;EACV,cAAY;EACZ,oBAAkB;EAClB,iBAAe;EACf,iBAAe;EACf,iBAAe;EACA;EACA;EACf,aAAa;EACb,iBAAiB;EACN;EACX,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Select-CLyBisCS.js","names":[],"sources":["../../src/components/Select/Select.module.css","../../src/components/Select/Select.tsx"],"sourcesContent":[".field {\n display: flex;\n flex-direction: column;\n width: 100%;\n min-width: 0;\n gap: 4px;\n}\n\n.trigger {\n display: flex;\n width: 100%;\n align-items: center;\n gap: 6px;\n height: 24px;\n padding: 0 6px 0 10px;\n background: var(--wzl-input-bg);\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n color: var(--wzl-fg);\n font-family: var(--wzl-font-ui);\n font-size: 13px;\n line-height: 1;\n cursor: pointer;\n transition: border-color var(--wzl-motion-fast), box-shadow var(--wzl-motion-fast);\n}\n\n.trigger[data-focus-visible] {\n border-color: var(--wzl-focus-ring);\n box-shadow: 0 0 0 1px var(--wzl-focus-ring);\n}\n\n.field[data-invalid] .trigger {\n border-color: var(--wzl-danger, #ff5b5b);\n}\n\n.trigger[data-disabled] {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.value {\n flex: 1;\n min-width: 0;\n text-align: left;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.value[data-placeholder] {\n color: var(--wzl-fg-subtle);\n}\n\n.chevron {\n width: 10px;\n height: 10px;\n color: var(--wzl-fg-muted);\n flex: 0 0 auto;\n}\n\n.popover {\n min-width: var(--trigger-width);\n max-height: inherit;\n outline: none;\n}\n\n.listbox {\n background: var(--wzl-surface-raised);\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-md);\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);\n padding: 4px;\n max-height: 280px;\n overflow: auto;\n outline: none;\n font-family: var(--wzl-font-ui);\n font-size: 13px;\n}\n\n.option {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 4px 8px;\n border-radius: 3px;\n cursor: pointer;\n color: var(--wzl-fg);\n outline: none;\n}\n\n.option[data-focused] {\n background: color-mix(in srgb, var(--wzl-fg) 10%, transparent);\n}\n\n.option[data-selected] {\n color: var(--wzl-fg);\n font-weight: 500;\n}\n\n.option[data-disabled] {\n opacity: 0.4;\n cursor: not-allowed;\n}\n\n.check {\n width: 10px;\n height: 10px;\n flex: 0 0 auto;\n opacity: 0;\n}\n\n.option[data-selected] .check {\n opacity: 1;\n}\n","import type { ReactNode } from 'react';\nimport {\n Select as RACSelect,\n Label,\n Button as RACButton,\n SelectValue,\n Popover as RACPopover,\n ListBox as RACListBox,\n ListBoxItem as RACListBoxItem,\n Text,\n FieldError,\n type SelectProps as RACSelectProps,\n type ListBoxItemProps as RACListBoxItemProps,\n type ValidationResult,\n} from 'react-aria-components';\nimport { fieldClasses } from '../Field/Field';\nimport s from './Select.module.css';\n\nexport type SelectOption = {\n value: string;\n label: ReactNode;\n isDisabled?: boolean;\n /**\n * Plain-text form of `label`, for type-to-select and screen readers.\n * Only needed when `label` isn't a bare string — a label built from\n * elements has no text React Aria can read off it. A string label\n * supplies this itself.\n */\n textValue?: string;\n};\n\ntype Key = string | number;\n\nexport type SelectProps<T extends Key = string> = Omit<RACSelectProps<object>, 'children' | 'className' | 'selectedKey' | 'defaultSelectedKey' | 'onSelectionChange'> & {\n label?: ReactNode;\n description?: ReactNode;\n errorMessage?: ReactNode | ((v: ValidationResult) => ReactNode);\n placeholder?: string;\n /** Either pass `options` for a quick render, or `children` for full control. */\n options?: ReadonlyArray<SelectOption & { value: T }>;\n children?: ReactNode;\n selectedKey?: T | null;\n defaultSelectedKey?: T;\n onSelectionChange?: (key: T) => void;\n className?: string;\n};\n\n/**\n * Form select wrapping React Aria's Select. Pass either `options` for a\n * quick declarative render or `children` of `<SelectItem>` for control over\n * each row.\n *\n * The selection key type is parameterized so consumers with a string-literal\n * union for `value` (e.g. `'r' | 'g' | 'b'`) get a typed `onSelectionChange`.\n */\nexport function Select<T extends Key = string>(props: SelectProps<T>) {\n const {\n label,\n description,\n errorMessage,\n placeholder,\n options,\n children,\n selectedKey,\n defaultSelectedKey,\n onSelectionChange,\n className,\n ...rest\n } = props;\n\n return (\n <RACSelect\n {...rest}\n selectedKey={selectedKey ?? undefined}\n defaultSelectedKey={defaultSelectedKey}\n onSelectionChange={onSelectionChange ? (k) => onSelectionChange(k as T) : undefined}\n className={[s.field, fieldClasses.root, className].filter(Boolean).join(' ')}\n >\n {label !== undefined && <Label className={fieldClasses.label}>{label}</Label>}\n <RACButton className={s.trigger}>\n <SelectValue className={s.value}>\n {({ defaultChildren, isPlaceholder }) =>\n isPlaceholder ? (placeholder ?? defaultChildren) : defaultChildren\n }\n </SelectValue>\n <svg className={s.chevron} viewBox=\"0 0 10 10\" aria-hidden=\"true\">\n <path d=\"M2 4 L5 7 L8 4\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n </RACButton>\n {description !== undefined && (\n <Text slot=\"description\" className={fieldClasses.hint}>\n {description}\n </Text>\n )}\n <FieldError className={fieldClasses.error}>{errorMessage}</FieldError>\n {/* `data-weasel-overlay` marks DOM that belongs to this control but\n renders in a portal, outside the subtree the trigger sits in. Any\n consumer reasoning about \"did focus leave my component?\" via\n `closest()` gets the wrong answer without it — a text editor whose\n font menu lives here would end its session the moment the menu is\n clicked. */}\n <RACPopover className={s.popover} data-weasel-overlay=\"\">\n <RACListBox className={s.listbox}>\n {options !== undefined\n ? options.map((o) => (\n <SelectItem\n key={String(o.value)}\n id={o.value}\n isDisabled={o.isDisabled}\n textValue={o.textValue}\n >\n {o.label}\n </SelectItem>\n ))\n : children}\n </RACListBox>\n </RACPopover>\n </RACSelect>\n );\n}\n\nexport type SelectItemProps = Omit<RACListBoxItemProps, 'className' | 'children'> & {\n children?: ReactNode;\n className?: string;\n};\n\n/**\n * Every row renders a check mark beside its label, so from React Aria's\n * side the children are never plain text and it can't derive the string\n * type-to-select and screen readers need — it warns once per row. A string\n * label already *is* that string, so derive it rather than making every\n * call site restate it; anything richer has to say what it reads as.\n */\nfunction textValueOf(children: ReactNode, explicit: string | undefined): string | undefined {\n if (explicit !== undefined) return explicit;\n return typeof children === 'string' || typeof children === 'number'\n ? String(children)\n : undefined;\n}\n\nexport function SelectItem({ children, className, textValue, ...rest }: SelectItemProps) {\n return (\n <RACListBoxItem\n {...rest}\n textValue={textValueOf(children, textValue)}\n className={[s.option, className].filter(Boolean).join(' ')}\n >\n <svg className={s.check} viewBox=\"0 0 10 10\" aria-hidden=\"true\">\n <polyline points=\"1.5,5 4,7.5 8.5,3\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n {children}\n </RACListBoxItem>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;ACuDA,SAAgB,EAA+B,GAAuB;CACpE,IAAM,EACJ,UACA,gBACA,iBACA,gBACA,YACA,aACA,gBACA,uBACA,sBACA,cACA,GAAG,MACD;AAEJ,QACE,kBAAC,GAAD;EACE,GAAI;EACJ,aAAa,KAAe,KAAA;EACR;EACpB,mBAAmB,KAAqB,MAAM,EAAkB,EAAO,GAAG,KAAA;EAC1E,WAAW;GAAC,EAAE;GAAO,EAAa;GAAM;GAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;YAL9E;GAOG,MAAU,KAAA,KAAa,kBAAC,GAAD;IAAO,WAAW,EAAa;cAAQ;IAAc,CAAA;GAC7E,kBAAC,GAAD;IAAW,WAAW,EAAE;cAAxB,CACE,kBAAC,GAAD;KAAa,WAAW,EAAE;gBACtB,EAAE,oBAAiB,uBACnB,IAAiB,KAAe,IAAmB;KAEzC,CAAA,EACd,kBAAC,OAAD;KAAK,WAAW,EAAE;KAAS,SAAQ;KAAY,eAAY;eACzD,kBAAC,QAAD;MAAM,GAAE;MAAiB,MAAK;MAAO,QAAO;MAAe,aAAY;MAAM,eAAc;MAAQ,gBAAe;MAAU,CAAA;KACxH,CAAA,CACI;;GACX,MAAgB,KAAA,KACf,kBAAC,GAAD;IAAM,MAAK;IAAc,WAAW,EAAa;cAC9C;IACI,CAAA;GAET,kBAAC,GAAD;IAAY,WAAW,EAAa;cAAQ;IAA0B,CAAA;GAOtE,kBAAC,GAAD;IAAY,WAAW,EAAE;IAAS,uBAAoB;cACpD,kBAAC,GAAD;KAAY,WAAW,EAAE;eACtB,MAAY,KAAA,IAWT,IAVA,EAAQ,KAAK,MACX,kBAAC,GAAD;MAEE,IAAI,EAAE;MACN,YAAY,EAAE;MACd,WAAW,EAAE;gBAEZ,EAAE;MACQ,EANN,OAAO,EAAE,MAAM,CAMT,CACb;KAEK,CAAA;IACF,CAAA;GACH;;;AAgBhB,SAAS,EAAY,GAAqB,GAAkD;AAE1F,QADI,MAAa,KAAA,IACV,OAAO,KAAa,YAAY,OAAO,KAAa,WACvD,OAAO,EAAS,GAChB,KAAA,IAH+B;;AAMrC,SAAgB,EAAW,EAAE,aAAU,cAAW,cAAW,GAAG,KAAyB;AACvF,QACE,kBAAC,GAAD;EACE,GAAI;EACJ,WAAW,EAAY,GAAU,EAAU;EAC3C,WAAW,CAAC,EAAE,QAAQ,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;YAH5D,CAKE,kBAAC,OAAD;GAAK,WAAW,EAAE;GAAO,SAAQ;GAAY,eAAY;aACvD,kBAAC,YAAD;IAAU,QAAO;IAAoB,MAAK;IAAO,QAAO;IAAe,aAAY;IAAM,eAAc;IAAQ,gBAAe;IAAU,CAAA;GACpI,CAAA,EACL,EACc"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"SelectionPanel-DBdhAKIK.js","names":[],"sources":["../../src/components/SelectionPanel/model.ts","../../src/components/SelectionPanel/SelectionPanel.module.css","../../src/components/SelectionPanel/SelectionPanel.tsx"],"sourcesContent":["// Pure selection→panel derivations for SelectionPanel. Kept free of\n// React so intersection/aggregation semantics are unit-testable.\n//\n// Path convention (see core `NodePropertiesEntry`): a leaf's OWN KEY in\n// the schema is its node path — a dotted path of any depth rooted at\n// `pose` or `data` (`pose.x`, `data.fill`, `data.style.fontSize`). Group\n// keys are organizational only.\n\nimport {\n MIXED,\n type Mixed,\n type NodePropertiesEntry,\n type NodeRoutingEntry,\n type SceneNode,\n type ToolPrefGroup,\n type ToolPrefLeaf,\n} from '@weasel-js/core';\n\n// Re-exported under its existing public name here: the kit-wide \"these\n// values disagree\" sentinel (`@weasel-js/core`'s `MIXED`) also covers\n// multi-node aggregation, so this panel doesn't get its own symbol.\nexport { MIXED };\nexport type { Mixed };\n\nexport type AnyNode = SceneNode<unknown, string, unknown>;\n\nexport interface PanelLeaf {\n /** Dotted node path — the leaf's key in the schema. */\n path: string;\n leaf: ToolPrefLeaf;\n}\n\nexport interface PanelRow {\n label: string;\n leaves: PanelLeaf[];\n}\n\nexport interface PanelSection {\n key: string;\n name: string;\n rows: PanelRow[];\n}\n\n/** Derive a node's kind: containers are `'group'`; leaves classify their\n * `data` through the routing entries (first match wins, `'unknown'`\n * otherwise) — same semantics as core's `NodeRouting.classify`. */\nexport function classifyKind(\n node: AnyNode,\n routing: readonly NodeRoutingEntry[],\n): string {\n if (node.kind === 'container') return 'group';\n for (const entry of routing) {\n if (entry.matches(node.data)) return entry.name;\n }\n return 'unknown';\n}\n\nfunction isGroup(n: ToolPrefLeaf | ToolPrefGroup): n is ToolPrefGroup {\n return !('kind' in n);\n}\n\n/** Flatten a schema to sections of leaves. Nested groups fold into their\n * top-level section; top-level leaves get an untitled leading section. */\nfunction flatten(schema: ToolPrefGroup): PanelSection[] {\n const sections: PanelSection[] = [];\n const untitled: PanelLeaf[] = [];\n\n const collect = (group: ToolPrefGroup, into: PanelLeaf[]): void => {\n for (const [key, child] of Object.entries(group.children)) {\n if (isGroup(child)) collect(child, into);\n else into.push({ path: key, leaf: child });\n }\n };\n\n for (const [key, child] of Object.entries(schema.children)) {\n if (isGroup(child)) {\n const leaves: PanelLeaf[] = [];\n collect(child, leaves);\n sections.push({ key, name: child.name, rows: pairRows(leaves) });\n } else {\n untitled.push({ path: key, leaf: child });\n }\n }\n if (untitled.length > 0) {\n sections.unshift({ key: '', name: '', rows: pairRows(untitled) });\n }\n return sections;\n}\n\n/** Merge consecutive leaves sharing a `pair` id into one labeled row. */\nfunction pairRows(leaves: readonly PanelLeaf[]): PanelRow[] {\n const rows: PanelRow[] = [];\n for (const item of leaves) {\n const pair = item.leaf.pair;\n const prev = rows[rows.length - 1];\n if (pair !== undefined && prev !== undefined && prev.label === pair) {\n prev.leaves.push(item);\n } else {\n rows.push({ label: pair ?? item.leaf.name, leaves: [item] });\n }\n }\n return rows;\n}\n\n/**\n * The schema the panel shows for a set of kinds: one kind → its full\n * schema; several → the intersection by (path, leaf kind), shaped by the\n * first kind's section/row layout. Kinds without a registered schema\n * contribute nothing, so their presence collapses the intersection.\n */\nexport function effectiveSections(\n kinds: readonly string[],\n entries: readonly NodePropertiesEntry[],\n): PanelSection[] {\n const uniq = [...new Set(kinds)];\n if (uniq.length === 0) return [];\n const byName = new Map(entries.map((e) => [e.name, e]));\n const schemas = uniq.map((k) => byName.get(k)?.schema);\n if (schemas.some((s) => s === undefined)) return [];\n const [first, ...rest] = schemas as ToolPrefGroup[];\n\n const flatFirst = flatten(first);\n if (rest.length === 0) return flatFirst;\n\n // '\\0' delimiter: path and kind strings are unconstrained, so a\n // printable separator could collide with real content.\n const leafKey = (l: PanelLeaf): string => `${l.path}\\0${l.leaf.kind}`;\n const restKeys = rest.map(\n (schema) =>\n new Set(\n flatten(schema)\n .flatMap((s) => s.rows)\n .flatMap((r) => r.leaves)\n .map(leafKey),\n ),\n );\n const keep = (l: PanelLeaf): boolean =>\n restKeys.every((set) => set.has(leafKey(l)));\n\n return flatFirst\n .map((section) => ({\n ...section,\n rows: section.rows\n .map((row) => ({ ...row, leaves: row.leaves.filter(keep) }))\n .filter((row) => row.leaves.length > 0),\n }))\n .filter((section) => section.rows.length > 0);\n}\n\n/** Read a node value at a dotted path of any depth (`pose.x`,\n * `data.style.fontSize`, `data.style.fill.color`). Returns `undefined` if\n * the path is dotless (mirrors `commit`'s no-op on a malformed schema\n * key) or if any intermediate segment is missing or not an object. */\nexport function nodeValueAt(node: AnyNode, path: string): unknown {\n const segments = path.split('.');\n if (segments.length < 2) return undefined;\n const head = segments[0];\n let cursor: unknown = head === 'pose' ? node.pose : head === 'data' ? node.data : undefined;\n for (let i = 1; i < segments.length; i++) {\n if (cursor == null || typeof cursor !== 'object') return undefined;\n cursor = (cursor as Record<string, unknown>)[segments[i]];\n }\n return cursor;\n}\n\n/** Immutably set `value` at a dotted path within `root`, cloning each level\n * on the way down so React sees new object identities. Arrays stay arrays\n * (an array intermediate is shallow-copied, not flattened into `{0: ...}`)\n * — this is the write-side twin of `nodeValueAt`, so a schema author who\n * gets a working read gets a working write. */\nexport function setAtPath(root: object, segments: readonly string[], value: unknown): object {\n const [head, ...rest] = segments;\n if (rest.length === 0) {\n return Array.isArray(root)\n ? Object.assign([...root], { [head]: value })\n : { ...root, [head]: value };\n }\n const child = (root as Record<string, unknown>)[head];\n const childObj = Array.isArray(child)\n ? [...child]\n : child != null && typeof child === 'object'\n ? { ...child }\n : {};\n const nested = setAtPath(childObj, rest, value);\n return Array.isArray(root)\n ? Object.assign([...root], { [head]: nested })\n : { ...root, [head]: nested };\n}\n\n/** Aggregate a path across nodes: the shared value, or `MIXED`. */\nexport function aggregateValue(\n nodes: readonly AnyNode[],\n path: string,\n): unknown | Mixed {\n let value: unknown;\n let first = true;\n for (const node of nodes) {\n const v = nodeValueAt(node, path);\n if (first) {\n value = v;\n first = false;\n } else if (!Object.is(v, value)) {\n return MIXED;\n }\n }\n return value;\n}\n\n/** `'rect ×2 · text'` — kinds in first-seen order with counts. */\nexport function kindBreakdown(kinds: readonly string[]): string {\n const counts = new Map<string, number>();\n for (const k of kinds) counts.set(k, (counts.get(k) ?? 0) + 1);\n return [...counts.entries()]\n .map(([k, n]) => (n > 1 ? `${k} ×${n}` : k))\n .join(' · ');\n}\n",".root {\n display: flex;\n flex-direction: column;\n font-size: 12px;\n color: var(--wzl-text);\n}\n\n.header {\n display: flex;\n align-items: baseline;\n gap: 8px;\n padding: 8px 10px;\n border-block-end: 1px solid var(--wzl-border);\n}\n\n.kind {\n font-weight: 600;\n font-size: 13px;\n}\n\n.breakdown {\n font-size: 11px;\n opacity: 0.6;\n}\n\n.section {\n padding: 6px 10px 8px;\n border-block-end: 1px solid var(--wzl-border);\n}\n\n.section:last-child {\n border-block-end: none;\n}\n\n.sectionTitle {\n margin: 0 0 6px;\n font-size: 10px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n opacity: 0.55;\n}\n\n.row {\n display: grid;\n grid-template-columns: 64px 1fr;\n gap: 6px;\n align-items: center;\n margin-block-end: 5px;\n}\n\n.rowLabel {\n opacity: 0.75;\n}\n\n.rowControls {\n display: flex;\n gap: 6px;\n align-items: center;\n min-inline-size: 0;\n}\n\n.rowControls > * {\n flex: 1;\n min-inline-size: 0;\n}\n\n.number :global(input),\n.select {\n font-size: 11px;\n}\n\n.unitSuffix {\n flex: none;\n font-size: 11px;\n opacity: 0.6;\n}\n\n.mixedSwitch {\n opacity: 0.55;\n}\n\n.unrenderable {\n font-size: 11px;\n opacity: 0.5;\n font-style: italic;\n}\n","import { Fragment, useState, useSyncExternalStore, type ReactNode } from 'react';\nimport {\n asNodeId,\n type NodePropertiesEntry,\n type NodeRoutingEntry,\n type Scene,\n type SelectionApi,\n type ToolPrefColor,\n type ToolPrefEnum,\n type ToolPrefLeaf,\n type ToolPrefNumber,\n type ToolPrefPaint,\n} from '@weasel-js/core';\nimport { ColorField } from '../ColorField';\nimport { Input } from '../Input';\nimport { NumberField } from '../NumberField';\nimport { Select } from '../Select';\nimport { Switch } from '../Switch';\nimport {\n MIXED,\n aggregateValue,\n classifyKind,\n effectiveSections,\n kindBreakdown,\n setAtPath,\n type AnyNode,\n type PanelLeaf,\n} from './model';\nimport s from './SelectionPanel.module.css';\n\nexport interface PropertyRenderContext {\n /** Dotted node path of the leaf (`pose.x`, `data.fill`). */\n path: string;\n /** The schema leaf. App renderers narrow it to their own kind shape. */\n pref: ToolPrefLeaf;\n /** Aggregated value across the selection; `undefined` when mixed. */\n value: unknown;\n /** True when selected nodes disagree at this path. */\n mixed: boolean;\n /** Commit a value — fans out to every selected node in one undo step. */\n setValue: (value: unknown) => void;\n}\n\nexport type PropertyRenderer = (ctx: PropertyRenderContext) => ReactNode;\n\nexport interface SelectionPanelProps<TData, TLayer extends string, TPose> {\n /** The scene handle (`useScene`). The panel subscribes itself, so it\n * re-renders on scene mutations regardless of parent renders. */\n scene: Scene<TData, TLayer, TPose>;\n /** Selection handle (`useSelection`). Only `current` is read. */\n selection: Pick<SelectionApi, 'current'>;\n /** Properties-trait entries, e.g. core's `defaultNodeProperties` /\n * `inferredNodeProperties` (+ consumer extras). Memoize or hoist. */\n properties: readonly NodePropertiesEntry[];\n /** Routing-trait classifiers used to derive each node's kind — pass\n * the same list the canvas uses. Memoize or hoist. */\n routing: readonly NodeRoutingEntry[];\n /** Control overrides / app-defined kinds (PrefsForm-style). Keys are\n * leaf paths (`data.fill`, checked first) or leaf kinds (`color`).\n * A renderer returning `null` collapses its leaf (and the row, when\n * every leaf in it collapses). */\n renderers?: Record<string, PropertyRenderer>;\n /** Kind → header label. Default: capitalized kind name. */\n kindLabel?: (kind: string) => string;\n /** Rendered when the selection is empty. */\n emptyState?: ReactNode;\n className?: string;\n}\n\nconst defaultKindLabel = (kind: string): string =>\n kind.length === 0 ? kind : kind[0].toUpperCase() + kind.slice(1);\n\n/**\n * Pre-baked selection properties panel. Shows the selected nodes' kind\n * and the properties-trait schema for that kind; multi-selections show\n * the intersection of the kinds' schemas with per-field Mixed state.\n * Edits commit as one labeled `scene.batch` fan-out per gesture.\n */\nexport function SelectionPanel<TData, TLayer extends string, TPose>(\n props: SelectionPanelProps<TData, TLayer, TPose>,\n) {\n const {\n scene,\n selection,\n properties,\n routing,\n renderers,\n kindLabel = defaultKindLabel,\n emptyState = null,\n className,\n } = props;\n\n useSyncExternalStore(scene.subscribe, scene.getVersion, scene.getVersion);\n\n const nodes = selection.current\n .map((id) => scene.get(asNodeId(id)))\n .filter((n): n is NonNullable<typeof n> => n != null) as readonly AnyNode[];\n\n const kinds = nodes.map((n) => classifyKind(n, routing));\n const sections = effectiveSections(kinds, properties);\n\n if (nodes.length === 0) {\n return <div className={[s.root, className].filter(Boolean).join(' ')}>{emptyState}</div>;\n }\n\n const commit = (leaf: PanelLeaf, value: unknown): void => {\n const [head, ...rest] = leaf.path.split('.');\n if (rest.length === 0) return;\n const ids = selection.current.map(asNodeId);\n scene.batch(`Edit ${leaf.leaf.name}`, () => {\n for (const id of ids) {\n const node = scene.get(id);\n if (!node) continue;\n // `setAtPath` spreads plain objects/arrays down the schema path;\n // consumer `pose`/`data` are assumed plain-object-shaped along\n // that path (a class instance's prototype would be dropped by\n // the spread) — the `as TPose`/`as TData` casts below rely on it.\n if (head === 'pose') {\n scene.setPose(id, setAtPath(node.pose as object, rest, value) as TPose);\n } else if (head === 'data') {\n scene.update(id, { data: setAtPath(node.data as object, rest, value) as TData });\n }\n }\n });\n };\n\n return (\n <div className={[s.root, className].filter(Boolean).join(' ')}>\n <header className={s.header}>\n {nodes.length === 1 ? (\n <span className={s.kind}>{kindLabel(kinds[0])}</span>\n ) : (\n <>\n <span className={s.kind}>{nodes.length} selected</span>\n <span className={s.breakdown}>{kindBreakdown(kinds)}</span>\n </>\n )}\n </header>\n {sections.map((section) => {\n // Render controls before emitting row chrome so a null-rendering\n // leaf (custom renderer opting out) collapses its cell — and the\n // whole row / section when nothing survives. PrefsForm precedent.\n const rows = section.rows\n .map((row) => {\n const controls = row.leaves\n .map((panelLeaf) => ({\n panelLeaf,\n content: renderLeafControl(\n panelLeaf,\n row.leaves.length > 1\n ? `${row.label} ${panelLeaf.leaf.name}`\n : panelLeaf.leaf.name,\n nodes,\n renderers,\n commit,\n ),\n }))\n .filter((c) => c.content != null);\n return { row, controls };\n })\n .filter(({ controls }) => controls.length > 0);\n if (rows.length === 0) return null;\n return (\n <section key={section.key} className={s.section}>\n {section.name !== '' && <h3 className={s.sectionTitle}>{section.name}</h3>}\n {rows.map(({ row, controls }) => (\n <div key={row.leaves[0].path} className={s.row}>\n <span className={s.rowLabel} title={row.leaves[0].leaf.description}>\n {row.label}\n </span>\n <span className={s.rowControls}>\n {controls.map(({ panelLeaf, content }) => (\n <Fragment key={panelLeaf.path}>{content}</Fragment>\n ))}\n </span>\n </div>\n ))}\n </section>\n );\n })}\n </div>\n );\n}\n\nfunction renderLeafControl(\n panelLeaf: PanelLeaf,\n ariaLabel: string,\n nodes: readonly AnyNode[],\n renderers: Record<string, PropertyRenderer> | undefined,\n commit: (leaf: PanelLeaf, value: unknown) => void,\n): ReactNode {\n const { path, leaf } = panelLeaf;\n const aggregated = aggregateValue(nodes, path);\n const mixed = aggregated === MIXED;\n const value = mixed ? undefined : aggregated;\n\n const ctx: PropertyRenderContext = {\n path,\n pref: leaf,\n value,\n mixed,\n setValue: (v) => commit(panelLeaf, v),\n };\n\n const custom = renderers?.[path] ?? renderers?.[leaf.kind];\n if (custom) return custom(ctx);\n return renderBuiltin(ctx, ariaLabel);\n}\n\n/** The color of a solid `FillStyle`, or `undefined` for anything else —\n * including a value that isn't a paint at all. `fill` is optional on the\n * solid member of the union, so the tag alone can't decide it. */\nfunction solidColorOf(value: unknown): string | undefined {\n if (typeof value !== 'object' || value === null) return undefined;\n const paint = value as { fill?: string; color?: unknown };\n if (paint.fill !== undefined && paint.fill !== 'solid') return undefined;\n return typeof paint.color === 'string' ? paint.color : undefined;\n}\n\nfunction renderBuiltin(ctx: PropertyRenderContext, ariaLabel: string): ReactNode {\n const { pref, value, mixed, setValue } = ctx;\n switch (pref.kind) {\n case 'number': {\n const p = pref as ToolPrefNumber;\n const stored = typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n const display = stored !== undefined ? (p.unit ? p.unit.toDisplay(stored) : stored) : NaN;\n const field = (\n <NumberField\n className={s.number}\n value={mixed || stored === undefined ? NaN : display}\n placeholder={mixed ? 'Mixed' : undefined}\n minValue={p.min}\n maxValue={p.max}\n step={p.step ?? 1}\n hideSteppers\n aria-label={ariaLabel}\n onChange={(n) => {\n if (Number.isNaN(n)) return;\n setValue(p.unit ? p.unit.fromDisplay(n) : n);\n }}\n />\n );\n if (p.unit?.suffix === undefined) return field;\n return (\n <>\n {field}\n <span className={s.unitSuffix} aria-hidden=\"true\">\n {p.unit.suffix}\n </span>\n </>\n );\n }\n case 'string': {\n return (\n <DraftInput\n text={mixed ? undefined : typeof value === 'string' ? value : ''}\n placeholder={mixed ? 'Mixed' : undefined}\n ariaLabel={ariaLabel}\n onCommit={setValue}\n />\n );\n }\n case 'boolean': {\n const control = (\n <Switch isSelected={Boolean(value)} onChange={setValue} aria-label={ariaLabel} />\n );\n // Switch has no indeterminate state; a reduced-opacity wrapper with\n // a title is the cheap honest cue for a mixed selection.\n if (!mixed) return control;\n return (\n <span className={s.mixedSwitch} title=\"Mixed\">\n {control}\n </span>\n );\n }\n case 'enum': {\n const p = pref as ToolPrefEnum;\n return (\n <Select<string>\n className={s.select}\n options={p.options.map((o) => ({ value: o.value, label: o.label }))}\n selectedKey={mixed ? null : typeof value === 'string' ? value : p.default}\n placeholder={mixed ? 'Mixed' : undefined}\n onSelectionChange={setValue}\n aria-label={ariaLabel}\n />\n );\n }\n case 'color': {\n const p = pref as ToolPrefColor;\n return (\n <ColorField\n value={mixed ? undefined : typeof value === 'string' ? value : p.default}\n mixed={mixed}\n alpha={p.alpha}\n onChange={setValue}\n aria-label={ariaLabel}\n />\n );\n }\n case 'paint': {\n // The value is a whole `FillStyle`. A solid one has a color to show; a\n // pattern or gradient does not, and showing the control's default there\n // would claim a color the shape doesn't have. It gets the same\n // indeterminate chip a genuinely mixed selection gets — in both cases\n // the honest statement is \"there is no single color here\".\n const p = pref as ToolPrefPaint;\n const solid = solidColorOf(value) ?? (mixed ? undefined : solidColorOf(p.default));\n return (\n <ColorField\n value={mixed ? undefined : solid}\n mixed={mixed || (value !== undefined && solidColorOf(value) === undefined)}\n alpha={p.alpha}\n // Write the whole union member. Setting a `color` key on the\n // existing paint would leave a `{ fill: 'linear-gradient', stops,\n // color }` hybrid that every structural `'color' in paint` check\n // downstream reads as solid.\n onChange={(color) => setValue({ fill: 'solid', color })}\n aria-label={ariaLabel}\n />\n );\n }\n default:\n return <span className={s.unrenderable}>({pref.kind}: no renderer)</span>;\n }\n}\n\n/** Text input with commit-on-blur/Enter semantics — live-per-keystroke\n * writes would emit one undo step per character. */\nfunction DraftInput({\n text,\n placeholder,\n ariaLabel,\n onCommit,\n}: {\n text: string | undefined;\n placeholder?: string;\n ariaLabel: string;\n onCommit: (v: string) => void;\n}) {\n const [draft, setDraft] = useState<string | null>(null);\n return (\n <Input\n value={draft ?? text ?? ''}\n placeholder={placeholder}\n aria-label={ariaLabel}\n onChange={setDraft}\n onBlur={() => {\n if (draft !== null && draft !== text) onCommit(draft);\n setDraft(null);\n }}\n onKeyDown={(e) => {\n if (e.key === 'Enter') (e.target as HTMLInputElement).blur();\n }}\n />\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Sidebar-doLqiTZH.js","names":[],"sources":["../../src/components/Sidebar/Sidebar.module.css","../../src/components/Sidebar/Sidebar.tsx"],"sourcesContent":[".sidebar {\n display: flex;\n flex-direction: column;\n gap: 4px;\n background: var(--wzl-panel-bg);\n color: var(--wzl-text);\n}\n\n.sidebar.left {\n border-right: 1px solid var(--wzl-panel-border);\n}\n.sidebar.right {\n border-left: 1px solid var(--wzl-panel-border);\n}\n","import type { CSSProperties, ReactNode } from 'react';\nimport s from './Sidebar.module.css';\n\nexport interface SidebarProps {\n /**\n * Edge the sidebar docks to. Adds a class hook (`s.left` / `s.right`)\n * so consumers can target either edge for borders, shadows, etc.\n * Purely cosmetic — layout is the parent's job.\n */\n side?: 'left' | 'right';\n /**\n * Accessible label — rendered onto the `<aside>`. Without it, screen\n * readers fall back to \"complementary landmark,\" which is fine for\n * apps with a single sidebar but ambiguous when there's both a left\n * and a right.\n */\n ariaLabel?: string;\n children?: ReactNode;\n className?: string;\n /** Inline style — useful for width-as-custom-property handles or any\n * other one-off CSS property the consumer wants to set dynamically. */\n style?: CSSProperties;\n}\n\n/**\n * Generic sidebar shell — `<aside>` with column layout and token-driven\n * surface treatment. Width, sticky positioning, and resize handles stay\n * the consumer's concern (every app has its own layout grid); the kit\n * just provides the chrome.\n *\n * Pair with `<SidebarPanel>` for collapsible sections.\n */\nexport function Sidebar(props: SidebarProps) {\n const { side, ariaLabel, children, className, style } = props;\n const cls = [s.sidebar, side === 'left' && s.left, side === 'right' && s.right, className]\n .filter(Boolean).join(' ');\n return (\n <aside className={cls} aria-label={ariaLabel} style={style}>\n {children}\n </aside>\n );\n}\n"],"mappings":";;;;;;;;ACgCA,SAAgB,EAAQ,GAAqB;CAC3C,IAAM,EAAE,SAAM,cAAW,aAAU,cAAW,aAAU;AAGxD,QACE,kBAAC,SAAD;EAAO,WAHG;GAAC,EAAE;GAAS,MAAS,UAAU,EAAE;GAAM,MAAS,WAAW,EAAE;GAAO;GAAU,CACvF,OAAO,QAAQ,CAAC,KAAK,IAEJ;EAAK,cAAY;EAAkB;EAClD;EACK,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"SidebarPanel-DjTfOzB9.js","names":[],"sources":["../../src/components/SidebarPanel/SidebarPanel.module.css","../../src/components/SidebarPanel/SidebarPanel.tsx"],"sourcesContent":[".panel {\n padding: 8px;\n background: var(--wzl-panel-bg);\n color: var(--wzl-text);\n}\n\n.title {\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n color: var(--wzl-text-muted);\n}\n\n.titleRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 4px;\n padding: 0;\n}\n\n.titleButton {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n flex: 1 1 auto;\n background: transparent;\n border: 0;\n padding: 4px 8px;\n cursor: pointer;\n color: inherit;\n font: inherit;\n text-align: left;\n}\n.titleButton:hover {\n color: var(--wzl-text);\n}\n\n.chevron {\n display: inline-block;\n font-size: 10px;\n line-height: 1;\n color: var(--wzl-text-muted);\n transition: transform var(--wzl-motion-fast) ease;\n}\n.chevronCollapsed {\n transform: rotate(-90deg);\n}\n\n.hideButton {\n background: transparent;\n border: 0;\n padding: 0 6px;\n font-size: 14px;\n line-height: 1;\n color: var(--wzl-text-muted);\n cursor: pointer;\n}\n.hideButton:hover {\n color: var(--wzl-text);\n}\n\n.panel.collapsed {\n padding-bottom: 4px;\n}\n\n.body {\n padding: 0 4px;\n}\n","import type { ReactNode } from 'react';\nimport { Focusable } from 'react-aria-components';\nimport { Tooltip, TooltipTrigger } from '../Tooltip';\nimport s from './SidebarPanel.module.css';\n\nexport interface SidebarPanelProps {\n /** Title rendered in the panel's header row. */\n title?: ReactNode;\n /**\n * Collapsed state — when true, only the header paints; the body is\n * omitted. Caller owns the state (typically via prefs) and flips it\n * back via `onToggleCollapse`.\n */\n collapsed?: boolean;\n /**\n * Click handler for the title row. When provided, the header renders\n * as a button (chevron + label) and acts as the collapse toggle.\n * When omitted, the title is static text and the panel can't collapse.\n */\n onToggleCollapse?(): void;\n /**\n * Optional close handler — when provided, the header gains a `×`\n * button on the trailing edge. Caller flips its \"hidden\" state and\n * conditionally unmounts the panel from the parent.\n */\n onHide?(): void;\n children?: ReactNode;\n className?: string;\n}\n\n/**\n * Generic collapsible panel section for sidebars. Pure presentation —\n * caller manages `collapsed`/`hidden` state externally (the pattern is\n * to drive it from app prefs so the Preferences modal and inline\n * chevrons toggle the same map).\n *\n * Pair with `<Sidebar>` for the docked column layout.\n */\nexport function SidebarPanel(props: SidebarPanelProps) {\n const { title, collapsed, onToggleCollapse, onHide, children, className } = props;\n const cls = [s.panel, collapsed && s.collapsed, className].filter(Boolean).join(' ');\n const headerInteractive = onToggleCollapse !== undefined;\n return (\n <div className={cls}>\n {title !== undefined && (\n <div className={s.titleRow}>\n {headerInteractive ? (\n <button\n type=\"button\"\n className={s.titleButton}\n onClick={onToggleCollapse}\n aria-expanded={!collapsed}\n >\n <span className={[s.chevron, collapsed && s.chevronCollapsed].filter(Boolean).join(' ')} aria-hidden=\"true\">▾</span>\n <span className={s.title}>{title}</span>\n </button>\n ) : (\n <span className={s.title}>{title}</span>\n )}\n {onHide !== undefined && (\n <TooltipTrigger>\n <Focusable>\n <button\n type=\"button\"\n className={s.hideButton}\n onClick={onHide}\n aria-label=\"Hide panel\"\n >\n ×\n </button>\n </Focusable>\n <Tooltip>Hide panel</Tooltip>\n </TooltipTrigger>\n )}\n </div>\n )}\n {!collapsed && <div className={s.body}>{children}</div>}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;ACsCA,SAAgB,EAAa,GAA0B;CACrD,IAAM,EAAE,UAAO,cAAW,qBAAkB,WAAQ,aAAU,iBAAc,GACtE,IAAM;EAAC,EAAE;EAAO,KAAa,EAAE;EAAW;EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI,EAC9E,IAAoB,MAAqB,KAAA;AAC/C,QACE,kBAAC,OAAD;EAAK,WAAW;YAAhB,CACG,MAAU,KAAA,KACT,kBAAC,OAAD;GAAK,WAAW,EAAE;aAAlB,CACG,IACC,kBAAC,UAAD;IACE,MAAK;IACL,WAAW,EAAE;IACb,SAAS;IACT,iBAAe,CAAC;cAJlB,CAME,kBAAC,QAAD;KAAM,WAAW,CAAC,EAAE,SAAS,KAAa,EAAE,iBAAiB,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;KAAE,eAAY;eAAO;KAAQ,CAAA,EACpH,kBAAC,QAAD;KAAM,WAAW,EAAE;eAAQ;KAAa,CAAA,CACjC;QAET,kBAAC,QAAD;IAAM,WAAW,EAAE;cAAQ;IAAa,CAAA,EAEzC,MAAW,KAAA,KACV,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,UAAD;IACE,MAAK;IACL,WAAW,EAAE;IACb,SAAS;IACT,cAAW;cACZ;IAEQ,CAAA,EACC,CAAA,EACZ,kBAAC,GAAD,EAAA,UAAS,cAAoB,CAAA,CACd,EAAA,CAAA,CAEf;MAEP,CAAC,KAAa,kBAAC,OAAD;GAAK,WAAW,EAAE;GAAO;GAAe,CAAA,CACnD"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Slider-sUJGgmKR.js","names":[],"sources":["../../src/components/Slider/Slider.module.css","../../src/format/number.ts","../../src/components/Slider/Slider.tsx"],"sourcesContent":[".root {\n position: relative;\n user-select: none;\n -webkit-user-select: none;\n touch-action: none;\n}\n\n.row {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.track {\n position: relative;\n flex: 1;\n min-width: 0;\n height: var(--rp-track-height, 24px);\n background: var(--wzl-track-bg);\n border: 1px solid var(--wzl-track-border);\n border-radius: 3px;\n cursor: crosshair;\n /* No `overflow: hidden` here — thumbs at extreme values would get\n * clipped at the track edges. The gradient/track-paint clip is\n * applied to `.trackInner` instead, which lets thumbs spill freely. */\n}\n\n.trackInner {\n position: absolute;\n inset: 0;\n border-radius: 3px;\n overflow: hidden;\n}\n\n.thumb {\n position: absolute;\n /* Thumb extends 2px above + below the track for grabbability + visual\n * weight. Track itself stays its natural height; the thumb just spills. */\n top: -2px;\n bottom: -2px;\n width: 14px;\n margin-left: -7px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: color-mix(in srgb, var(--wzl-thumb-fill) 70%, transparent);\n border: 1px solid var(--wzl-thumb-border);\n border-radius: 3px;\n cursor: ew-resize;\n /* Frosted-glass over the (often colorful) track. Cheap on modern GPUs;\n * gracefully degrades to just the partial-alpha background on engines\n * without backdrop-filter support. */\n backdrop-filter: blur(3px);\n -webkit-backdrop-filter: blur(3px);\n /* Subtle raise — sells the \"thumb is on top of the track\" hierarchy. */\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);\n font: 500 0.65rem/1 ui-sans-serif, system-ui, sans-serif;\n font-variant-numeric: tabular-nums;\n /* Thumb text reads against `--wzl-thumb-fill`, which can differ from the\n * surrounding panel text color. Default = dark; consumers re-skin via\n * `--wzl-thumb-text` if they pick a dark thumb fill. */\n color: var(--wzl-thumb-text);\n /* White halo so labels stay legible over wildly varying gradient\n * backgrounds (e.g. labels 'T' / 'P' / 'B' over teal, hue 200° over\n * yellow-green, etc.). */\n text-shadow: 0 0 2px rgba(255, 255, 255, 0.7);\n}\n\n.thumb:focus-visible {\n outline: 2px solid var(--wzl-accent);\n outline-offset: 1px;\n}\n\n.thumbActive {\n /* Visual marker for the dragging/focused thumb; subclass-overridable. */\n z-index: 1;\n}\n\n.readoutsBelow {\n position: relative;\n height: 14px;\n margin-top: 4px;\n}\n\n.readoutBelow {\n position: absolute;\n transform: translateX(-50%);\n font: 500 0.65rem/1 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n color: var(--wzl-text-muted);\n white-space: nowrap;\n}\n\n.readoutInline {\n display: inline-block;\n margin-left: 8px;\n font: 500 0.7rem/1 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n color: var(--wzl-text-muted);\n vertical-align: middle;\n}\n\n.notched {\n background: var(--thumb-svg, none);\n background-size: 100% 100%;\n background-repeat: no-repeat;\n background-color: transparent;\n border: none;\n /* Notched thumbs need to disable the .thumb defaults that paint to\n * the bounding box: backdrop-filter and box-shadow both ignore the\n * SVG's polygon/notch and would render through the notch cutout.\n * Use filter: drop-shadow instead — it traces the actual rendered\n * alpha (i.e. the polygon shape including the cut). */\n backdrop-filter: none;\n -webkit-backdrop-filter: none;\n box-shadow: none;\n filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.25));\n /* Default notched SVG (down-pointing pentagon, matches the perceptual-color experiment). */\n --thumb-svg: url(\"data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 30' preserveAspectRatio='none'%3E%3Cpolygon points='0,0 2.52,0 7,10.3 11.48,0 14,0 14,30 0,30' fill='rgba(255,255,255,0.62)' stroke='rgba(0,0,0,0.55)' stroke-width='0.75' stroke-linejoin='miter'/%3E%3C/svg%3E\");\n}\n","/**\n * Display-formatter for numbers. Use this anywhere a number is shown to\n * a user. The whole point: negative values get prefixed with the real\n * MINUS SIGN (U+2212) instead of the ASCII HYPHEN-MINUS (U+002D) that\n * `toLocaleString` and template literals produce by default.\n *\n * U+2212 is the same visual width as `+` and reads as a sign rather\n * than a hyphen — columns of signed numbers align cleanly and the\n * glyph doesn't get confused with a bullet or list dash.\n */\nexport const MINUS_SIGN = '−';\n\nexport function formatNumber(value: number, options?: Intl.NumberFormatOptions): string {\n const formatted = Number.isFinite(value)\n ? value.toLocaleString(undefined, options)\n : String(value);\n return formatted.replace(/^-/, MINUS_SIGN);\n}\n","import { useCallback, useRef, type CSSProperties, type KeyboardEvent as ReactKeyboardEvent, type MouseEvent as ReactMouseEvent, type PointerEvent as ReactPointerEvent, type ReactElement, type ReactNode } from 'react';\nimport s from './Slider.module.css';\nimport { formatNumber } from '../../format/number';\n\nexport type ThumbRenderCtx = {\n width: number;\n height: number;\n isActive: boolean;\n};\n\nexport type ThumbShape =\n | 'round'\n | 'notched'\n | { render: (ctx: ThumbRenderCtx) => ReactNode };\n\nexport type Thumb = {\n value: number;\n label?: string;\n shape?: ThumbShape;\n bounds?: [number, number] | ((ctx: BoundsCtx) => [number, number]);\n};\n\nexport type BoundsCtx = {\n thumbs: readonly Thumb[];\n index: number;\n};\n\nexport type TrackCtx = {\n trackWidth: number;\n valueToFraction: (v: number) => number;\n};\n\nexport type SliderProps<T extends Thumb = Thumb> = {\n thumbs: readonly T[];\n onChange: (next: T[]) => void;\n onCommit?: (next: T[]) => void;\n min: number;\n max: number;\n step?: number;\n constraint?: 'free' | 'ordered';\n onAddThumb?: (atValue: number) => T | null;\n onRemoveThumb?: (index: number) => boolean;\n allowShiftAll?: boolean;\n renderTrack?: (ctx: TrackCtx) => ReactNode;\n trackHeight?: number;\n renderReadout?: (thumb: T, index: number) => ReactNode;\n readoutPlacement?: 'none' | 'inline-after' | 'below-thumb';\n ariaLabel?: string;\n className?: string;\n};\n\nfunction snap(v: number, step: number | undefined, min: number): number {\n if (step === undefined || step <= 0) return v;\n return Math.round((v - min) / step) * step + min;\n}\n\nfunction clamp(v: number, lo: number, hi: number): number {\n return Math.max(lo, Math.min(hi, v));\n}\n\nfunction defaultStep(step: number | undefined, min: number, max: number): number {\n if (step !== undefined && step > 0) return step;\n return (max - min) / 100;\n}\n\nfunction resolveBounds(thumb: Thumb, ctx: BoundsCtx, fallbackMin: number, fallbackMax: number): [number, number] {\n if (!thumb.bounds) return [fallbackMin, fallbackMax];\n const tuple = typeof thumb.bounds === 'function' ? thumb.bounds(ctx) : thumb.bounds;\n return [tuple[0], tuple[1]];\n}\n\nfunction defaultReadout(thumb: Thumb): string {\n return formatNumber(thumb.value, { minimumFractionDigits: 3, maximumFractionDigits: 3 });\n}\n\nexport function Slider<T extends Thumb = Thumb>(props: SliderProps<T>): ReactElement {\n const { thumbs, onChange, onCommit, min, max, step, constraint, trackHeight, ariaLabel, className } = props;\n\n const trackRef = useRef<HTMLDivElement | null>(null);\n // In-flight thumb buffer during a drag; null when not dragging.\n const dragBufferRef = useRef<T[] | null>(null);\n\n const valueToFraction = useCallback(\n (v: number): number => (max === min ? 0 : clamp((v - min) / (max - min), 0, 1)),\n [min, max],\n );\n\n const fractionToValue = useCallback(\n (f: number): number => min + clamp(f, 0, 1) * (max - min),\n [min, max],\n );\n\n const beginThumbDrag = useCallback(\n (index: number) => {\n const buf: T[] = thumbs.map(t => ({ ...t }));\n dragBufferRef.current = buf;\n let droppedOff = false;\n\n const onMove = (ev: PointerEvent) => {\n const track = trackRef.current;\n const buffer = dragBufferRef.current;\n if (!track || !buffer) return;\n const rect = track.getBoundingClientRect();\n const f = clamp((ev.clientX - rect.left) / rect.width, 0, 1);\n let v = fractionToValue(f);\n v = snap(v, step, min);\n v = clamp(v, min, max);\n\n const [bLo, bHi] = resolveBounds(buffer[index], { thumbs: buffer, index }, min, max);\n v = clamp(v, bLo, bHi);\n\n if (constraint === 'ordered') {\n const gap = step !== undefined && step > 0 ? step : (max - min) / 1000;\n const lower = index > 0 ? buffer[index - 1].value + gap : min;\n const upper = index < buffer.length - 1 ? buffer[index + 1].value - gap : max;\n v = clamp(v, lower, upper);\n }\n\n // Drop-off detection: pointer exits the track vertically by more than trackHeight.\n const bandHeight = rect.height;\n if (props.onRemoveThumb) {\n if (ev.clientY < rect.top - bandHeight || ev.clientY > rect.bottom + bandHeight) {\n droppedOff = true;\n } else {\n droppedOff = false;\n }\n }\n\n buffer[index] = { ...buffer[index], value: v };\n onChange(buffer.map(t => ({ ...t })));\n };\n\n const onUp = () => {\n document.removeEventListener('pointermove', onMove);\n document.removeEventListener('pointerup', onUp);\n const buffer = dragBufferRef.current;\n dragBufferRef.current = null;\n if (!buffer) return;\n\n if (droppedOff && props.onRemoveThumb) {\n const accepted = props.onRemoveThumb(index);\n if (accepted) {\n const next = buffer.filter((_, i) => i !== index).map(t => ({ ...t })) as T[];\n onChange(next);\n onCommit?.(next);\n return;\n }\n }\n\n if (onCommit) onCommit(buffer.map(t => ({ ...t })));\n };\n\n document.addEventListener('pointermove', onMove);\n document.addEventListener('pointerup', onUp);\n },\n [thumbs, onChange, onCommit, fractionToValue, min, max, step, constraint, props],\n );\n\n const beginShiftAllDrag = useCallback(\n (anchorX: number) => {\n const buf: T[] = thumbs.map(t => ({ ...t }));\n const startValues = buf.map(t => t.value);\n dragBufferRef.current = buf;\n\n const onMove = (ev: PointerEvent) => {\n const track = trackRef.current;\n const buffer = dragBufferRef.current;\n if (!track || !buffer) return;\n const rect = track.getBoundingClientRect();\n const dxFraction = (ev.clientX - anchorX) / rect.width;\n let dValue = dxFraction * (max - min);\n dValue = snap(dValue, step, 0);\n\n // Clamp delta so no thumb leaves [min, max] (per-thumb bounds intentionally\n // not enforced — matches the experiment's hue-band shift-translate semantics).\n let allowedNeg = -Infinity;\n let allowedPos = Infinity;\n for (let i = 0; i < startValues.length; i++) {\n allowedNeg = Math.max(allowedNeg, min - startValues[i]);\n allowedPos = Math.min(allowedPos, max - startValues[i]);\n }\n dValue = clamp(dValue, allowedNeg, allowedPos);\n\n for (let i = 0; i < buffer.length; i++) {\n buffer[i] = { ...buffer[i], value: clamp(startValues[i] + dValue, min, max) };\n }\n onChange(buffer.map(t => ({ ...t })));\n };\n\n const onUp = () => {\n document.removeEventListener('pointermove', onMove);\n document.removeEventListener('pointerup', onUp);\n const buffer = dragBufferRef.current;\n dragBufferRef.current = null;\n if (buffer && onCommit) onCommit(buffer.map(t => ({ ...t })));\n };\n\n document.addEventListener('pointermove', onMove);\n document.addEventListener('pointerup', onUp);\n },\n [thumbs, onChange, onCommit, min, max, step],\n );\n\n const onThumbPointerDown = (index: number) => (e: ReactPointerEvent) => {\n // Only bail on explicit non-primary buttons (button > 0). jsdom's PointerEvent\n // leaves `button` undefined; treat that as primary so tests can drive drags.\n if (typeof e.button === 'number' && e.button > 0) return;\n e.preventDefault();\n e.stopPropagation();\n if (e.shiftKey && props.allowShiftAll) {\n beginShiftAllDrag(e.clientX);\n } else {\n beginThumbDrag(index);\n }\n };\n\n const onThumbContextMenu = (index: number) => (e: ReactMouseEvent) => {\n if (!props.onRemoveThumb) return;\n e.preventDefault();\n const accepted = props.onRemoveThumb(index);\n if (!accepted) return;\n const next = thumbs.filter((_, i) => i !== index).map(t => ({ ...t })) as T[];\n onChange(next);\n onCommit?.(next);\n };\n\n const onTrackPointerDown = (e: ReactPointerEvent<HTMLDivElement>) => {\n if (typeof e.button === 'number' && e.button > 0) return;\n if (!props.onAddThumb) return;\n // If the event originated on a thumb, the thumb's own handler ran first; this is a track click.\n if ((e.target as HTMLElement).closest(`.${s.thumb}`)) return;\n e.preventDefault();\n const track = trackRef.current;\n if (!track) return;\n const rect = track.getBoundingClientRect();\n const f = clamp((e.clientX - rect.left) / rect.width, 0, 1);\n let v = fractionToValue(f);\n v = snap(v, step, min);\n v = clamp(v, min, max);\n const created = props.onAddThumb(v);\n if (!created) return;\n const next = [...thumbs.map(t => ({ ...t })), created] as T[];\n onChange(next);\n onCommit?.(next);\n };\n\n const onThumbKeyDown = (index: number) => (e: ReactKeyboardEvent) => {\n const stepSize = defaultStep(step, min, max);\n let delta = 0;\n let snapTo: 'home' | 'end' | null = null;\n\n switch (e.key) {\n case 'ArrowRight':\n case 'ArrowUp':\n delta = e.shiftKey ? stepSize * 10 : stepSize;\n break;\n case 'ArrowLeft':\n case 'ArrowDown':\n delta = e.shiftKey ? -stepSize * 10 : -stepSize;\n break;\n case 'PageUp':\n delta = stepSize * 10;\n break;\n case 'PageDown':\n delta = -stepSize * 10;\n break;\n case 'Home':\n snapTo = 'home';\n break;\n case 'End':\n snapTo = 'end';\n break;\n default:\n return;\n }\n\n e.preventDefault();\n const next = thumbs.map(t => ({ ...t }));\n const [bLo, bHi] = resolveBounds(next[index], { thumbs: next, index }, min, max);\n const lo = Math.max(min, bLo);\n const hi = Math.min(max, bHi);\n let v: number;\n if (snapTo === 'home') v = lo;\n else if (snapTo === 'end') v = hi;\n else v = next[index].value + delta;\n v = snap(v, step, min);\n v = clamp(v, lo, hi);\n next[index] = { ...next[index], value: v };\n onChange(next);\n onCommit?.(next);\n };\n\n const placement = props.readoutPlacement ?? 'none';\n const renderReadout = props.renderReadout;\n\n return (\n <div\n className={className ? `${s.root} ${className}` : s.root}\n style={trackHeight !== undefined ? ({ ['--rp-track-height' as string]: `${trackHeight}px` } as CSSProperties) : undefined}\n >\n <div className={s.row}>\n <div className={s.track} ref={trackRef} onPointerDown={onTrackPointerDown}>\n {props.renderTrack && (\n <div className={s.trackInner}>\n {props.renderTrack({\n trackWidth: trackRef.current?.getBoundingClientRect().width ?? 0,\n valueToFraction,\n })}\n </div>\n )}\n {thumbs.map((thumb, i) => {\n const isNotched = thumb.shape === 'notched';\n const customRender = typeof thumb.shape === 'object' && thumb.shape !== null ? thumb.shape.render : null;\n const cls = `${s.thumb}${isNotched ? ` ${s.notched}` : ''}`;\n return (\n <div\n key={i}\n role=\"slider\"\n tabIndex={0}\n aria-orientation=\"horizontal\"\n aria-valuemin={min}\n aria-valuemax={max}\n aria-valuenow={thumb.value}\n aria-label={[ariaLabel, thumb.label].filter(Boolean).join(' ') || undefined}\n className={cls}\n style={{ left: `${valueToFraction(thumb.value) * 100}%` }}\n onPointerDown={onThumbPointerDown(i)}\n onKeyDown={onThumbKeyDown(i)}\n onContextMenu={onThumbContextMenu(i)}\n >\n {customRender ? customRender({ width: 14, height: 24, isActive: false }) : (thumb.label ?? '')}\n </div>\n );\n })}\n </div>\n {placement === 'inline-after' && (\n <span data-readout=\"inline\" className={s.readoutInline}>\n {thumbs.map((t, i) => (\n <span key={i}>{i > 0 ? ' / ' : ''}{renderReadout ? renderReadout(t, i) : defaultReadout(t)}</span>\n ))}\n </span>\n )}\n </div>\n {placement === 'below-thumb' && (\n <div className={s.readoutsBelow}>\n {thumbs.map((t, i) => (\n <span\n key={i}\n data-readout=\"below\"\n className={s.readoutBelow}\n style={{ left: `${valueToFraction(t.value) * 100}%` }}\n >\n {renderReadout ? renderReadout(t, i) : defaultReadout(t)}\n </span>\n ))}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"StatusBar-OOjfqCD8.js","names":[],"sources":["../../src/components/StatusBar/StatusBar.module.css","../../src/components/StatusBar/StatusBar.tsx"],"sourcesContent":[".bar {\n display: flex;\n align-items: center;\n gap: 16px;\n flex-shrink: 0;\n padding: 4px 12px;\n font-family: var(--wzl-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);\n font-size: 11px;\n color: var(--wzl-text-muted, var(--wzl-fg-subtle));\n background: var(--wzl-panel-bg);\n border-top: 1px solid var(--wzl-panel-border);\n}\n\n.item {\n white-space: nowrap;\n}\n\n.muted {\n opacity: 0.6;\n font-variant-numeric: tabular-nums;\n}\n\n.hasTitle {\n cursor: help;\n}\n\n.spacer {\n margin-left: auto;\n}\n","import type { ReactNode } from 'react';\nimport s from './StatusBar.module.css';\n\nexport interface StatusBarProps {\n /**\n * Accessible label for the bar. Deliberately not a live region — a status\n * bar's readouts change on every pointer move (zoom, cursor position,\n * selection count), and announcing each one would make a screen reader\n * unusable. Screen-reader users reach the same facts through the controls\n * that own them.\n */\n ariaLabel?: string;\n children?: ReactNode;\n className?: string;\n}\n\n/**\n * The thin readout strip along the bottom of an editor window: monospace,\n * one row, items separated by a fixed gap, with an optional spacer that\n * pushes everything after it to the trailing edge.\n *\n * Pure presentation — it holds whatever the app puts in it. Compose with\n * `<StatusBarItem>` and `<StatusBarSpacer>`.\n */\nexport function StatusBar(props: StatusBarProps) {\n const { ariaLabel, children, className } = props;\n return (\n <footer className={[s.bar, className].filter(Boolean).join(' ')} aria-label={ariaLabel}>\n {children}\n </footer>\n );\n}\n\nexport interface StatusBarItemProps {\n /**\n * Hover text. Items that carry one also get a `help` cursor, so the\n * affordance is discoverable rather than hidden behind a hover delay.\n */\n title?: string;\n /**\n * Dim the item and use tabular figures. For reference material that\n * shouldn't compete with live readouts — build stamps, hints, units.\n */\n muted?: boolean;\n children?: ReactNode;\n className?: string;\n}\n\n/** One readout in a `<StatusBar>`. */\nexport function StatusBarItem(props: StatusBarItemProps) {\n const { title, muted, children, className } = props;\n const cls = [s.item, muted && s.muted, title !== undefined && s.hasTitle, className]\n .filter(Boolean).join(' ');\n return <span className={cls} title={title}>{children}</span>;\n}\n\n/**\n * Splits a status bar into leading and trailing runs — everything after the\n * spacer is pushed to the trailing edge. Decorative, so it's hidden from the\n * accessibility tree.\n */\nexport function StatusBarSpacer() {\n return <span className={s.spacer} aria-hidden=\"true\" />;\n}\n"],"mappings":";;;;;;;;;;ACwBA,SAAgB,EAAU,GAAuB;CAC/C,IAAM,EAAE,cAAW,aAAU,iBAAc;AAC3C,QACE,kBAAC,UAAD;EAAQ,WAAW,CAAC,EAAE,KAAK,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;EAAE,cAAY;EAC1E;EACM,CAAA;;AAoBb,SAAgB,EAAc,GAA2B;CACvD,IAAM,EAAE,UAAO,UAAO,aAAU,iBAAc;AAG9C,QAAO,kBAAC,QAAD;EAAM,WAFD;GAAC,EAAE;GAAM,KAAS,EAAE;GAAO,MAAU,KAAA,KAAa,EAAE;GAAU;GAAU,CACjF,OAAO,QAAQ,CAAC,KAAK,IACA;EAAY;EAAQ;EAAgB,CAAA;;AAQ9D,SAAgB,IAAkB;AAChC,QAAO,kBAAC,QAAD;EAAM,WAAW,EAAE;EAAQ,eAAY;EAAS,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Switch-DxJLMYki.js","names":[],"sources":["../../src/components/Switch/Switch.module.css","../../src/components/Switch/Switch.tsx"],"sourcesContent":[".switch {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n cursor: pointer;\n font-family: var(--wzl-font-ui);\n font-size: 13px;\n color: var(--wzl-fg);\n user-select: none;\n -webkit-user-select: none;\n}\n\n.switch[data-disabled] {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.track {\n position: relative;\n flex: 0 0 auto;\n width: 28px;\n height: 16px;\n background: var(--wzl-input-bg);\n border: 1px solid var(--wzl-border-strong);\n border-radius: 8px;\n transition: background-color var(--wzl-motion-fast), border-color var(--wzl-motion-fast), box-shadow var(--wzl-motion-fast);\n}\n\n.switch[data-focus-visible] .track {\n box-shadow: 0 0 0 2px var(--wzl-focus-ring);\n}\n\n.switch[data-selected] .track {\n background: var(--wzl-accent-strong);\n border-color: var(--wzl-accent-strong);\n}\n\n.thumb {\n position: absolute;\n top: 1px;\n left: 1px;\n width: 12px;\n height: 12px;\n background: var(--wzl-fg);\n border-radius: 50%;\n transition: transform var(--wzl-motion-fast);\n}\n\n.switch[data-selected] .thumb {\n transform: translateX(12px);\n background: var(--wzl-fg-on-accent);\n}\n\n.label {\n line-height: 1.2;\n}\n","import { forwardRef, type ReactNode, type Ref } from 'react';\nimport {\n Switch as RACSwitch,\n type SwitchProps as RACSwitchProps,\n} from 'react-aria-components';\nimport s from './Switch.module.css';\n\nexport type SwitchProps = Omit<RACSwitchProps, 'children' | 'className'> & {\n children?: ReactNode;\n className?: string;\n};\n\nexport const Switch = forwardRef(function Switch(\n props: SwitchProps,\n ref: Ref<HTMLLabelElement>,\n) {\n const { children, className, ...rest } = props;\n return (\n <RACSwitch\n {...rest}\n ref={ref}\n className={[s.switch, className].filter(Boolean).join(' ')}\n >\n <span className={s.track}>\n <span className={s.thumb} />\n </span>\n {children !== undefined && <span className={s.label}>{children}</span>}\n </RACSwitch>\n );\n});\n"],"mappings":";;;;;;;;GCYa,IAAS,EAAW,SAC/B,GACA,GACA;CACA,IAAM,EAAE,aAAU,cAAW,GAAG,MAAS;AACzC,QACE,kBAAC,GAAD;EACE,GAAI;EACC;EACL,WAAW,CAAC,EAAE,QAAQ,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;YAH5D,CAKE,kBAAC,QAAD;GAAM,WAAW,EAAE;aACjB,kBAAC,QAAD,EAAM,WAAW,EAAE,OAAS,CAAA;GACvB,CAAA,EACN,MAAa,KAAA,KAAa,kBAAC,QAAD;GAAM,WAAW,EAAE;GAAQ;GAAgB,CAAA,CAC5D;;EAEd"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ToolButton-Dv6NdZKh.js","names":[],"sources":["../../src/components/ToolButton/ToolButton.module.css","../../src/components/ToolButton/ToolButton.tsx"],"sourcesContent":[".button {\n display: inline-flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 2px;\n min-width: 44px;\n padding: 6px 4px;\n background: transparent;\n border: 1px solid transparent;\n border-radius: 3px;\n color: var(--wzl-text);\n cursor: default;\n font-family: inherit;\n font-size: 10px;\n line-height: 1.1;\n}\n.button:hover:not([disabled]) {\n background: var(--wzl-button-fill-hover);\n color: var(--wzl-button-text);\n}\n.button:focus-visible {\n outline: 2px solid var(--wzl-accent);\n outline-offset: -2px;\n}\n.button.active {\n background: var(--wzl-button-fill-pressed);\n border-color: var(--wzl-accent);\n color: var(--wzl-button-text);\n}\n.button[disabled] {\n opacity: 0.4;\n}\n\n.icon { display: block; }\n.icon > svg { display: block; }\n.label { font-size: 10px; }\n.shortcut {\n color: var(--wzl-text-muted);\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 9px;\n}\n","import type { ReactNode } from 'react';\nimport { Focusable } from 'react-aria-components';\nimport { Tooltip, TooltipTrigger } from '../Tooltip';\nimport s from './ToolButton.module.css';\n\nexport interface ToolButtonProps {\n /** Icon node (typically an SVG component). */\n icon: ReactNode;\n /** Human-readable label shown under the icon. */\n label: string;\n /** Optional shortcut hint (e.g. \"V\" or \"⌘Z\"). */\n shortcut?: string;\n /** Selected/active state — toggles the active visual treatment. */\n active?: boolean;\n /** Disabled state — passes through to the underlying button. */\n disabled?: boolean;\n /**\n * When true, sets `aria-disabled=\"true\"` on the button without using the\n * native `disabled` attribute. This keeps the button focusable and reachable\n * by keyboard (roving-tabindex still applies) while marking it as ineligible\n * to screen readers. The caller is responsible for making `onClick` a no-op.\n */\n ariaDisabled?: boolean;\n /**\n * Whether this button is the currently tabbable member of its toolbar.\n * Toolbars use roving tabindex: exactly one button has `tabIndex=0` at\n * a time; the rest are `-1`. Caller manages which.\n */\n tabbable?: boolean;\n /** Click handler. */\n onClick(): void;\n /**\n * Tooltip content. Defaults to `label` (plus `shortcut` if provided).\n */\n title?: string;\n /** Additional class for the root button. */\n className?: string;\n}\n\n/**\n * Generic icon-+-label toolbar button — building block for `ToolPalette`\n * and similar surfaces. Headless about layout direction (parent group\n * supplies flex direction via `ToolGroup`). Theme via `--wzl-*` tokens.\n */\nexport function ToolButton(props: ToolButtonProps) {\n const { icon, label, shortcut, active, disabled, ariaDisabled, tabbable, onClick, title, className } = props;\n const resolvedTitle = title ?? (shortcut ? `${label} (${shortcut})` : label);\n const cls = [s.button, active && s.active, className].filter(Boolean).join(' ');\n return (\n <TooltipTrigger isDisabled={disabled}>\n <Focusable isDisabled={disabled}>\n <button\n type=\"button\"\n tabIndex={tabbable ? 0 : -1}\n className={cls}\n aria-current={active ? 'true' : undefined}\n aria-disabled={ariaDisabled ? 'true' : undefined}\n disabled={disabled}\n onClick={onClick}\n >\n <span className={s.icon} aria-hidden=\"true\">{icon}</span>\n <span className={s.label}>{label}</span>\n {shortcut && <span className={s.shortcut}>{shortcut}</span>}\n </button>\n </Focusable>\n <Tooltip>{resolvedTitle}</Tooltip>\n </TooltipTrigger>\n );\n}\n"],"mappings":";;;;;;;;;;;;AC4CA,SAAgB,EAAW,GAAwB;CACjD,IAAM,EAAE,SAAM,UAAO,aAAU,WAAQ,aAAU,iBAAc,aAAU,YAAS,UAAO,iBAAc,GACjG,IAAgB,MAAU,IAAW,GAAG,EAAM,IAAI,EAAS,KAAK,IAChE,IAAM;EAAC,EAAE;EAAQ,KAAU,EAAE;EAAQ;EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;AAC/E,QACE,kBAAC,GAAD;EAAgB,YAAY;YAA5B,CACE,kBAAC,GAAD;GAAW,YAAY;aACrB,kBAAC,UAAD;IACE,MAAK;IACL,UAAU,IAAW,IAAI;IACzB,WAAW;IACX,gBAAc,IAAS,SAAS,KAAA;IAChC,iBAAe,IAAe,SAAS,KAAA;IAC7B;IACD;cAPX;KASE,kBAAC,QAAD;MAAM,WAAW,EAAE;MAAM,eAAY;gBAAQ;MAAY,CAAA;KACzD,kBAAC,QAAD;MAAM,WAAW,EAAE;gBAAQ;MAAa,CAAA;KACvC,KAAY,kBAAC,QAAD;MAAM,WAAW,EAAE;gBAAW;MAAgB,CAAA;KACpD;;GACC,CAAA,EACZ,kBAAC,GAAD,EAAA,UAAU,GAAwB,CAAA,CACnB"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ToolOptionsBar-ChiL-HVA.js","names":[],"sources":["../../src/components/ToolOptionsBar/ToolOptionsBar.module.css","../../src/components/ToolOptionsBar/ToolOptionsBar.tsx"],"sourcesContent":["/* Horizontal strip reserved above the workspace for contextual tool\n options. Fixed height so mounting/unmounting content never resizes\n the workspace beneath it — that's the whole reason this row exists\n as permanent chrome rather than an on-demand overlay.\n\n Overflow: when controls exceed the available width, `.controls`\n scrolls horizontally rather than wrapping (which would grow the row\n and defeat the fixed-height guarantee above) or clipping (which\n would hide controls with no way to reach them). A flyout/long-press\n affordance was considered and rejected — grouped, directly visible\n layouts are preferred in this repo. */\n\n.root {\n display: flex;\n align-items: center;\n gap: 8px;\n height: var(--wzl-tb-height, 28px);\n padding: 0 8px;\n background: var(--wzl-panel-bg);\n border-bottom: 1px solid var(--wzl-panel-border);\n color: var(--wzl-text);\n overflow: hidden;\n}\n\n.label {\n flex: 0 0 auto;\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n color: var(--wzl-text-muted);\n white-space: nowrap;\n}\n\n.controls {\n display: flex;\n align-items: center;\n gap: 6px;\n flex: 1 1 auto;\n min-width: 0;\n height: 100%;\n overflow-x: auto;\n overflow-y: hidden;\n scrollbar-width: thin;\n}\n\n/* Firefox gets a thin scrollbar for free via `scrollbar-width` above.\n * Chromium/WebKit need it spelled out — on a classic (non-overlay)\n * scrollbar theme (default on Windows Chrome/Edge), an unstyled\n * horizontal scrollbar is ~15-17px tall and renders *inside* this box,\n * clipping/off-centering the row's controls. That would reintroduce the\n * exact height instability the scroll-over-wrap overflow choice above\n * exists to avoid — just at the row's inner edge instead of its outer\n * one. Pin it thin and out of the way instead. */\n.controls::-webkit-scrollbar {\n height: 4px;\n}\n.controls::-webkit-scrollbar-track {\n background: transparent;\n}\n.controls::-webkit-scrollbar-thumb {\n background: var(--wzl-panel-border);\n border-radius: 2px;\n}\n","import type { ReactNode } from 'react';\nimport s from './ToolOptionsBar.module.css';\n\nexport interface ToolOptionsBarProps {\n /**\n * Context label for the row — e.g. the active tool's name. Rendered\n * before the children and doubles as the toolbar's `aria-label`. Omit\n * it and the row still renders (it's a permanently reserved slot), just\n * without an accessible name.\n *\n * Using `label` for both the visible text and `aria-label` means a\n * screen reader announces it twice when landing on the toolbar (\"Text,\n * toolbar\", then \"Text\" again when browsing into the visible span).\n * `aria-labelledby` would avoid the echo at the cost of id plumbing and\n * an awkward no-`id`-to-point-at case when `label` is omitted. The\n * double-announcement is common enough for labelled landmark regions\n * that it's left as-is — considered, not missed.\n */\n label?: string;\n children?: ReactNode;\n className?: string;\n}\n\n/**\n * Horizontal strip reserved above the workspace for contextual tool\n * options — its first tenant is the character controls a text tool\n * exposes while a caret range is active. Pure chrome: it renders an\n * optional label and a children slot, nothing else. It knows nothing\n * about text, or about any specific tool — any tool can fill it.\n *\n * The row always renders, even with no `label` and no `children`,\n * because the app reserves its height permanently rather than mounting\n * it on demand — mounting mid-edit would resize the workspace under a\n * canvas that's sized to the page.\n *\n * `role=\"toolbar\"` without roving tabindex: the ARIA authoring practices\n * expect arrow-key roving focus across a toolbar's items, but this\n * component doesn't own or know the shape of its children — they're\n * arbitrary, possibly compound controls (`NumberField`, `Select`,\n * `ColorField`) that already use arrow keys for their own value editing.\n * Imposing roving focus here would fight those controls' own key\n * handling. Tab order falls back to normal DOM order for now; revisit\n * once the first tenant's control set is concrete and a roving-focus\n * contract can be designed in coordination with those controls.\n */\nexport function ToolOptionsBar(props: ToolOptionsBarProps) {\n const { label, children, className } = props;\n const cls = [s.root, className].filter(Boolean).join(' ');\n return (\n <div className={cls} role=\"toolbar\" aria-label={label}>\n {label !== undefined && <span className={s.label}>{label}</span>}\n <div className={s.controls}>{children}</div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;AC6CA,SAAgB,EAAe,GAA4B;CACzD,IAAM,EAAE,UAAO,aAAU,iBAAc;AAEvC,QACE,kBAAC,OAAD;EAAK,WAFK,CAAC,EAAE,MAAM,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAEnC;EAAK,MAAK;EAAU,cAAY;YAAhD,CACG,MAAU,KAAA,KAAa,kBAAC,QAAD;GAAM,WAAW,EAAE;aAAQ;GAAa,CAAA,EAChE,kBAAC,OAAD;GAAK,WAAW,EAAE;GAAW;GAAe,CAAA,CACxC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ToolPalette-DXVIEO0w.js","names":[],"sources":["../../src/components/ToolPalette/ToolPalette.module.css","../../src/components/ToolPalette/formatShortcut.ts","../../src/components/ToolPalette/ToolPalette.tsx"],"sourcesContent":[".palette {\n display: flex;\n flex-direction: column;\n gap: 4px;\n background: var(--wzl-panel-bg);\n border: 1px solid var(--wzl-panel-border);\n border-radius: 4px;\n padding: 4px;\n user-select: none;\n}\n.palette.horizontal {\n flex-direction: row;\n}\n\n/* Applied to ineligible tool buttons in the active mode. */\n.ineligibleBtn {\n opacity: 0.35;\n cursor: not-allowed;\n}\n\n.separator {\n background: var(--wzl-panel-border);\n}\n.palette:not(.horizontal) .separator {\n height: 1px;\n margin: 2px 0;\n}\n.palette.horizontal .separator {\n width: 1px;\n margin: 0 2px;\n}\n","import { keyGlyph } from '../Keycaps/keyGlyph';\n\nexport interface ShortcutInput {\n key: string | readonly string[];\n mod?: boolean;\n alt?: boolean;\n shift?: boolean | 'optional';\n}\n\n/** Format a shortcut as an array of display chips, one per key — modifier\n * icons (⌘, ↑, ⌥) followed by the key glyph. UIs that want a boxed\n * per-key look render each chip in its own element. `shift: 'optional'` is\n * treated as falsy. */\nexport function formatShortcutParts(b: ShortcutInput | undefined): readonly string[] | undefined {\n if (!b) return undefined;\n const rawKey = Array.isArray(b.key) ? b.key[0] : (b.key as string);\n const parts: string[] = [];\n if (b.mod) parts.push('⌘');\n if (b.shift === true) parts.push('⇧');\n if (b.alt) parts.push('⌥');\n parts.push(keyGlyph(rawKey));\n return parts;\n}\n\n/** Format a shortcut as a single display string. Order: mod, shift, alt,\n * key. Returns `undefined` for `undefined` input so callers can `??` a\n * fallback. When `key` is an array, the first element is used. `shift: 'optional'`\n * is treated as falsy. */\nexport function formatShortcut(b: ShortcutInput | undefined): string | undefined {\n const parts = formatShortcutParts(b);\n return parts ? parts.join('') : undefined;\n}\n","import { Fragment, useRef, type ReactNode } from 'react';\nimport { UnknownIcon } from '@weasel-js/core';\nimport type { AnyTool, ToolsApi } from '@weasel-js/core';\nimport { eligibleTool, type ModeRegistry } from '@weasel-js/modes';\nimport { ToolButton } from '../ToolButton';\nimport { ToolGroup } from '../ToolGroup';\nimport s from './ToolPalette.module.css';\nimport { formatShortcut, type ShortcutInput } from './formatShortcut';\n\nconst DEFAULT_GROUP_ORDER = ['select', 'shape', 'draw', 'type', 'view'] as const;\nconst MISC = 'misc';\n\nfunction partitionByGroup(list: AnyTool[]): Map<string, AnyTool[]> {\n const groups = new Map<string, AnyTool[]>();\n for (const tool of list) {\n const g = tool.presentation?.group ?? MISC;\n const bucket = groups.get(g) ?? [];\n bucket.push(tool);\n groups.set(g, bucket);\n }\n return groups;\n}\n\nfunction orderedGroupKeys(\n groups: Map<string, AnyTool[]>,\n groupOrder: readonly string[],\n): string[] {\n const seen = new Set(groups.keys());\n const ordered: string[] = [];\n for (const known of groupOrder) {\n if (seen.has(known)) {\n ordered.push(known);\n seen.delete(known);\n }\n }\n // Unknown groups in insertion order (Map preserves it), misc last.\n for (const key of groups.keys()) {\n if (seen.has(key) && key !== MISC) {\n ordered.push(key);\n seen.delete(key);\n }\n }\n if (seen.has(MISC)) ordered.push(MISC);\n return ordered;\n}\n\nfunction resolveIcon(tool: AnyTool): ReactNode {\n const rawIcon = tool.presentation?.icon;\n if (rawIcon == null) return <UnknownIcon />;\n if (typeof rawIcon === 'function') return rawIcon(undefined);\n return rawIcon;\n}\n\nexport interface ToolPaletteProps {\n tools: ToolsApi;\n orientation?: 'vertical' | 'horizontal';\n className?: string;\n /** Derive the keyboard shortcut for a tool button from the action registry.\n * Called with the tool's id; return `undefined` to suppress a chip. When\n * omitted, falls back to `tool.keybinding` (legacy path). */\n lookupShortcut?: (toolId: string) => ShortcutInput | undefined;\n /**\n * Optional mode registry. When provided, tools whose `capabilities` do not\n * match the current mode are rendered greyed-out, aria-disabled, and their\n * onClick is suppressed. When omitted, all tools are treated as eligible\n * (preserves existing behaviour in consumers that haven't wired a registry).\n */\n modeRegistry?: ModeRegistry;\n /**\n * Presentation-group display order. Groups not listed keep their registry\n * insertion order after the listed ones; `misc` always renders last.\n * Defaults to `['select', 'shape', 'draw', 'type', 'view']`.\n */\n groupOrder?: readonly string[];\n}\n\nexport function ToolPalette(props: ToolPaletteProps) {\n const {\n tools, orientation = 'vertical', className, lookupShortcut, modeRegistry,\n groupOrder = DEFAULT_GROUP_ORDER,\n } = props;\n const list = Object.values(tools.registry);\n const groups = partitionByGroup(list);\n const groupKeys = orderedGroupKeys(groups, groupOrder);\n const cls = [s.palette, orientation === 'horizontal' && s.horizontal, className]\n .filter(Boolean).join(' ');\n\n const orderedIds = groupKeys.flatMap((k) => groups.get(k)!.map((t) => t.id));\n const tabbableId = tools.active && orderedIds.includes(tools.active)\n ? tools.active\n : orderedIds[0];\n const rootRef = useRef<HTMLDivElement | null>(null);\n\n function onKeyDown(e: React.KeyboardEvent<HTMLDivElement>) {\n const target = e.target as HTMLElement;\n if (target.tagName !== 'BUTTON') return;\n const root = rootRef.current;\n if (!root) return;\n const buttons = Array.from(root.querySelectorAll<HTMLButtonElement>('button'));\n const i = buttons.indexOf(target as HTMLButtonElement);\n if (i < 0) return;\n\n let next = -1;\n switch (e.key) {\n case 'ArrowDown':\n case 'ArrowRight':\n next = (i + 1) % buttons.length;\n break;\n case 'ArrowUp':\n case 'ArrowLeft':\n next = (i - 1 + buttons.length) % buttons.length;\n break;\n case 'Home':\n next = 0;\n break;\n case 'End':\n next = buttons.length - 1;\n break;\n }\n if (next >= 0) {\n e.preventDefault();\n buttons[next].focus();\n }\n }\n\n return (\n <div\n ref={rootRef}\n className={cls}\n role=\"toolbar\"\n aria-label=\"Tools\"\n onKeyDown={onKeyDown}\n >\n {groupKeys.map((key, i) => (\n <Fragment key={key}>\n {i > 0 && (\n <div\n className={s.separator}\n role=\"separator\"\n aria-orientation={orientation === 'horizontal' ? 'vertical' : 'horizontal'}\n />\n )}\n <ToolGroup orientation={orientation} groupKey={key}>\n {groups.get(key)!.map((tool) => {\n const label = tool.presentation?.label ?? tool.id;\n const shortcut = tool.presentation?.shortcut\n ?? formatShortcut(lookupShortcut ? lookupShortcut(tool.id) : tool.keybinding);\n const enabled = modeRegistry ? eligibleTool(modeRegistry, tool) : true;\n const modeName = modeRegistry ? modeRegistry.current().id : undefined;\n const resolvedTitle = !enabled && modeName\n ? `${label}${shortcut ? ` (${shortcut})` : ''} — disabled in ${modeName} mode`\n : undefined;\n return (\n <ToolButton\n key={tool.id}\n icon={resolveIcon(tool)}\n label={label}\n shortcut={shortcut}\n active={tools.active === tool.id}\n tabbable={tool.id === tabbableId}\n ariaDisabled={!enabled}\n className={!enabled ? s.ineligibleBtn : undefined}\n title={resolvedTitle}\n onClick={enabled ? () => tools.setActive(tool.id) : () => {}}\n />\n );\n })}\n </ToolGroup>\n </Fragment>\n ))}\n </div>\n );\n}\n"],"mappings":"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"}
|