@kanzo-tech/ui 0.14.0 → 0.15.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/analytics.d.ts +1 -1
- package/dist/analytics.d.ts.map +1 -1
- package/dist/analytics.js +35 -36
- package/dist/composites/CodeEditor.d.ts +1 -1
- package/dist/composites/CodeEditor.d.ts.map +1 -1
- package/dist/composites/CodeEditor.js +79 -78
- package/dist/composites/CodeEditor.js.map +1 -1
- package/dist/guard-corpus.d.ts +21 -5
- package/dist/guard-corpus.d.ts.map +1 -1
- package/dist/simples/button.js +21 -21
- package/dist/simples/button.js.map +1 -1
- package/dist/simples/command.js +1 -1
- package/dist/simples/command.js.map +1 -1
- package/dist/simples/dialog.js +1 -1
- package/dist/simples/dialog.js.map +1 -1
- package/dist/simples/hover-card.js +4 -4
- package/dist/simples/hover-card.js.map +1 -1
- package/dist/simples/input-group.js +1 -1
- package/dist/simples/input-group.js.map +1 -1
- package/dist/simples/menu.js +5 -5
- package/dist/simples/menu.js.map +1 -1
- package/dist/simples/popover.js +1 -1
- package/dist/simples/popover.js.map +1 -1
- package/package.json +3 -3
- package/src/tailwind.css +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CodeEditor.js","sources":["../../src/composites/CodeEditor.tsx"],"sourcesContent":["\"use client\";\n\nimport { type ReactNode, useEffect, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { cn } from \"../lib/cn.js\";\nimport { type ReactPanelHost, kanzoSearch } from \"./code-editor-search.js\";\nimport { useKanzoThemeOptional } from \"../theme/theme-context.js\";\nimport { Annotation, Compartment, EditorState, type Extension, RangeSet } from \"@codemirror/state\";\nimport {\n Decoration,\n type DecorationSet,\n EditorView,\n GutterMarker,\n ViewPlugin,\n type ViewUpdate,\n crosshairCursor,\n drawSelection,\n dropCursor,\n gutterLineClass,\n highlightSpecialChars,\n keymap,\n lineNumbers as cmLineNumbers,\n placeholder as cmPlaceholder,\n rectangularSelection,\n} from \"@codemirror/view\";\nimport { defaultKeymap, history, historyKeymap } from \"@codemirror/commands\";\nimport {\n HighlightStyle,\n bracketMatching,\n foldGutter,\n foldKeymap,\n indentOnInput,\n syntaxHighlighting,\n} from \"@codemirror/language\";\nimport { autocompletion, closeBrackets, closeBracketsKeymap, completionKeymap } from \"@codemirror/autocomplete\";\nimport { highlightSelectionMatches, searchKeymap } from \"@codemirror/search\";\nimport { tags as t } from \"@lezer/highlight\";\n\n/**\n * A **CodeMirror 6** editing surface — the reusable lifecycle core (view\n * create/destroy, controlled-value reconcile, live-reconfigured `extensions`\n * slot, readOnly toggle) plus optional batteries + chrome. Domain-free: it knows\n * nothing about any specific language — the consumer injects that via `extensions`.\n *\n * Two consumer shapes, both real:\n * · **Batteries-included** (default): history + default keymap + line wrapping +\n * the Kanzo-tokened theme, wrapped in a styled focus-ring chrome. For quick\n * editors (a JSON field, a code preview).\n * · **Bring-your-own** (`basics={false} chrome={false}`): a bare surface driven\n * entirely by the caller's `extensions`, for a mature editor that owns its own\n * theme/keymap/behaviour (e.g. fossil's LSP editor). `onView` hands back the\n * `EditorView` for side-wiring.\n */\n\nconst External = Annotation.define<boolean>();\n\n/**\n * Kanzo syntax theme — maps Lezer highlight tags to the design tokens, so a consumer's language\n * extension is coloured by whichever palette document is on the page. Exported so a\n * bring-your-own editor can wire the same palette. Inert until the caller injects a language\n * (there is no tree to tag).\n *\n * **Seven of these are `--syntax-*` and the rest are tokens that already existed**, which is the\n * shape the palette layer settled on: `comment` is `--faint` (the quietest legible ink — a gutter\n * number, a field's placeholder and a code comment are one decision), `punctuation` is\n * `--muted-foreground`, `operator` is `--foreground`, and `invalid` is the destructive family.\n * There were thirteen `--syntax-*` tokens; six of them were duplicating a tint the ramp\n * already publishes.\n *\n * The seven that remain are derived per tenant and graded against `--editor-active-line`, so a\n * palette finally repaints keywords: Dracula's pink, Nord's purple, Catppuccin's mauve. Every\n * document used to declare Kanzo's.\n */\nexport const kanzoHighlightStyle = HighlightStyle.define([\n { tag: [t.keyword, t.operatorKeyword, t.controlKeyword, t.definitionKeyword, t.moduleKeyword, t.self], color: \"var(--syntax-keyword)\" },\n { tag: [t.atom, t.bool, t.constant(t.name), t.standard(t.name)], color: \"var(--syntax-number)\" },\n { tag: [t.string, t.special(t.string), t.docString, t.character, t.regexp], color: \"var(--syntax-string)\" },\n // An escape sequence inside a string used to fall through to --foreground, so `\\n` rendered\n // as plain text mid-string. `t.escape`'s parent is `literal`, which was unmapped.\n { tag: [t.escape, t.special(t.brace)], color: \"var(--syntax-number)\" },\n { tag: [t.number, t.integer, t.float, t.unit], color: \"var(--syntax-number)\" },\n { tag: [t.comment, t.lineComment, t.blockComment, t.docComment], color: \"var(--faint)\", fontStyle: \"italic\" },\n // Object keys and attribute names. These used to be grouped with `variableName`, which\n // resolved to a token byte-identical to --foreground — so every key in a JSON document\n // rendered as unstyled text and the whole sample looked near-monochrome.\n { tag: [t.propertyName, t.attributeName], color: \"var(--syntax-property)\" },\n { tag: [t.function(t.variableName), t.function(t.propertyName), t.macroName], color: \"var(--syntax-function)\" },\n // Declarations are coloured; plain variable USES inherit --foreground, as in most themes.\n { tag: [t.definition(t.variableName), t.definition(t.propertyName)], color: \"var(--syntax-identifier)\" },\n { tag: [t.operator, t.derefOperator, t.arithmeticOperator, t.logicOperator, t.bitwiseOperator, t.compareOperator, t.updateOperator], color: \"var(--foreground)\" },\n { tag: [t.punctuation, t.separator, t.bracket, t.angleBracket, t.squareBracket, t.paren, t.brace], color: \"var(--muted-foreground)\" },\n { tag: [t.typeName, t.className, t.namespace, t.tagName, t.labelName], color: \"var(--syntax-type)\" },\n { tag: [t.meta, t.annotation, t.processingInstruction, t.documentMeta], color: \"var(--faint)\" },\n { tag: [t.url, t.link], color: \"var(--syntax-function)\", textDecoration: \"underline\" },\n // Markdown: only `heading` was mapped, so prose rendered flat.\n { tag: t.heading, color: \"var(--syntax-keyword)\", fontWeight: \"bold\" },\n { tag: t.emphasis, fontStyle: \"italic\" },\n { tag: t.strong, fontWeight: \"bold\" },\n { tag: t.strikethrough, textDecoration: \"line-through\" },\n { tag: [t.monospace, t.list], color: \"var(--syntax-string)\" },\n { tag: t.quote, color: \"var(--faint)\" },\n // Diff.\n { tag: t.inserted, color: \"var(--success)\" },\n { tag: t.deleted, color: \"var(--destructive)\" },\n { tag: t.changed, color: \"var(--warning)\" },\n { tag: t.invalid, color: \"var(--destructive-foreground)\" },\n]);\n\n/** The Kanzo highlight style as a ready-to-drop extension. */\nexport const kanzoHighlighting: Extension = syntaxHighlighting(kanzoHighlightStyle, { fallback: true });\n\n/**\n * The caret's line — **and only while nothing is selected.**\n *\n * `highlightActiveLine` decorates the line under every range's `head` whether the range is empty or\n * not (`@codemirror/view@6.43.4`), so one line of a selection also wore the active-line band. Ours\n * is `--muted`, which is *lighter* than the selection tint and runs the full width, so that line\n * read as hovered rather than selected: two affordances arguing over one row. VS Code drops its\n * line highlight while a selection exists, and this is that.\n *\n * Reimplemented rather than suppressed with a CSS rule. Overriding upstream would leave two\n * extensions drawing the same decoration and the reason for it nowhere near the theme.\n */\nconst caretLine = Decoration.line({ class: \"cm-activeLine\" });\n\nconst caretLineGutter = new (class extends GutterMarker {\n override elementClass = \"cm-activeLineGutter\";\n})();\n\n/** The line start of each caret, empty while any range holds a selection. Ranges arrive sorted. */\nconst caretLines = (state: EditorState): number[] => {\n if (state.selection.ranges.some((r) => !r.empty)) return [];\n const starts: number[] = [];\n let last = -1;\n for (const r of state.selection.ranges) {\n const { from } = state.doc.lineAt(r.head);\n if (from > last) {\n starts.push(from);\n last = from;\n }\n }\n return starts;\n};\n\nconst highlightCaretLine = ViewPlugin.fromClass(\n class {\n decorations: DecorationSet;\n constructor(view: EditorView) {\n this.decorations = build(view.state);\n }\n update(u: ViewUpdate) {\n if (u.docChanged || u.selectionSet) this.decorations = build(u.state);\n }\n },\n { decorations: (v) => v.decorations },\n);\n\nconst build = (state: EditorState): DecorationSet =>\n Decoration.set(caretLines(state).map((from) => caretLine.range(from)));\n\nconst highlightCaretLineGutter = gutterLineClass.compute([\"selection\", \"doc\"], (state) =>\n RangeSet.of(caretLines(state).map((from) => caretLineGutter.range(from))),\n);\n\nconst baseTheme = EditorView.theme({\n // `flex: 1`, not `height: 100%`: the chrome surface sizes with `min-height`/`max-height`, and\n // a percentage height does not resolve against a `min-height`'d parent — so the editor stayed\n // at content height and the field's extra `min-height` showed as dead space below the last\n // line. As a flex child of the flex-column surface it fills the field; `min-height: 0` lets it\n // shrink so `.cm-scroller` scrolls once content passes `max-height`.\n \"&\": { fontSize: \"var(--kanzo-font-size-base, 14px)\", backgroundColor: \"transparent\", flex: \"1 1 auto\", minHeight: 0 },\n // NO PADDING AT ALL any more, and that is the fix for a gap somebody could see: the vertical\n // padding used to live here, and `.cm-line` sits INSIDE it — so the active-line highlight on the\n // first line started half a rem below the top of the field and read as a bar floating in a\n // margin. It moved to `.cm-scroller`, which contains the gutter as well as the content, so both\n // shift together and the numbers stay on their lines.\n //\n // NO BACKGROUND either, and no horizontal padding. Both were here and both were wrong:\n //\n // · An opaque background on `.cm-content` HIDES THE SELECTION. `drawSelection` paints into\n // `.cm-selectionLayer`, a sibling at `z-index: -2`, and CSS paints negative-z-index\n // descendants *below* the backgrounds of in-flow block-level siblings — so the paper\n // covered the layer completely. There is no fallback either: `drawSelection` forces the\n // native `::selection` to transparent, so a selection rendered as nothing at all and the\n // selected glyphs went invisible. Verified in the browser, and it is why no CodeMirror\n // theme upstream puts a background here. The paper is on `.cm-scroller` now.\n // · Horizontal padding here inset the ACTIVE LINE highlight by 12px at each edge, because\n // the highlight is `.cm-line`'s own background and `.cm-line` sits inside the padding\n // box. The row read as a floating bar rather than a highlighted line. The inset moved to\n // `.cm-line`, so the highlight spans the field and the text keeps the same rhythm.\n \".cm-content\": {\n // Explicitly zero, and as the SHORTHAND: CodeMirror's base theme ships `padding: 4px 0` here,\n // and a longhand `padding-block: 0` beside a shorthand loses — measured in the browser, the\n // content kept its 4px and the gutter numbers drifted 4px off their lines. That 4px was the\n // last of the gap above the first line's highlight.\n padding: 0,\n color: \"var(--foreground)\",\n caretColor: \"var(--foreground)\",\n },\n // The field rhythm — 0.75rem is Textarea's `px-3`, so code sits on the same inset as an\n // input's text (rem-based, so it tracks the density preference). On the LINE, so every\n // full-width line decoration (active line, and any caller's `Decoration.line`) reaches the\n // edges instead of stopping short of them.\n //\n // The inset is a HANGING one: a wrapped line resumes past the first row's start, so a Turtle URI\n // that does not fit reads as a continuation rather than as a new statement. `basics` turns\n // `EditorView.lineWrapping` on, so in a narrow pane most lines are wrapped ones — which is where\n // the editor is used: a dock beside a canvas, an aside beside a table.\n \".cm-line\": {\n paddingBlock: 0,\n paddingInlineStart: \"2rem\",\n paddingInlineEnd: \"0.75rem\",\n textIndent: \"-1.25rem\",\n },\n // The font belongs on `.cm-scroller`, not `.cm-content`: the GUTTER is a child of the\n // scroller, and CodeMirror's base sets `.cm-scroller { font-family: monospace }`. With the\n // family only on the content, line numbers rendered in the browser's generic monospace —\n // Courier on many systems — beside content in the Kanzo stack. It also made the gutter's\n // `min-width: 2.25ch` compute against the wrong font.\n \".cm-scroller\": {\n // NO VERTICAL INSET, on either surface. It was `0.5rem` here, on the argument that a code field\n // wants the same breathing room `Textarea` has — and what it actually produced was nine pixels\n // between the field's border and the first line, and a first line that does not start where\n // every other box on the screen starts. A pane docked under its own header showed it worst.\n // A caller who wants an inset has `className` and the surface it owns.\n fontFamily: \"var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace)\",\n lineHeight: \"1.5\",\n overflow: \"auto\",\n // Fill the editor so clicking the empty area below the last line still lands the caret,\n // rather than leaving dead surface — see the `&` note.\n flexGrow: 1,\n minHeight: 0,\n // The paper. It lives here and NOT on `.cm-content` — see the note there: an opaque\n // background on the content box hides the selection layer beneath it. The scroller fills\n // the whole field (flexGrow above), so the paper reaches the bottom on a short document.\n background: \"var(--background)\",\n },\n \"&.cm-focused\": { outline: \"none\" },\n // The gutter column carries its own tint. It used to be painted by the scroller instead, on\n // the grounds that a fill here \"only spans the content rows and stops mid-field on a short\n // document\" — measured against the current CodeMirror, that is no longer true: this element\n // is sized to the full scroller height, so the column reaches the bottom. Reclaiming it\n // freed the scroller to hold the paper, which is what un-hides the selection layer.\n // No divider; the tint step from `--muted` to the scroller's `--background` is the separation.\n \".cm-gutters\": {\n // NO vertical padding: the scroller's is the only one, and the gutter is inside it, so every\n // number already sits at its line's y. Repeating it here dropped each number one step too\n // low — verified back when the padding was on `.cm-content`: a constant 9px below its line.\n background: \"var(--muted)\",\n border: \"none\",\n color: \"var(--faint)\",\n },\n // Line numbers: tabular figures (no jitter across 9→10→100), right-aligned with breathing\n // room from the border edge, and a comfortable minimum width.\n \".cm-lineNumbers .cm-gutterElement\": {\n fontVariantNumeric: \"tabular-nums\",\n fontFeatureSettings: '\"tnum\"',\n minWidth: \"2.25ch\",\n padding: \"0 0.5rem 0 0.75rem\",\n textAlign: \"right\",\n cursor: \"pointer\",\n transition: \"color 120ms, background-color 120ms\",\n },\n // A line number is a click target (it selects the line), so it has to answer the\n // pointer — without this the whole column reads as inert decoration.\n // Quieter than the active line on purpose: this is a hover affordance, and at equal\n // strength the two read as the same state and the column flickers as the pointer moves.\n \".cm-lineNumbers .cm-gutterElement:hover\": {\n backgroundColor: \"color-mix(in srgb, var(--editor-active-line, var(--muted)) 60%, transparent)\",\n color: \"var(--foreground)\",\n },\n \".cm-foldGutter .cm-gutterElement:hover\": { color: \"var(--foreground)\" },\n \".cm-placeholder\": { color: \"var(--muted-foreground)\" },\n // `highlightSpecialChars` ships its own `&light`/`&dark` rule at a raw `red` / `#f78`. Whichever\n // half the appearance selects is a raw hue nobody chose. Tokenised here, at equal specificity and\n // later in the sheet, so ours wins in both.\n \".cm-specialChar\": { color: \"var(--destructive-foreground)\" },\n\n // ── Floating surfaces ─────────────────────────────────────────────────────────\n // CodeMirror ships its own light-mode chrome for tooltips, autocomplete and the\n // search panel. Left alone they arrive as white boxes with system-blue selection —\n // the moment an editor is more than a text box, that is where the theme visibly\n // breaks. These read from the same tokens as `popover` / `menu`.\n \".cm-tooltip\": {\n background: \"var(--popover)\",\n color: \"var(--popover-foreground)\",\n border: \"1px solid var(--border)\",\n borderRadius: \"var(--radius-md)\",\n boxShadow: \"0 4px 12px rgb(0 0 0 / 0.08)\",\n overflow: \"hidden\",\n },\n // The divider between an autocomplete tooltip's sections. CodeMirror's `&light` default is\n // `1px solid #bbb`, and it is the half with no `&dark` counterpart — so before the facet followed\n // the appearance it drew a pale hairline inside a dark popover, and it is *still* the rule that\n // covers a light page. Found by `codemirror-dark-parity.test.ts`, not by looking.\n \".cm-tooltip-section:not(:first-child)\": { borderTop: \"1px solid var(--border)\" },\n \".cm-tooltip .cm-tooltip-arrow:before\": { borderTopColor: \"var(--border)\" },\n \".cm-tooltip .cm-tooltip-arrow:after\": { borderTopColor: \"var(--popover)\" },\n \".cm-tooltip-autocomplete > ul\": {\n fontFamily: \"var(--font-mono, ui-monospace, monospace)\",\n maxHeight: \"16rem\",\n },\n \".cm-tooltip-autocomplete > ul > li\": {\n padding: \"0.125rem 0.5rem\",\n lineHeight: \"1.6\",\n },\n \".cm-tooltip-autocomplete > ul > li[aria-selected]\": {\n background: \"var(--accent)\",\n color: \"var(--accent-foreground)\",\n },\n \".cm-completionLabel\": { color: \"inherit\" },\n \".cm-completionMatchedText\": {\n color: \"var(--primary)\",\n fontWeight: \"600\",\n textDecoration: \"none\",\n },\n \".cm-completionDetail\": {\n color: \"var(--muted-foreground)\",\n fontStyle: \"normal\",\n marginInlineStart: \"0.5rem\",\n },\n \".cm-completionIcon\": { opacity: 0.64, paddingInlineEnd: \"0.5rem\" },\n\n \".cm-panels\": {\n background: \"var(--card)\",\n color: \"var(--card-foreground)\",\n borderColor: \"var(--border)\",\n },\n \".cm-panels.cm-panels-top\": { borderBottom: \"1px solid var(--border)\" },\n \".cm-panels.cm-panels-bottom\": { borderTop: \"1px solid var(--border)\" },\n\n // **`.cm-textfield` and `.cm-button` are no longer OUR controls, and that is the whole point of\n // `code-editor-search.tsx`.** The find/replace panel is `InputGroup`, `Toggle`, `Button` and\n // `ButtonGroup` now, rendered through a portal, so it wears the library's typography, focus ring\n // and hover states rather than a run of rules restating them in CSS. Six selectors went with it:\n // `.cm-panel.cm-search`, its `input, button, label { font-size: inherit }`, the two\n // `.cm-textfield` rules and the two for `button[name=close]`.\n //\n // These two stay, unscoped, for the controls that are still CodeMirror's: `gotoLine`'s dialog\n // (Mod-Alt-G), and any panel a caller's own extensions bring — `@codemirror/lint`'s is the\n // obvious one, and it is a caller's dependency rather than ours.\n //\n // **They also have to stay because they are the only thing standing between a page and a box\n // nobody themed.** `&light .cm-textfield { background: white; border: 1px solid silver }` and\n // `&light .cm-button { background: linear-gradient(#eff1f5, #d9d9df) }` are one half of it and\n // `&dark`'s `#333` and `linear-gradient(#393939, #111)` are the other; the `darkTheme` facet picks\n // which, and neither is a token. `codemirror-dark-parity.test.ts` checks BOTH halves and fails the\n // day either selector leaves this file. The previous\n // spelling was `.cm-panel.cm-search .cm-textfield`, which covered the search field and left\n // `gotoLine`'s identical field wearing the white default; before that it was `input[type=text]`,\n // which matched nothing at all, because CodeMirror sets no `type` on it.\n \".cm-textfield\": {\n background: \"var(--background)\",\n color: \"var(--foreground)\",\n border: \"1px solid var(--input)\",\n borderRadius: \"var(--radius-sm)\",\n padding: \"0.125rem 0.375rem\",\n outline: \"none\",\n },\n \".cm-textfield:focus\": {\n borderColor: \"var(--ring)\",\n boxShadow: \"0 0 0 3px color-mix(in srgb, var(--ring) 32%, transparent)\",\n },\n \".cm-button\": {\n background: \"var(--secondary)\",\n color: \"var(--secondary-foreground)\",\n border: \"1px solid transparent\",\n borderRadius: \"var(--radius-sm)\",\n backgroundImage: \"none\",\n padding: \"0.125rem 0.5rem\",\n cursor: \"pointer\",\n },\n \".cm-button:hover\": { background: \"var(--accent)\" },\n \".cm-button:active\": { backgroundImage: \"none\" },\n \".cm-dialog\": { padding: \"0.375rem 0.5rem\", fontSize: \"var(--kanzo-font-size-small, 12px)\" },\n \".cm-dialog label\": { display: \"inline-flex\", alignItems: \"center\", gap: \"0.375rem\" },\n \".cm-dialog-close\": { color: \"var(--muted-foreground)\", cursor: \"pointer\" },\n \".cm-dialog-close:hover\": { color: \"var(--foreground)\" },\n\n // Search hits: the current one is the primary-tinted anchor, the rest are quieter so\n // \"where am I\" stays readable at a glance.\n \".cm-searchMatch\": {\n backgroundColor: \"var(--warning-a5)\",\n outline: \"1px solid var(--border)\",\n borderRadius: \"2px\",\n },\n \".cm-searchMatch.cm-searchMatch-selected\": {\n backgroundColor: \"var(--warning-a8)\",\n outline: \"1px solid var(--warning)\",\n },\n\n // Lint diagnostics — semantic tokens, so severity survives a re-theme.\n \".cm-diagnostic\": {\n padding: \"0.25rem 0.5rem\",\n borderInlineStartWidth: \"3px\",\n borderInlineStartStyle: \"solid\",\n fontFamily: \"var(--font-sans)\",\n },\n \".cm-diagnostic-error\": { borderInlineStartColor: \"var(--destructive)\" },\n \".cm-diagnostic-warning\": { borderInlineStartColor: \"var(--warning)\" },\n \".cm-diagnostic-info\": { borderInlineStartColor: \"var(--info)\" },\n \".cm-lintPoint-error:after\": { borderBottomColor: \"var(--destructive)\" },\n \".cm-lintPoint-warning:after\": { borderBottomColor: \"var(--warning)\" },\n\n // Scrollbars: the default OS chrome is the loudest thing in a dark editor.\n \".cm-scroller::-webkit-scrollbar\": { width: \"10px\", height: \"10px\" },\n \".cm-scroller::-webkit-scrollbar-track\": { background: \"transparent\" },\n \".cm-scroller::-webkit-scrollbar-thumb\": {\n background: \"color-mix(in srgb, var(--muted-foreground) 32%, transparent)\",\n borderRadius: \"6px\",\n border: \"2px solid transparent\",\n backgroundClip: \"content-box\",\n },\n \".cm-scroller::-webkit-scrollbar-thumb:hover\": {\n background: \"color-mix(in srgb, var(--muted-foreground) 56%, transparent)\",\n backgroundClip: \"content-box\",\n },\n \".cm-scroller::-webkit-scrollbar-corner\": { background: \"transparent\" },\n // Tokenised selection (drawSelection), active line, matching bracket, search matches.\n \".cm-cursor, .cm-dropCursor\": { borderLeftColor: \"var(--foreground)\" },\n // The full child chain is LOAD-BEARING. CodeMirror's base theme ships\n // `&light.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground`\n // (@codemirror/view), which compiles to specificity 0,5,0. A flat\n // `&.cm-focused .cm-selectionBackground` is 0,3,0 and LOSES — so the focused selection\n // rendered in CodeMirror's stock lavender (#d7d4f0) instead of a Kanzo token. Worse, that\n // base rule is `&light`, and for as long as the `darkTheme` facet was pinned false the light\n // lavender applied in dark mode too. It is driven from the appearance now, which moves the\n // default to `&dark`'s — a different stock colour, still not ours, still covered by this rule.\n \"&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground\": {\n backgroundColor: \"var(--brand-a5)\",\n },\n // **A selection must not repaint the text, and ours repainted all of it.** `tokens.css` sets a\n // document-wide `::selection { bg-primary/80 text-primary-foreground }`, which is right\n // everywhere prose is selected and wrong inside an editor: `drawSelection` suppresses the native\n // selection's *background* and not its `color`, so the fill came from `.cm-selectionBackground`\n // — `--brand-a5`, black at 12% — while every glyph inside it took `--primary-foreground`.\n //\n // Measured on `/docs/forms/code-editor`: #fafafa text on a #dcdcdc block. Keys, strings and\n // numbers all flattened to one near-white, so selecting seven lines of JSON erased the\n // highlighting from them. It happens under every palette, because a `-foreground` role is a\n // contrast colour for a fill that is not being painted here.\n //\n // `currentColor` is the fix, and it has to be `currentColor` and not a token: a fixed colour\n // flattens the tokens just as thoroughly, one shade later.\n \".cm-selectionBackground, .cm-content ::selection\": {\n backgroundColor: \"var(--brand-a5)\",\n color: \"currentColor\",\n },\n \".cm-activeLine\": { backgroundColor: \"var(--editor-active-line, var(--muted))\" },\n // The active line's gutter cell is emphasised beyond the row: stronger tint, full-strength\n // ink and a weight bump so the current line number stands out from the dim column.\n \".cm-activeLineGutter\": {\n backgroundColor: \"var(--editor-active-line, var(--muted))\",\n color: \"var(--foreground)\",\n fontWeight: \"600\",\n },\n \".cm-matchingBracket, &.cm-focused .cm-matchingBracket\": { backgroundColor: \"var(--brand-a5)\", outline: \"1px solid var(--primary)\" },\n \".cm-nonmatchingBracket\": { backgroundColor: \"var(--destructive-a4)\" },\n // **A neutral tint, because it is not a search hit.** This wore `--warning-a5`, byte for byte\n // what `.cm-searchMatch` wears — so the other occurrences of the word under your caret were\n // indistinguishable from the hits of a query you actually typed, and the two co-occur constantly:\n // open the find panel while a word is selected and every match looks like the same kind of thing.\n //\n // They are not. A search hit is something you asked for; this is the editor noticing a\n // repetition. Every editor that draws both separates them the same way — the query keeps the\n // colour, the passive one goes neutral — and `--base-a5` is the house's neutral wash. It also\n // takes the collision off `--warning-a5`, which `Highlight` uses for a search-term mark in prose.\n \".cm-selectionMatch\": { backgroundColor: \"var(--base-a5)\" },\n \".cm-foldGutter .cm-gutterElement\": { cursor: \"pointer\", color: \"var(--faint)\" },\n \".cm-foldPlaceholder\": { background: \"var(--muted)\", border: \"1px solid var(--border)\", color: \"var(--muted-foreground)\", borderRadius: \"var(--radius-sm)\", padding: \"0 4px\" },\n});\n\n/**\n * **The base theme's other half, switched on by the side the page is wearing.**\n *\n * `EditorView.theme(spec, { dark })` bakes the answer into the theme, and ours cannot: it is ONE\n * theme for both appearances, because the tokens flip underneath it. So the facet is set beside the\n * theme instead of by it, from `resolvedAppearance`, and `codemirror-dark-parity.test.ts` widened to\n * both halves the day this landed — until now only `&light` could ever apply, and now either can.\n *\n * `false` is spelled as *nothing*, not as `of(false)`: the facet combines with\n * `values.indexOf(true) > -1`, so a `false` from us would not switch off a `true` a caller's own\n * theme contributed, and pretending otherwise would be the more confusing lie.\n */\nconst darkFacet = (dark: boolean): Extension => (dark ? EditorView.darkTheme.of(true) : []);\n\nexport interface CodeEditorProps {\n value: string;\n onChange?: (value: string) => void;\n /** The language brain — CodeMirror extensions injected by the consumer (LSP,\n * StreamParser, autocomplete, linting…). Reconfigured live when it changes. */\n extensions?: Extension;\n readOnly?: boolean;\n lineNumbers?: boolean;\n invalid?: boolean;\n placeholder?: string;\n minHeight?: string;\n maxHeight?: string;\n /** Include the built-in batteries (history + default keymap + line wrapping +\n * the Kanzo token theme). Default true. Set false when the caller's `extensions`\n * own all behaviour and theming. */\n basics?: boolean;\n /**\n * Wrap long lines, or scroll sideways. Default true, which is what `basics` always did.\n *\n * Wrapping is right where the editor is a reading surface in a narrow pane; it is wrong where\n * the document has structure a reader tracks by column — a Turtle predicate list read against\n * its indentation stops being a list once every third line reflows. There is no third option:\n * `EditorView.lineWrapping` is on or it is not.\n *\n * Read once, when the view is built — like `basics`, and for the same reason. Changing it after\n * mount does nothing.\n */\n wrap?: boolean;\n /**\n * Wrap the editor in the styled focus-ring chrome (Box + inset ring + surface background).\n * Default true. Set false for a bare surface.\n */\n chrome?: boolean;\n /** Class applied to the editor host (only meaningful when `chrome={false}`). */\n className?: string;\n /** Receives the `EditorView` on mount and `null` on unmount — for side-wiring\n * (e.g. pushing LSP notifications). */\n onView?: (view: EditorView | null) => void;\n}\n\nexport function CodeEditor(p: CodeEditorProps) {\n const container = useRef<HTMLDivElement>(null);\n const view = useRef<EditorView>(undefined);\n const [focused, setFocused] = useState(false);\n\n const props = useRef(p);\n props.current = p;\n\n const langSlot = useRef(new Compartment());\n const editable = useRef(new Compartment());\n const appearance = useRef(new Compartment());\n\n // Optional, like every other reader of the cascade in this package: without a provider the facet\n // stays false, which is where it sat before this existed, and the guard proves the `&light`\n // defaults that then apply are all overridden anyway.\n const dark = useKanzoThemeOptional()?.resolvedAppearance === \"dark\";\n const darkNow = useRef(dark);\n darkNow.current = dark;\n\n // **CodeMirror's panels, rendered by React.** A `Panel` is an object with a `dom`, and nothing\n // says who fills it — so we hand over an empty element and portal into it. The portals are\n // children of THIS tree, which is the reason the host lives here and not in the panel's own\n // module: a panel rendered from anywhere else loses every provider above the editor.\n //\n // `mount`/`destroy` are CodeMirror's, and they bracket the element's life in the document\n // exactly. `mount` runs before React has rendered anything into it, which is why a panel that\n // wants focus takes it in its own effect rather than relying on CodeMirror's.\n const [panels, setPanels] = useState<{ id: number; dom: HTMLElement; node: ReactNode }[]>([]);\n\n // Build the editor once; long-lived callbacks read the latest props via the ref.\n useEffect(() => {\n const basics = props.current.basics ?? true;\n let nextPanel = 0;\n const panelHost: ReactPanelHost = {\n panel: (node, opts) => {\n const dom = document.createElement(\"div\");\n const id = ++nextPanel;\n return {\n dom,\n top: opts?.top,\n mount: () => setPanels((open) => [...open, { id, dom, node }]),\n destroy: () => setPanels((open) => open.filter((entry) => entry.id !== id)),\n };\n },\n };\n const updateListener = EditorView.updateListener.of((u) => {\n if (u.focusChanged) setFocused(u.view.hasFocus);\n if (!u.docChanged) return;\n if (u.transactions.some((t) => t.annotation(External))) return; // our own reconcile\n props.current.onChange?.(u.state.doc.toString());\n });\n const wrap = props.current.wrap ?? true;\n const batteries: Extension[] = basics\n ? [\n history(),\n keymap.of([\n ...defaultKeymap,\n ...historyKeymap,\n ...searchKeymap,\n ...closeBracketsKeymap,\n ...completionKeymap,\n ...foldKeymap,\n ]),\n ...(wrap ? [EditorView.lineWrapping] : []),\n drawSelection(),\n dropCursor(),\n // `completionKeymap` was already bound below, but the extension itself was never\n // installed — so Ctrl-Space did nothing and the ~25 lines of\n // `.cm-tooltip-autocomplete` styling in this theme were dead CSS.\n autocompletion(),\n // Makes NBSP, zero-width and control characters visible. Without it they are\n // invisible in the document, which is a real footgun when editing data.\n highlightSpecialChars(),\n // Alt-drag column selection, and the crosshair cursor that signals it is available.\n rectangularSelection(),\n crosshairCursor(),\n highlightCaretLine,\n bracketMatching(),\n closeBrackets(),\n indentOnInput(),\n highlightSelectionMatches(),\n // **`search()` has to be in the configuration, and it was not.** `searchKeymap` was\n // bound below and nothing installed the extension, so `openSearchPanel` appended the\n // defaults itself the first time Mod-F was pressed — which works, and silently discards\n // any configuration, `createPanel` included. Same shape as the `autocompletion()` bug\n // noted further down: a keymap for an extension nobody added.\n kanzoSearch(panelHost),\n kanzoHighlighting,\n baseTheme,\n ]\n : [];\n const v = new EditorView({\n parent: container.current!,\n state: EditorState.create({\n doc: props.current.value ?? \"\",\n extensions: [\n // Multiple cursors. Off by default in CodeMirror, and table stakes in an editor.\n EditorState.allowMultipleSelections.of(true),\n ...batteries,\n ...(props.current.lineNumbers ? [cmLineNumbers()] : []),\n ...(basics && props.current.lineNumbers ? [highlightCaretLineGutter, foldGutter()] : []),\n ...(props.current.placeholder ? [cmPlaceholder(props.current.placeholder)] : []),\n updateListener,\n appearance.current.of(darkFacet(darkNow.current)),\n langSlot.current.of(props.current.extensions ?? []),\n editable.current.of([\n EditorView.editable.of(!props.current.readOnly),\n EditorState.readOnly.of(!!props.current.readOnly),\n ]),\n ],\n }),\n });\n view.current = v;\n props.current.onView?.(v);\n return () => {\n props.current.onView?.(null);\n v.destroy();\n };\n }, []);\n\n // Reconcile external value changes — but never clobber active typing.\n useEffect(() => {\n const v = view.current;\n if (!v || v.hasFocus) return;\n if (p.value !== v.state.doc.toString()) {\n v.dispatch({\n changes: { from: 0, to: v.state.doc.length, insert: p.value },\n annotations: External.of(true),\n });\n }\n }, [p.value]);\n\n // Swap the language extensions live (compartment reconfigure, no re-create).\n useEffect(() => {\n view.current?.dispatch({ effects: langSlot.current.reconfigure(p.extensions ?? []) });\n }, [p.extensions]);\n\n // Follow the page's side. Nothing of ours is keyed off the facet — our rules are unprefixed and\n // the tokens do the flipping — so what this moves is CodeMirror's own base theme and any theme a\n // caller's extensions bring.\n useEffect(() => {\n view.current?.dispatch({ effects: appearance.current.reconfigure(darkFacet(dark)) });\n }, [dark]);\n\n // Toggle editability when `readOnly` changes.\n useEffect(() => {\n view.current?.dispatch({\n effects: editable.current.reconfigure([\n EditorView.editable.of(!p.readOnly),\n EditorState.readOnly.of(!!p.readOnly),\n ]),\n });\n }, [p.readOnly]);\n\n // A portal contributes no DOM to the element it is written inside, so these are safe as children\n // of the very element CodeMirror owns: React mounts each node into the panel's `dom` and nothing\n // else. Written once and used by both branches below.\n const portals = panels.map((entry) => createPortal(entry.node, entry.dom, String(entry.id)));\n\n // Bare surface (caller owns theme/chrome).\n //\n // `flex flex-col` is LOAD-BEARING, not styling. The theme sizes the editor with\n // `\"&\": { flex: \"1 1 auto\", minHeight: 0 }`, which resolves only inside a flex container — and\n // this host was a plain block, so `.cm-editor` fell back to content height. A caller asking for\n // a full-height pane (`chrome={false} className=\"flex-1\"`) got a host that filled its parent\n // and an editor that did not fill the host: measured 467px of scroller inside an 883px pane,\n // with the rest dead. The chromed branch below never had the bug because its wrapper is\n // already a flex column.\n if (p.chrome === false) {\n return (\n <div\n className={cn(\"flex flex-col\", p.className)}\n data-slot=\"code-editor\"\n ref={container}\n style={{ minHeight: p.minHeight, maxHeight: p.maxHeight }}\n >\n {portals}\n </div>\n );\n }\n\n // A field, not an IDE pane: the surface wears the exact chrome as Textarea/Input — a\n // hairline `--input` border, `rounded-lg`, the transparent/`--field`-tinted surface, a\n // faint shadow, and the same focus/invalid treatment (border shifts colour + a 3px ring).\n // The ring is a `box-shadow` (Tailwind `ring`), drawn OUTSIDE the border box, so it is not\n // clipped by `overflow-hidden` and the gutter's own background cannot paint over it — the\n // failure the old inset approach worked around. Focus is driven off `data-focused`\n // (CodeMirror's contenteditable holds focus, not this wrapper, so `:focus-visible` can't).\n return (\n <div data-slot=\"code-editor\" style={{ flex: 1, width: \"100%\" }}>\n <div\n data-slot=\"code-editor-surface\"\n data-focused={focused || undefined}\n data-invalid={p.invalid || undefined}\n ref={container}\n className={cn(\n \"flex w-full flex-col overflow-hidden\",\n \"rounded-lg border border-input bg-transparent shadow-xs/5 bg-field\",\n \"transition-[color,box-shadow]\",\n \"data-focused:border-primary data-focused:ring-[3px] data-focused:ring-ring\",\n \"data-invalid:border-destructive data-invalid:ring-[3px] data-invalid:ring-destructive/24\",\n \"motion-reduce:transition-none!\"\n )}\n style={{ minHeight: p.minHeight, maxHeight: p.maxHeight }}\n >\n {portals}\n </div>\n </div>\n );\n}"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CodeEditor.js","sources":["../../src/composites/CodeEditor.tsx"],"sourcesContent":["\"use client\";\n\nimport { type ReactNode, useEffect, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { cn } from \"../lib/cn.js\";\nimport { type ReactPanelHost, kanzoSearch } from \"./code-editor-search.js\";\nimport { useKanzoThemeOptional } from \"../theme/theme-context.js\";\nimport { Annotation, Compartment, EditorState, type Extension, RangeSet } from \"@codemirror/state\";\nimport {\n Decoration,\n type DecorationSet,\n EditorView,\n GutterMarker,\n ViewPlugin,\n type ViewUpdate,\n crosshairCursor,\n drawSelection,\n dropCursor,\n gutterLineClass,\n highlightSpecialChars,\n keymap,\n lineNumbers as cmLineNumbers,\n placeholder as cmPlaceholder,\n rectangularSelection,\n} from \"@codemirror/view\";\nimport { defaultKeymap, history, historyKeymap } from \"@codemirror/commands\";\nimport {\n HighlightStyle,\n bracketMatching,\n foldGutter,\n foldKeymap,\n indentOnInput,\n syntaxHighlighting,\n} from \"@codemirror/language\";\nimport { autocompletion, closeBrackets, closeBracketsKeymap, completionKeymap } from \"@codemirror/autocomplete\";\nimport { highlightSelectionMatches, searchKeymap } from \"@codemirror/search\";\nimport { tags as t } from \"@lezer/highlight\";\n\n/**\n * A **CodeMirror 6** editing surface — the reusable lifecycle core (view\n * create/destroy, controlled-value reconcile, live-reconfigured `extensions`\n * slot, readOnly toggle) plus optional batteries + chrome. Domain-free: it knows\n * nothing about any specific language — the consumer injects that via `extensions`.\n *\n * Two consumer shapes, both real:\n * · **Batteries-included** (default): history + default keymap + line wrapping +\n * the Kanzo-tokened theme, wrapped in a styled focus-ring chrome. For quick\n * editors (a JSON field, a code preview).\n * · **Bring-your-own** (`basics={false} chrome={false}`): a bare surface driven\n * entirely by the caller's `extensions`, for a mature editor that owns its own\n * theme/keymap/behaviour (e.g. fossil's LSP editor). `onView` hands back the\n * `EditorView` for side-wiring.\n */\n\nconst External = Annotation.define<boolean>();\n\n/**\n * Kanzo syntax theme — maps Lezer highlight tags to the design tokens, so a consumer's language\n * extension is coloured by whichever palette document is on the page. Exported so a\n * bring-your-own editor can wire the same palette. Inert until the caller injects a language\n * (there is no tree to tag).\n *\n * **Seven of these are `--syntax-*` and the rest are tokens that already existed**, which is the\n * shape the palette layer settled on: `comment` is `--faint` (the quietest legible ink — a gutter\n * number, a field's placeholder and a code comment are one decision), `punctuation` is\n * `--muted-foreground`, `operator` is `--foreground`, and `invalid` is the destructive family.\n * There were thirteen `--syntax-*` tokens; six of them were duplicating a tint the ramp\n * already publishes.\n *\n * The seven that remain are authored per theme and graded against the active line (`--muted`), so a\n * palette finally repaints keywords: Dracula's pink, Nord's purple, Catppuccin's mauve. Every\n * document used to declare Kanzo's.\n */\nexport const kanzoHighlightStyle = HighlightStyle.define([\n { tag: [t.keyword, t.operatorKeyword, t.controlKeyword, t.definitionKeyword, t.moduleKeyword, t.self], color: \"var(--syntax-keyword)\" },\n { tag: [t.atom, t.bool, t.constant(t.name), t.standard(t.name)], color: \"var(--syntax-number)\" },\n { tag: [t.string, t.special(t.string), t.docString, t.character, t.regexp], color: \"var(--syntax-string)\" },\n // An escape sequence inside a string used to fall through to --foreground, so `\\n` rendered\n // as plain text mid-string. `t.escape`'s parent is `literal`, which was unmapped.\n { tag: [t.escape, t.special(t.brace)], color: \"var(--syntax-number)\" },\n { tag: [t.number, t.integer, t.float, t.unit], color: \"var(--syntax-number)\" },\n { tag: [t.comment, t.lineComment, t.blockComment, t.docComment], color: \"var(--faint, var(--muted-foreground))\", fontStyle: \"italic\" },\n // Object keys and attribute names. These used to be grouped with `variableName`, which\n // resolved to a token byte-identical to --foreground — so every key in a JSON document\n // rendered as unstyled text and the whole sample looked near-monochrome.\n { tag: [t.propertyName, t.attributeName], color: \"var(--syntax-property)\" },\n { tag: [t.function(t.variableName), t.function(t.propertyName), t.macroName], color: \"var(--syntax-function)\" },\n // Declarations are coloured; plain variable USES inherit --foreground, as in most themes.\n { tag: [t.definition(t.variableName), t.definition(t.propertyName)], color: \"var(--syntax-identifier)\" },\n { tag: [t.operator, t.derefOperator, t.arithmeticOperator, t.logicOperator, t.bitwiseOperator, t.compareOperator, t.updateOperator], color: \"var(--foreground)\" },\n { tag: [t.punctuation, t.separator, t.bracket, t.angleBracket, t.squareBracket, t.paren, t.brace], color: \"var(--muted-foreground)\" },\n { tag: [t.typeName, t.className, t.namespace, t.tagName, t.labelName], color: \"var(--syntax-type)\" },\n { tag: [t.meta, t.annotation, t.processingInstruction, t.documentMeta], color: \"var(--faint, var(--muted-foreground))\" },\n { tag: [t.url, t.link], color: \"var(--syntax-function)\", textDecoration: \"underline\" },\n // Markdown: only `heading` was mapped, so prose rendered flat.\n { tag: t.heading, color: \"var(--syntax-keyword)\", fontWeight: \"bold\" },\n { tag: t.emphasis, fontStyle: \"italic\" },\n { tag: t.strong, fontWeight: \"bold\" },\n { tag: t.strikethrough, textDecoration: \"line-through\" },\n { tag: [t.monospace, t.list], color: \"var(--syntax-string)\" },\n { tag: t.quote, color: \"var(--faint, var(--muted-foreground))\" },\n // Diff.\n { tag: t.inserted, color: \"var(--success)\" },\n { tag: t.deleted, color: \"var(--destructive)\" },\n { tag: t.changed, color: \"var(--warning)\" },\n { tag: t.invalid, color: \"var(--destructive-foreground)\" },\n]);\n\n/** The Kanzo highlight style as a ready-to-drop extension. */\nexport const kanzoHighlighting: Extension = syntaxHighlighting(kanzoHighlightStyle, { fallback: true });\n\n/**\n * The caret's line — **and only while nothing is selected.**\n *\n * `highlightActiveLine` decorates the line under every range's `head` whether the range is empty or\n * not (`@codemirror/view@6.43.4`), so one line of a selection also wore the active-line band. Ours\n * is `--muted`, which is *lighter* than the selection tint and runs the full width, so that line\n * read as hovered rather than selected: two affordances arguing over one row. VS Code drops its\n * line highlight while a selection exists, and this is that.\n *\n * Reimplemented rather than suppressed with a CSS rule. Overriding upstream would leave two\n * extensions drawing the same decoration and the reason for it nowhere near the theme.\n */\nconst caretLine = Decoration.line({ class: \"cm-activeLine\" });\n\nconst caretLineGutter = new (class extends GutterMarker {\n override elementClass = \"cm-activeLineGutter\";\n})();\n\n/** The line start of each caret, empty while any range holds a selection. Ranges arrive sorted. */\nconst caretLines = (state: EditorState): number[] => {\n if (state.selection.ranges.some((r) => !r.empty)) return [];\n const starts: number[] = [];\n let last = -1;\n for (const r of state.selection.ranges) {\n const { from } = state.doc.lineAt(r.head);\n if (from > last) {\n starts.push(from);\n last = from;\n }\n }\n return starts;\n};\n\nconst highlightCaretLine = ViewPlugin.fromClass(\n class {\n decorations: DecorationSet;\n constructor(view: EditorView) {\n this.decorations = build(view.state);\n }\n update(u: ViewUpdate) {\n if (u.docChanged || u.selectionSet) this.decorations = build(u.state);\n }\n },\n { decorations: (v) => v.decorations },\n);\n\nconst build = (state: EditorState): DecorationSet =>\n Decoration.set(caretLines(state).map((from) => caretLine.range(from)));\n\nconst highlightCaretLineGutter = gutterLineClass.compute([\"selection\", \"doc\"], (state) =>\n RangeSet.of(caretLines(state).map((from) => caretLineGutter.range(from))),\n);\n\nconst baseTheme = EditorView.theme({\n // `flex: 1`, not `height: 100%`: the chrome surface sizes with `min-height`/`max-height`, and\n // a percentage height does not resolve against a `min-height`'d parent — so the editor stayed\n // at content height and the field's extra `min-height` showed as dead space below the last\n // line. As a flex child of the flex-column surface it fills the field; `min-height: 0` lets it\n // shrink so `.cm-scroller` scrolls once content passes `max-height`.\n \"&\": { fontSize: \"var(--kanzo-font-size-base)\", backgroundColor: \"transparent\", flex: \"1 1 auto\", minHeight: 0 },\n // NO PADDING AT ALL any more, and that is the fix for a gap somebody could see: the vertical\n // padding used to live here, and `.cm-line` sits INSIDE it — so the active-line highlight on the\n // first line started half a rem below the top of the field and read as a bar floating in a\n // margin. It moved to `.cm-scroller`, which contains the gutter as well as the content, so both\n // shift together and the numbers stay on their lines.\n //\n // NO BACKGROUND either, and no horizontal padding. Both were here and both were wrong:\n //\n // · An opaque background on `.cm-content` HIDES THE SELECTION. `drawSelection` paints into\n // `.cm-selectionLayer`, a sibling at `z-index: -2`, and CSS paints negative-z-index\n // descendants *below* the backgrounds of in-flow block-level siblings — so the paper\n // covered the layer completely. There is no fallback either: `drawSelection` forces the\n // native `::selection` to transparent, so a selection rendered as nothing at all and the\n // selected glyphs went invisible. Verified in the browser, and it is why no CodeMirror\n // theme upstream puts a background here. The paper is on `.cm-scroller` now.\n // · Horizontal padding here inset the ACTIVE LINE highlight by 12px at each edge, because\n // the highlight is `.cm-line`'s own background and `.cm-line` sits inside the padding\n // box. The row read as a floating bar rather than a highlighted line. The inset moved to\n // `.cm-line`, so the highlight spans the field and the text keeps the same rhythm.\n \".cm-content\": {\n // Explicitly zero, and as the SHORTHAND: CodeMirror's base theme ships `padding: 4px 0` here,\n // and a longhand `padding-block: 0` beside a shorthand loses — measured in the browser, the\n // content kept its 4px and the gutter numbers drifted 4px off their lines. That 4px was the\n // last of the gap above the first line's highlight.\n padding: 0,\n color: \"var(--foreground)\",\n caretColor: \"var(--foreground)\",\n },\n // The field rhythm — 0.75rem is Textarea's `px-3`, so code sits on the same inset as an\n // input's text (rem-based, so it tracks the density preference). On the LINE, so every\n // full-width line decoration (active line, and any caller's `Decoration.line`) reaches the\n // edges instead of stopping short of them.\n //\n // The inset is a HANGING one: a wrapped line resumes past the first row's start, so a Turtle URI\n // that does not fit reads as a continuation rather than as a new statement. `basics` turns\n // `EditorView.lineWrapping` on, so in a narrow pane most lines are wrapped ones — which is where\n // the editor is used: a dock beside a canvas, an aside beside a table.\n \".cm-line\": {\n paddingBlock: 0,\n paddingInlineStart: \"2rem\",\n paddingInlineEnd: \"0.75rem\",\n textIndent: \"-1.25rem\",\n },\n // The font belongs on `.cm-scroller`, not `.cm-content`: the GUTTER is a child of the\n // scroller, and CodeMirror's base sets `.cm-scroller { font-family: monospace }`. With the\n // family only on the content, line numbers rendered in the browser's generic monospace —\n // Courier on many systems — beside content in the Kanzo stack. It also made the gutter's\n // `min-width: 2.25ch` compute against the wrong font.\n \".cm-scroller\": {\n // NO VERTICAL INSET, on either surface. It was `0.5rem` here, on the argument that a code field\n // wants the same breathing room `Textarea` has — and what it actually produced was nine pixels\n // between the field's border and the first line, and a first line that does not start where\n // every other box on the screen starts. A pane docked under its own header showed it worst.\n // A caller who wants an inset has `className` and the surface it owns.\n fontFamily: \"var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace)\",\n lineHeight: \"1.5\",\n overflow: \"auto\",\n // Fill the editor so clicking the empty area below the last line still lands the caret,\n // rather than leaving dead surface — see the `&` note.\n flexGrow: 1,\n minHeight: 0,\n // The paper. It lives here and NOT on `.cm-content` — see the note there: an opaque\n // background on the content box hides the selection layer beneath it. The scroller fills\n // the whole field (flexGrow above), so the paper reaches the bottom on a short document.\n background: \"var(--background)\",\n },\n \"&.cm-focused\": { outline: \"none\" },\n // The gutter column carries its own tint. It used to be painted by the scroller instead, on\n // the grounds that a fill here \"only spans the content rows and stops mid-field on a short\n // document\" — measured against the current CodeMirror, that is no longer true: this element\n // is sized to the full scroller height, so the column reaches the bottom. Reclaiming it\n // freed the scroller to hold the paper, which is what un-hides the selection layer.\n // No divider; the tint step from `--muted` to the scroller's `--background` is the separation.\n \".cm-gutters\": {\n // NO vertical padding: the scroller's is the only one, and the gutter is inside it, so every\n // number already sits at its line's y. Repeating it here dropped each number one step too\n // low — verified back when the padding was on `.cm-content`: a constant 9px below its line.\n background: \"var(--muted)\",\n border: \"none\",\n color: \"var(--faint, var(--muted-foreground))\",\n },\n // Line numbers: tabular figures (no jitter across 9→10→100), right-aligned with breathing\n // room from the border edge, and a comfortable minimum width.\n \".cm-lineNumbers .cm-gutterElement\": {\n fontVariantNumeric: \"tabular-nums\",\n fontFeatureSettings: '\"tnum\"',\n minWidth: \"2.25ch\",\n padding: \"0 0.5rem 0 0.75rem\",\n textAlign: \"right\",\n cursor: \"pointer\",\n transition: \"color 120ms, background-color 120ms\",\n },\n // A line number is a click target (it selects the line), so it has to answer the\n // pointer — without this the whole column reads as inert decoration.\n // Quieter than the active line on purpose: this is a hover affordance, and at equal\n // strength the two read as the same state and the column flickers as the pointer moves.\n \".cm-lineNumbers .cm-gutterElement:hover\": {\n backgroundColor: \"color-mix(in oklab, var(--muted) 60%, transparent)\",\n color: \"var(--foreground)\",\n },\n \".cm-foldGutter .cm-gutterElement:hover\": { color: \"var(--foreground)\" },\n \".cm-placeholder\": { color: \"var(--muted-foreground)\" },\n // `highlightSpecialChars` ships its own `&light`/`&dark` rule at a raw `red` / `#f78`. Whichever\n // half the appearance selects is a raw hue nobody chose. Tokenised here, at equal specificity and\n // later in the sheet, so ours wins in both.\n \".cm-specialChar\": { color: \"var(--destructive-foreground)\" },\n\n // ── Floating surfaces ─────────────────────────────────────────────────────────\n // CodeMirror ships its own light-mode chrome for tooltips, autocomplete and the\n // search panel. Left alone they arrive as white boxes with system-blue selection —\n // the moment an editor is more than a text box, that is where the theme visibly\n // breaks. These read from the same tokens as `popover` / `menu`.\n \".cm-tooltip\": {\n background: \"var(--popover, var(--card))\",\n color: \"var(--foreground)\",\n border: \"1px solid var(--border)\",\n borderRadius: \"var(--radius-md)\",\n boxShadow: \"0 4px 12px rgb(0 0 0 / 0.08)\",\n overflow: \"hidden\",\n },\n // The divider between an autocomplete tooltip's sections. CodeMirror's `&light` default is\n // `1px solid #bbb`, and it is the half with no `&dark` counterpart — so before the facet followed\n // the appearance it drew a pale hairline inside a dark popover, and it is *still* the rule that\n // covers a light page. Found by `codemirror-dark-parity.test.ts`, not by looking.\n \".cm-tooltip-section:not(:first-child)\": { borderTop: \"1px solid var(--border)\" },\n \".cm-tooltip .cm-tooltip-arrow:before\": { borderTopColor: \"var(--border)\" },\n \".cm-tooltip .cm-tooltip-arrow:after\": { borderTopColor: \"var(--popover, var(--card))\" },\n \".cm-tooltip-autocomplete > ul\": {\n fontFamily: \"var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace)\",\n maxHeight: \"16rem\",\n },\n \".cm-tooltip-autocomplete > ul > li\": {\n padding: \"0.125rem 0.5rem\",\n lineHeight: \"1.6\",\n },\n \".cm-tooltip-autocomplete > ul > li[aria-selected]\": {\n background: \"var(--accent)\",\n color: \"var(--accent-foreground, var(--foreground))\",\n },\n \".cm-completionLabel\": { color: \"inherit\" },\n \".cm-completionMatchedText\": {\n color: \"var(--primary)\",\n fontWeight: \"600\",\n textDecoration: \"none\",\n },\n \".cm-completionDetail\": {\n color: \"var(--muted-foreground)\",\n fontStyle: \"normal\",\n marginInlineStart: \"0.5rem\",\n },\n \".cm-completionIcon\": { opacity: 0.64, paddingInlineEnd: \"0.5rem\" },\n\n \".cm-panels\": {\n background: \"var(--card)\",\n color: \"var(--foreground)\",\n borderColor: \"var(--border)\",\n },\n \".cm-panels.cm-panels-top\": { borderBottom: \"1px solid var(--border)\" },\n \".cm-panels.cm-panels-bottom\": { borderTop: \"1px solid var(--border)\" },\n\n // **`.cm-textfield` and `.cm-button` are no longer OUR controls, and that is the whole point of\n // `code-editor-search.tsx`.** The find/replace panel is `InputGroup`, `Toggle`, `Button` and\n // `ButtonGroup` now, rendered through a portal, so it wears the library's typography, focus ring\n // and hover states rather than a run of rules restating them in CSS. Six selectors went with it:\n // `.cm-panel.cm-search`, its `input, button, label { font-size: inherit }`, the two\n // `.cm-textfield` rules and the two for `button[name=close]`.\n //\n // These two stay, unscoped, for the controls that are still CodeMirror's: `gotoLine`'s dialog\n // (Mod-Alt-G), and any panel a caller's own extensions bring — `@codemirror/lint`'s is the\n // obvious one, and it is a caller's dependency rather than ours.\n //\n // **They also have to stay because they are the only thing standing between a page and a box\n // nobody themed.** `&light .cm-textfield { background: white; border: 1px solid silver }` and\n // `&light .cm-button { background: linear-gradient(#eff1f5, #d9d9df) }` are one half of it and\n // `&dark`'s `#333` and `linear-gradient(#393939, #111)` are the other; the `darkTheme` facet picks\n // which, and neither is a token. `codemirror-dark-parity.test.ts` checks BOTH halves and fails the\n // day either selector leaves this file. The previous\n // spelling was `.cm-panel.cm-search .cm-textfield`, which covered the search field and left\n // `gotoLine`'s identical field wearing the white default; before that it was `input[type=text]`,\n // which matched nothing at all, because CodeMirror sets no `type` on it.\n \".cm-textfield\": {\n background: \"var(--background)\",\n color: \"var(--foreground)\",\n border: \"1px solid var(--input, var(--border))\",\n borderRadius: \"var(--radius-sm)\",\n padding: \"0.125rem 0.375rem\",\n outline: \"none\",\n },\n \".cm-textfield:focus\": {\n borderColor: \"var(--ring)\",\n boxShadow: \"0 0 0 3px color-mix(in srgb, var(--ring) 32%, transparent)\",\n },\n \".cm-button\": {\n background: \"var(--secondary)\",\n color: \"var(--secondary-foreground, var(--foreground))\",\n border: \"1px solid transparent\",\n borderRadius: \"var(--radius-sm)\",\n backgroundImage: \"none\",\n padding: \"0.125rem 0.5rem\",\n cursor: \"pointer\",\n },\n \".cm-button:hover\": { background: \"var(--accent)\" },\n \".cm-button:active\": { backgroundImage: \"none\" },\n \".cm-dialog\": { padding: \"0.375rem 0.5rem\", fontSize: \"var(--kanzo-font-size-small)\" },\n \".cm-dialog label\": { display: \"inline-flex\", alignItems: \"center\", gap: \"0.375rem\" },\n \".cm-dialog-close\": { color: \"var(--muted-foreground)\", cursor: \"pointer\" },\n \".cm-dialog-close:hover\": { color: \"var(--foreground)\" },\n\n // Search hits: the current one is the primary-tinted anchor, the rest are quieter so\n // \"where am I\" stays readable at a glance.\n \".cm-searchMatch\": {\n backgroundColor: \"color-mix(in oklab, var(--warning) 17%, transparent)\",\n outline: \"1px solid var(--border)\",\n borderRadius: \"2px\",\n },\n \".cm-searchMatch.cm-searchMatch-selected\": {\n backgroundColor: \"color-mix(in oklab, var(--warning) 60%, transparent)\",\n outline: \"1px solid var(--warning)\",\n },\n\n // Lint diagnostics — semantic tokens, so severity survives a re-theme.\n \".cm-diagnostic\": {\n padding: \"0.25rem 0.5rem\",\n borderInlineStartWidth: \"3px\",\n borderInlineStartStyle: \"solid\",\n fontFamily: \"var(--font-sans, ui-sans-serif, system-ui, sans-serif)\",\n },\n \".cm-diagnostic-error\": { borderInlineStartColor: \"var(--destructive)\" },\n \".cm-diagnostic-warning\": { borderInlineStartColor: \"var(--warning)\" },\n \".cm-diagnostic-info\": { borderInlineStartColor: \"var(--info)\" },\n \".cm-lintPoint-error:after\": { borderBottomColor: \"var(--destructive)\" },\n \".cm-lintPoint-warning:after\": { borderBottomColor: \"var(--warning)\" },\n\n // Scrollbars: the default OS chrome is the loudest thing in a dark editor.\n \".cm-scroller::-webkit-scrollbar\": { width: \"10px\", height: \"10px\" },\n \".cm-scroller::-webkit-scrollbar-track\": { background: \"transparent\" },\n \".cm-scroller::-webkit-scrollbar-thumb\": {\n background: \"color-mix(in srgb, var(--muted-foreground) 32%, transparent)\",\n borderRadius: \"6px\",\n border: \"2px solid transparent\",\n backgroundClip: \"content-box\",\n },\n \".cm-scroller::-webkit-scrollbar-thumb:hover\": {\n background: \"color-mix(in srgb, var(--muted-foreground) 56%, transparent)\",\n backgroundClip: \"content-box\",\n },\n \".cm-scroller::-webkit-scrollbar-corner\": { background: \"transparent\" },\n // Tokenised selection (drawSelection), active line, matching bracket, search matches.\n \".cm-cursor, .cm-dropCursor\": { borderLeftColor: \"var(--foreground)\" },\n // The full child chain is LOAD-BEARING. CodeMirror's base theme ships\n // `&light.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground`\n // (@codemirror/view), which compiles to specificity 0,5,0. A flat\n // `&.cm-focused .cm-selectionBackground` is 0,3,0 and LOSES — so the focused selection\n // rendered in CodeMirror's stock lavender (#d7d4f0) instead of a Kanzo token. Worse, that\n // base rule is `&light`, and for as long as the `darkTheme` facet was pinned false the light\n // lavender applied in dark mode too. It is driven from the appearance now, which moves the\n // default to `&dark`'s — a different stock colour, still not ours, still covered by this rule.\n \"&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground\": {\n backgroundColor: \"color-mix(in oklab, var(--primary) 17%, transparent)\",\n },\n // **A selection must not repaint the text, and ours repainted all of it.** `tokens.css` sets a\n // document-wide `::selection { bg-primary/80 text-primary-foreground }`, which is right\n // everywhere prose is selected and wrong inside an editor: `drawSelection` suppresses the native\n // selection's *background* and not its `color`, so the fill came from `.cm-selectionBackground`\n // — a 17% wash of `--primary` — while every glyph inside it took `--primary-foreground`.\n //\n // Measured on `/docs/forms/code-editor`: #fafafa text on a #dcdcdc block. Keys, strings and\n // numbers all flattened to one near-white, so selecting seven lines of JSON erased the\n // highlighting from them. It happens under every palette, because a `-foreground` role is a\n // contrast colour for a fill that is not being painted here.\n //\n // `currentColor` is the fix, and it has to be `currentColor` and not a token: a fixed colour\n // flattens the tokens just as thoroughly, one shade later.\n \".cm-selectionBackground, .cm-content ::selection\": {\n backgroundColor: \"color-mix(in oklab, var(--primary) 17%, transparent)\",\n color: \"currentColor\",\n },\n \".cm-activeLine\": { backgroundColor: \"var(--muted)\" },\n // The active line's gutter cell is emphasised beyond the row: stronger tint, full-strength\n // ink and a weight bump so the current line number stands out from the dim column.\n \".cm-activeLineGutter\": {\n backgroundColor: \"var(--muted)\",\n color: \"var(--foreground)\",\n fontWeight: \"600\",\n },\n \".cm-matchingBracket, &.cm-focused .cm-matchingBracket\": { backgroundColor: \"color-mix(in oklab, var(--primary) 17%, transparent)\", outline: \"1px solid var(--primary)\" },\n \".cm-nonmatchingBracket, &.cm-focused .cm-nonmatchingBracket\": { backgroundColor: \"color-mix(in oklab, var(--destructive) 14%, transparent)\" },\n // **A neutral tint, because it is not a search hit.** This wore the warning wash, byte for byte\n // what `.cm-searchMatch` wears — so the other occurrences of the word under your caret were\n // indistinguishable from the hits of a query you actually typed, and the two co-occur constantly:\n // open the find panel while a word is selected and every match looks like the same kind of thing.\n //\n // They are not. A search hit is something you asked for; this is the editor noticing a\n // repetition. Every editor that draws both separates them the same way — the query keeps the\n // colour, the passive one goes neutral — and `--foreground` at 17% is the house's neutral wash\n // (`bg-foreground/17`, a selected table row). It also takes the collision off `bg-warning/17`,\n // which `Highlight` uses for a search-term mark in prose.\n \".cm-selectionMatch\": { backgroundColor: \"color-mix(in oklab, var(--foreground) 17%, transparent)\" },\n \".cm-foldGutter .cm-gutterElement\": { cursor: \"pointer\", color: \"var(--faint, var(--muted-foreground))\" },\n \".cm-foldPlaceholder\": { background: \"var(--muted)\", border: \"1px solid var(--border)\", color: \"var(--muted-foreground)\", borderRadius: \"var(--radius-sm)\", padding: \"0 4px\" },\n});\n\n/**\n * **The base theme's other half, switched on by the side the page is wearing.**\n *\n * `EditorView.theme(spec, { dark })` bakes the answer into the theme, and ours cannot: it is ONE\n * theme for both appearances, because the tokens flip underneath it. So the facet is set beside the\n * theme instead of by it, from `resolvedAppearance`, and `codemirror-dark-parity.test.ts` widened to\n * both halves the day this landed — until now only `&light` could ever apply, and now either can.\n *\n * `false` is spelled as *nothing*, not as `of(false)`: the facet combines with\n * `values.indexOf(true) > -1`, so a `false` from us would not switch off a `true` a caller's own\n * theme contributed, and pretending otherwise would be the more confusing lie.\n */\nconst darkFacet = (dark: boolean): Extension => (dark ? EditorView.darkTheme.of(true) : []);\n\nexport interface CodeEditorProps {\n value: string;\n onChange?: (value: string) => void;\n /** The language brain — CodeMirror extensions injected by the consumer (LSP,\n * StreamParser, autocomplete, linting…). Reconfigured live when it changes. */\n extensions?: Extension;\n readOnly?: boolean;\n lineNumbers?: boolean;\n invalid?: boolean;\n placeholder?: string;\n minHeight?: string;\n maxHeight?: string;\n /** Include the built-in batteries (history + default keymap + line wrapping +\n * the Kanzo token theme). Default true. Set false when the caller's `extensions`\n * own all behaviour and theming. */\n basics?: boolean;\n /**\n * Wrap long lines, or scroll sideways. Default true, which is what `basics` always did.\n *\n * Wrapping is right where the editor is a reading surface in a narrow pane; it is wrong where\n * the document has structure a reader tracks by column — a Turtle predicate list read against\n * its indentation stops being a list once every third line reflows. There is no third option:\n * `EditorView.lineWrapping` is on or it is not.\n *\n * Read once, when the view is built — like `basics`, and for the same reason. Changing it after\n * mount does nothing.\n */\n wrap?: boolean;\n /**\n * Wrap the editor in the styled focus-ring chrome (Box + inset ring + surface background).\n * Default true. Set false for a bare surface.\n */\n chrome?: boolean;\n /** Class applied to the editor host (only meaningful when `chrome={false}`). */\n className?: string;\n /** Receives the `EditorView` on mount and `null` on unmount — for side-wiring\n * (e.g. pushing LSP notifications). */\n onView?: (view: EditorView | null) => void;\n}\n\nexport function CodeEditor(p: CodeEditorProps) {\n const container = useRef<HTMLDivElement>(null);\n const view = useRef<EditorView>(undefined);\n const [focused, setFocused] = useState(false);\n\n const props = useRef(p);\n props.current = p;\n\n const langSlot = useRef(new Compartment());\n const editable = useRef(new Compartment());\n const appearance = useRef(new Compartment());\n\n // Optional, like every other reader of the cascade in this package: without a provider the facet\n // stays false, which is where it sat before this existed, and the guard proves the `&light`\n // defaults that then apply are all overridden anyway.\n const dark = useKanzoThemeOptional()?.resolvedAppearance === \"dark\";\n const darkNow = useRef(dark);\n darkNow.current = dark;\n\n // **CodeMirror's panels, rendered by React.** A `Panel` is an object with a `dom`, and nothing\n // says who fills it — so we hand over an empty element and portal into it. The portals are\n // children of THIS tree, which is the reason the host lives here and not in the panel's own\n // module: a panel rendered from anywhere else loses every provider above the editor.\n //\n // `mount`/`destroy` are CodeMirror's, and they bracket the element's life in the document\n // exactly. `mount` runs before React has rendered anything into it, which is why a panel that\n // wants focus takes it in its own effect rather than relying on CodeMirror's.\n const [panels, setPanels] = useState<{ id: number; dom: HTMLElement; node: ReactNode }[]>([]);\n\n // Build the editor once; long-lived callbacks read the latest props via the ref.\n useEffect(() => {\n const basics = props.current.basics ?? true;\n let nextPanel = 0;\n const panelHost: ReactPanelHost = {\n panel: (node, opts) => {\n const dom = document.createElement(\"div\");\n const id = ++nextPanel;\n return {\n dom,\n top: opts?.top,\n mount: () => setPanels((open) => [...open, { id, dom, node }]),\n destroy: () => setPanels((open) => open.filter((entry) => entry.id !== id)),\n };\n },\n };\n const updateListener = EditorView.updateListener.of((u) => {\n if (u.focusChanged) setFocused(u.view.hasFocus);\n if (!u.docChanged) return;\n if (u.transactions.some((t) => t.annotation(External))) return; // our own reconcile\n props.current.onChange?.(u.state.doc.toString());\n });\n const wrap = props.current.wrap ?? true;\n const batteries: Extension[] = basics\n ? [\n history(),\n keymap.of([\n ...defaultKeymap,\n ...historyKeymap,\n ...searchKeymap,\n ...closeBracketsKeymap,\n ...completionKeymap,\n ...foldKeymap,\n ]),\n ...(wrap ? [EditorView.lineWrapping] : []),\n drawSelection(),\n dropCursor(),\n // `completionKeymap` was already bound below, but the extension itself was never\n // installed — so Ctrl-Space did nothing and the ~25 lines of\n // `.cm-tooltip-autocomplete` styling in this theme were dead CSS.\n autocompletion(),\n // Makes NBSP, zero-width and control characters visible. Without it they are\n // invisible in the document, which is a real footgun when editing data.\n highlightSpecialChars(),\n // Alt-drag column selection, and the crosshair cursor that signals it is available.\n rectangularSelection(),\n crosshairCursor(),\n highlightCaretLine,\n bracketMatching(),\n closeBrackets(),\n indentOnInput(),\n highlightSelectionMatches(),\n // **`search()` has to be in the configuration, and it was not.** `searchKeymap` was\n // bound below and nothing installed the extension, so `openSearchPanel` appended the\n // defaults itself the first time Mod-F was pressed — which works, and silently discards\n // any configuration, `createPanel` included. Same shape as the `autocompletion()` bug\n // noted further down: a keymap for an extension nobody added.\n kanzoSearch(panelHost),\n kanzoHighlighting,\n baseTheme,\n ]\n : [];\n const v = new EditorView({\n parent: container.current!,\n state: EditorState.create({\n doc: props.current.value ?? \"\",\n extensions: [\n // Multiple cursors. Off by default in CodeMirror, and table stakes in an editor.\n EditorState.allowMultipleSelections.of(true),\n ...batteries,\n ...(props.current.lineNumbers ? [cmLineNumbers()] : []),\n ...(basics && props.current.lineNumbers ? [highlightCaretLineGutter, foldGutter()] : []),\n ...(props.current.placeholder ? [cmPlaceholder(props.current.placeholder)] : []),\n updateListener,\n appearance.current.of(darkFacet(darkNow.current)),\n langSlot.current.of(props.current.extensions ?? []),\n editable.current.of([\n EditorView.editable.of(!props.current.readOnly),\n EditorState.readOnly.of(!!props.current.readOnly),\n ]),\n ],\n }),\n });\n view.current = v;\n props.current.onView?.(v);\n return () => {\n props.current.onView?.(null);\n v.destroy();\n };\n }, []);\n\n // Reconcile external value changes — but never clobber active typing.\n useEffect(() => {\n const v = view.current;\n if (!v || v.hasFocus) return;\n if (p.value !== v.state.doc.toString()) {\n v.dispatch({\n changes: { from: 0, to: v.state.doc.length, insert: p.value },\n annotations: External.of(true),\n });\n }\n }, [p.value]);\n\n // Swap the language extensions live (compartment reconfigure, no re-create).\n useEffect(() => {\n view.current?.dispatch({ effects: langSlot.current.reconfigure(p.extensions ?? []) });\n }, [p.extensions]);\n\n // Follow the page's side. Nothing of ours is keyed off the facet — our rules are unprefixed and\n // the tokens do the flipping — so what this moves is CodeMirror's own base theme and any theme a\n // caller's extensions bring.\n useEffect(() => {\n view.current?.dispatch({ effects: appearance.current.reconfigure(darkFacet(dark)) });\n }, [dark]);\n\n // Toggle editability when `readOnly` changes.\n useEffect(() => {\n view.current?.dispatch({\n effects: editable.current.reconfigure([\n EditorView.editable.of(!p.readOnly),\n EditorState.readOnly.of(!!p.readOnly),\n ]),\n });\n }, [p.readOnly]);\n\n // A portal contributes no DOM to the element it is written inside, so these are safe as children\n // of the very element CodeMirror owns: React mounts each node into the panel's `dom` and nothing\n // else. Written once and used by both branches below.\n const portals = panels.map((entry) => createPortal(entry.node, entry.dom, String(entry.id)));\n\n // Bare surface (caller owns theme/chrome).\n //\n // `flex flex-col` is LOAD-BEARING, not styling. The theme sizes the editor with\n // `\"&\": { flex: \"1 1 auto\", minHeight: 0 }`, which resolves only inside a flex container — and\n // this host was a plain block, so `.cm-editor` fell back to content height. A caller asking for\n // a full-height pane (`chrome={false} className=\"flex-1\"`) got a host that filled its parent\n // and an editor that did not fill the host: measured 467px of scroller inside an 883px pane,\n // with the rest dead. The chromed branch below never had the bug because its wrapper is\n // already a flex column.\n if (p.chrome === false) {\n return (\n <div\n className={cn(\"flex flex-col\", p.className)}\n data-slot=\"code-editor\"\n ref={container}\n style={{ minHeight: p.minHeight, maxHeight: p.maxHeight }}\n >\n {portals}\n </div>\n );\n }\n\n // A field, not an IDE pane: the surface wears the exact chrome as Textarea/Input — a\n // hairline `--input` border, `rounded-lg`, the transparent/`--field`-tinted surface, a\n // faint shadow, and the same focus/invalid treatment (border shifts colour + a 3px ring).\n // The ring is a `box-shadow` (Tailwind `ring`), drawn OUTSIDE the border box, so it is not\n // clipped by `overflow-hidden` and the gutter's own background cannot paint over it — the\n // failure the old inset approach worked around. Focus is driven off `data-focused`\n // (CodeMirror's contenteditable holds focus, not this wrapper, so `:focus-visible` can't).\n return (\n <div data-slot=\"code-editor\" style={{ flex: 1, width: \"100%\" }}>\n <div\n data-slot=\"code-editor-surface\"\n data-focused={focused || undefined}\n data-invalid={p.invalid || undefined}\n ref={container}\n className={cn(\n \"flex w-full flex-col overflow-hidden\",\n \"rounded-lg border border-input bg-transparent shadow-xs/5 bg-field\",\n \"transition-[color,box-shadow]\",\n \"data-focused:border-primary data-focused:ring-[3px] data-focused:ring-ring\",\n \"data-invalid:border-destructive data-invalid:ring-[3px] data-invalid:ring-destructive/24\",\n \"motion-reduce:transition-none!\"\n )}\n style={{ minHeight: p.minHeight, maxHeight: p.maxHeight }}\n >\n {portals}\n </div>\n </div>\n );\n}"],"names":[],"mappings":"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"}
|
package/dist/guard-corpus.d.ts
CHANGED
|
@@ -5,13 +5,29 @@ export interface Root {
|
|
|
5
5
|
/** Absolute path to that package's `src/`. */
|
|
6
6
|
readonly src: string;
|
|
7
7
|
}
|
|
8
|
+
/** Every package the idiom scans cover, derived from the workspace rather than hard-coded. */
|
|
9
|
+
export declare const ROOTS: readonly Root[];
|
|
8
10
|
/**
|
|
9
|
-
*
|
|
11
|
+
* The population for the one rule that is not about the idiom: **a custom property that is read
|
|
12
|
+
* has to exist.** Every shipped package's `src/`, and `docs/`.
|
|
13
|
+
*
|
|
14
|
+
* The idiom filter above is right for the rules about how a recipe is written and wrong for this
|
|
15
|
+
* one, and the failure that proved it is the reason this constant exists: `graph-canvas.tsx`
|
|
16
|
+
* filled the lasso with `var(--brand-a5)`, a token the theme refoundation deleted, and it drew
|
|
17
|
+
* BLACK in keasy — an undefined property makes an SVG `fill` invalid, and `fill`'s initial value is
|
|
18
|
+
* black. `graph` writes no `tv()` recipe, so no guard read a line of it. What a file reads from the
|
|
19
|
+
* theme does not depend on how it spells its classes.
|
|
10
20
|
*
|
|
11
|
-
* `
|
|
12
|
-
*
|
|
21
|
+
* **`docs` is in, and only here.** The showcases and examples are what a consumer copies, and two of
|
|
22
|
+
* them were teaching deleted names (`bg-base-a5`, `var(--brand-a3)`) — a page that paints nothing
|
|
23
|
+
* is a defect whether or not it ships in a tarball. It stays out of {@link ROOTS}: a showcase
|
|
24
|
+
* deliberately hand-rolls what a recipe would own, and the idiom rules would be arguing with the
|
|
25
|
+
* one place that is allowed to.
|
|
26
|
+
*
|
|
27
|
+
* `theme` is in as well, although it declares the vocabulary: its `src/` is TypeScript that READS
|
|
28
|
+
* names (`categoricalColor` returns `var(--chart-N)`), and a read there resolves like any other.
|
|
13
29
|
*/
|
|
14
|
-
export declare const
|
|
30
|
+
export declare const READERS: readonly Root[];
|
|
15
31
|
/** `/Users/…/packages/ai/src/message.tsx` → `ai/message.tsx`. Throws rather than guessing. */
|
|
16
32
|
export declare function label(file: string): string;
|
|
17
33
|
/** The inverse, so a pinned list in a guard is spelled the way that guard's failures are. */
|
|
@@ -22,7 +38,7 @@ export declare function resolvePath(reported: string): string;
|
|
|
22
38
|
* Tests are excluded: they quote the very spellings the rules ban, on purpose, and several of them
|
|
23
39
|
* assemble one at runtime to keep it out of Tailwind's scan.
|
|
24
40
|
*/
|
|
25
|
-
export declare function sourceFiles(extensions?: RegExp): string[];
|
|
41
|
+
export declare function sourceFiles(extensions?: RegExp, roots?: readonly Root[]): string[];
|
|
26
42
|
/**
|
|
27
43
|
* Every subtree a scan has to reach, spelled as a path prefix: `ui/simples/`, `ui/charts/`, `ai/`.
|
|
28
44
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guard-corpus.d.ts","sourceRoot":"","sources":["../src/guard-corpus.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"guard-corpus.d.ts","sourceRoot":"","sources":["../src/guard-corpus.ts"],"names":[],"mappings":"AAyEA,eAAO,MAAM,IAAI,QAA4B,CAAC;AAE9C,MAAM,WAAW,IAAI;IACnB,kGAAkG;IAClG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8CAA8C;IAC9C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AA4CD,8FAA8F;AAC9F,eAAO,MAAM,KAAK,EAAE,SAAS,IAAI,EAIS,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,OAAO,EAAE,SAAS,IAAI,EAGlC,CAAC;AA+BF,8FAA8F;AAC9F,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI1C;AAED,6FAA6F;AAC7F,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAUpD;AAKD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,UAAU,SAAY,EAAE,KAAK,GAAE,SAAS,IAAI,EAAU,GAAG,MAAM,EAAE,CAY5F;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,QAAQ,IAAI,MAAM,EAAE,CAYnC;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG;IAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,CAKjF;AASD;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,MAAM,CAAC;AAEhC,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/simples/button.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { jsx as r, jsxs as v, Fragment as
|
|
2
|
-
import { ark as
|
|
1
|
+
import { jsx as r, jsxs as v, Fragment as g } from "react/jsx-runtime";
|
|
2
|
+
import { ark as p } from "@ark-ui/react/factory";
|
|
3
3
|
import { tv as f } from "tailwind-variants";
|
|
4
4
|
import { cn as u } from "../lib/cn.js";
|
|
5
5
|
import { controlSizes as e, controlHeight as m } from "../lib/control-size.js";
|
|
@@ -14,18 +14,18 @@ const z = f({
|
|
|
14
14
|
// that assigns `--btn-bd`, while all six assign the fill and the ink. A default for those two
|
|
15
15
|
// would be a line that is overwritten every single render.
|
|
16
16
|
"[--btn-bd:transparent]",
|
|
17
|
-
// Derived, never assigned by a variant. `--
|
|
17
|
+
// Derived, never assigned by a variant. `--relief: 0` collapses the edge and the lift to
|
|
18
18
|
// nothing, which is what the library looks like today; `1` gives it relief. Same classes.
|
|
19
19
|
//
|
|
20
20
|
// daisyUI darkens a fill with `color-mix(in oklab, var(--btn-bg), #000 5%)`. Mixing toward
|
|
21
21
|
// `--foreground` instead is a divergence taken on a reason: `black` is only "darker" in a light
|
|
22
22
|
// theme, and on a dark one the hover of a pale button should move toward white. One formula
|
|
23
23
|
// reads as "push this fill away from the page and toward the ink" in both, and it follows a
|
|
24
|
-
// tenant's document instead of a constant — which is also why `--
|
|
24
|
+
// tenant's document instead of a constant — which is also why `--relief` can be a single number.
|
|
25
25
|
// Their half-pixel inset gloss needs a colour that is toward the light side in both modes and
|
|
26
26
|
// neither `--foreground` nor `--background` is that, so it waits for a reason to exist.
|
|
27
|
-
"[--btn-edge:color-mix(in_oklab,var(--btn-bd),var(--foreground)_calc(var(--
|
|
28
|
-
"[--btn-lift:color-mix(in_oklab,var(--btn-bg)_calc(var(--
|
|
27
|
+
"[--btn-edge:color-mix(in_oklab,var(--btn-bd),var(--foreground)_calc(var(--relief)*8%))]",
|
|
28
|
+
"[--btn-lift:color-mix(in_oklab,var(--btn-bg)_calc(var(--relief)*30%),transparent)]",
|
|
29
29
|
"bg-(--btn-bg) text-(--btn-fg)",
|
|
30
30
|
// The grain, and the whole of the conditional that switches it off: at `--noise: 0` the layer
|
|
31
31
|
// is sized to zero and never painted, at `1` it tiles. No variant — which is the property that
|
|
@@ -39,7 +39,7 @@ const z = f({
|
|
|
39
39
|
// caller. An arbitrary value is in a group the merge does know.
|
|
40
40
|
"border-[length:var(--stroke)] border-(--btn-edge) rounded-[var(--radius-field)]",
|
|
41
41
|
// As `shadow-*` rather than a raw `box-shadow`, so it composes with `focus-visible:ring-[3px]`
|
|
42
|
-
// through Tailwind's shadow chain instead of being replaced by it. At `--
|
|
42
|
+
// through Tailwind's shadow chain instead of being replaced by it. At `--relief: 0` the lift is
|
|
43
43
|
// transparent and nothing is drawn either way, which is why that was invisible before.
|
|
44
44
|
"shadow-[0_3px_2px_-2px_var(--btn-lift),0_4px_3px_-2px_var(--btn-lift)]",
|
|
45
45
|
// One hover and one active for every variant, solid and transparent alike — and the transparent
|
|
@@ -77,7 +77,7 @@ const z = f({
|
|
|
77
77
|
outline: [
|
|
78
78
|
"[--btn-bg:transparent]",
|
|
79
79
|
"[--btn-fg:var(--foreground)]",
|
|
80
|
-
"[--btn-bd:var(--input)]",
|
|
80
|
+
"[--btn-bd:var(--input,var(--border))]",
|
|
81
81
|
"focus-visible:border-primary"
|
|
82
82
|
],
|
|
83
83
|
destructive: [
|
|
@@ -89,7 +89,7 @@ const z = f({
|
|
|
89
89
|
],
|
|
90
90
|
secondary: [
|
|
91
91
|
"[--btn-bg:var(--secondary)]",
|
|
92
|
-
"[--btn-fg:var(--secondary-foreground)]",
|
|
92
|
+
"[--btn-fg:var(--secondary-foreground,var(--foreground))]",
|
|
93
93
|
"focus-visible:border-primary"
|
|
94
94
|
],
|
|
95
95
|
ghost: [
|
|
@@ -160,36 +160,36 @@ const z = f({
|
|
|
160
160
|
}
|
|
161
161
|
}), j = (s) => {
|
|
162
162
|
const {
|
|
163
|
-
variant:
|
|
163
|
+
variant: t = "default",
|
|
164
164
|
size: n = "md",
|
|
165
165
|
clickEffect: o = !0,
|
|
166
|
-
pill:
|
|
166
|
+
pill: l = !1,
|
|
167
167
|
isLoading: a = !1,
|
|
168
|
-
className:
|
|
169
|
-
children:
|
|
168
|
+
className: d,
|
|
169
|
+
children: i,
|
|
170
170
|
slot: b,
|
|
171
171
|
...c
|
|
172
172
|
} = s;
|
|
173
173
|
return /* @__PURE__ */ r(
|
|
174
|
-
|
|
174
|
+
p.button,
|
|
175
175
|
{
|
|
176
176
|
className: u(
|
|
177
|
-
z({ variant:
|
|
178
|
-
|
|
177
|
+
z({ variant: t, size: n, clickEffect: o, pill: l }),
|
|
178
|
+
d
|
|
179
179
|
),
|
|
180
180
|
"data-size": n,
|
|
181
181
|
"data-state": a ? "loading" : "idle",
|
|
182
|
-
"data-variant":
|
|
182
|
+
"data-variant": t,
|
|
183
183
|
type: "button",
|
|
184
184
|
...c,
|
|
185
185
|
"data-slot": b ?? "button",
|
|
186
186
|
"aria-busy": a,
|
|
187
187
|
"aria-disabled": a,
|
|
188
|
-
children: a ? /* @__PURE__ */ v(
|
|
189
|
-
/* @__PURE__ */ r("span", { "aria-hidden": !0, className: "invisible", children:
|
|
190
|
-
/* @__PURE__ */ r("span", { className: "sr-only", children:
|
|
188
|
+
children: a ? /* @__PURE__ */ v(g, { children: [
|
|
189
|
+
/* @__PURE__ */ r("span", { "aria-hidden": !0, className: "invisible", children: i }),
|
|
190
|
+
/* @__PURE__ */ r("span", { className: "sr-only", children: i }),
|
|
191
191
|
/* @__PURE__ */ r("span", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ r(x, { "aria-hidden": !0 }) })
|
|
192
|
-
] }) :
|
|
192
|
+
] }) : i
|
|
193
193
|
}
|
|
194
194
|
);
|
|
195
195
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"button.js","sources":["../../src/simples/button.tsx"],"sourcesContent":["import { ark } from \"@ark-ui/react/factory\";\nimport type React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\nimport { cn } from \"../lib/cn\";\nimport { controlHeight, controlSizes } from \"../lib/control-size\";\nimport { Spinner } from \"./spinner\";\n\n/**\n * The whole control: the fill, the ink, the edge, the lift, both washes and the height, all on the\n * `base` and all written once in terms of three locals a variant assigns.\n *\n * **The shape is daisyUI's.** Their `.btn-primary` is two custom-property assignments over a `.btn`\n * that derives every state; ours was six variants of five to six utilities each, naming eight\n * different tokens between them, with the hover of each written separately. A hover rule that lives\n * in six places is a hover rule that drifts in six places.\n *\n * **The placement is daisyUI's too, and that half arrived late.** The derivations sat in\n * the stylesheet under `[data-slot=\"button\"]`, which is the one attribute this library invites a\n * caller to RENAME — every part takes `slot?: string`. So every component of ours that renames a\n * `Button` got the variant's three assignments and no rule that read them: `AlertDialogAction` and\n * `AlertDialogCancel` reported their `data-variant` correctly and `background-color:\n * rgba(0, 0, 0, 0)` in a live document, and twenty-nine more call sites sat on the same fault.\n * `05f3a0a` had already moved the *height* here for exactly this reason and left the colour behind,\n * on the argument that losing a colour on a rename is a caller asking for a different look — which\n * is true of a caller and was never true of us. `no-paint-on-a-renameable-slot.test.ts` holds it.\n *\n * What a variant may set: `--btn-bg`, `--btn-fg`, `--btn-bd`. Nothing else here paints.\n */\nexport const buttonVariants = tv({\n base: [\n \"relative\",\n // Nothing below may be keyed on `data-slot`. See the docblock: `--size` was moved here first,\n // in `05f3a0a`, and the paint followed it once the same rename was measured against the colour.\n controlHeight,\n // The only one of the three locals a variant may leave unset — `outline` is the sole variant\n // that assigns `--btn-bd`, while all six assign the fill and the ink. A default for those two\n // would be a line that is overwritten every single render.\n \"[--btn-bd:transparent]\",\n // Derived, never assigned by a variant. `--depth: 0` collapses the edge and the lift to\n // nothing, which is what the library looks like today; `1` gives it relief. Same classes.\n //\n // daisyUI darkens a fill with `color-mix(in oklab, var(--btn-bg), #000 5%)`. Mixing toward\n // `--foreground` instead is a divergence taken on a reason: `black` is only \"darker\" in a light\n // theme, and on a dark one the hover of a pale button should move toward white. One formula\n // reads as \"push this fill away from the page and toward the ink\" in both, and it follows a\n // tenant's document instead of a constant — which is also why `--depth` can be a single number.\n // Their half-pixel inset gloss needs a colour that is toward the light side in both modes and\n // neither `--foreground` nor `--background` is that, so it waits for a reason to exist.\n \"[--btn-edge:color-mix(in_oklab,var(--btn-bd),var(--foreground)_calc(var(--depth)*8%))]\",\n \"[--btn-lift:color-mix(in_oklab,var(--btn-bg)_calc(var(--depth)*30%),transparent)]\",\n \"bg-(--btn-bg) text-(--btn-fg)\",\n // The grain, and the whole of the conditional that switches it off: at `--noise: 0` the layer\n // is sized to zero and never painted, at `1` it tiles. No variant — which is the property that\n // lets it be a value a tenant sets rather than a look somebody has to write. It defaults to `0`\n // here rather than in every theme, because a theme that wants no texture is saying nothing.\n \"bg-(image:--fx-noise) bg-size-[calc(var(--noise,0)*100%)]\",\n // `rounded-[var(--radius-field)]` and not `rounded-field`, which is the same radius and the\n // spelling every other recipe here uses: `tailwind-merge` knows nothing about a theme key it\n // was never configured with, so `rounded-field` and a caller's `rounded-full` both survive the\n // merge and the winner is whichever Tailwind happened to emit last. The `pill` variant is that\n // caller. An arbitrary value is in a group the merge does know.\n \"border-[length:var(--stroke)] border-(--btn-edge) rounded-[var(--radius-field)]\",\n // As `shadow-*` rather than a raw `box-shadow`, so it composes with `focus-visible:ring-[3px]`\n // through Tailwind's shadow chain instead of being replaced by it. At `--depth: 0` the lift is\n // transparent and nothing is drawn either way, which is why that was invisible before.\n \"shadow-[0_3px_2px_-2px_var(--btn-lift),0_4px_3px_-2px_var(--btn-lift)]\",\n // One hover and one active for every variant, solid and transparent alike — and the transparent\n // case is the tell that the formula is right. Mixing `transparent` with the foreground at 10%\n // IS the foreground at 10% alpha, which is exactly the wash a ghost button wants.\n //\n // The CSS rule guarded these with `:not(:disabled, [aria-disabled=\"true\"], [data-disabled])`.\n // All three spellings already carry `pointer-events-none` on this same base and an element with\n // `pointer-events: none` can match neither `:hover` nor `:active`, so the guard was\n // belt-and-braces. Dropping it is not tidiness: with the plain modifier, `tailwind-merge` can\n // see a variant's own `hover:bg-*` as the same utility and let it win — which is how `link`\n // opts out in one class, instead of losing to a longer selector on specificity.\n \"hover:bg-[color-mix(in_oklab,var(--btn-bg),var(--foreground)_10%)]\",\n \"active:bg-[color-mix(in_oklab,var(--btn-bg),var(--foreground)_16%)]\",\n \"inline-flex shrink-0 items-center justify-center gap-2\",\n \"whitespace-nowrap font-medium text-sm\",\n \"border-solid\",\n \"transition-all\",\n \"outline-none focus-visible:ring-[3px] focus-visible:ring-ring\",\n \"disabled:pointer-events-none disabled:opacity-64\",\n \"data-disabled:pointer-events-none data-disabled:opacity-64\",\n \"aria-disabled:pointer-events-none aria-disabled:opacity-64\",\n \"data-[state=loading]:pointer-events-none\",\n \"aria-invalid:border-destructive aria-invalid:ring-destructive/24\",\n \"[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n \"motion-reduce:transition-none!\",\n ],\n variants: {\n variant: {\n default: [\n \"[--btn-bg:var(--primary)]\",\n \"[--btn-fg:var(--primary-foreground)]\",\n \"focus-visible:border-background\",\n ],\n outline: [\n \"[--btn-bg:transparent]\",\n \"[--btn-fg:var(--foreground)]\",\n \"[--btn-bd:var(--input)]\",\n \"focus-visible:border-primary\",\n ],\n destructive: [\n \"[--btn-bg:var(--destructive)]\",\n // Was `text-white` at 3.81 on red-500. The fill moved to `-600` and the ink became a token,\n // because no choice of ink rescued red-500: white 3.81, near-black 4.15, AA needs 4.5.\n \"[--btn-fg:var(--destructive-content)]\",\n \"focus-visible:border-background focus-visible:ring-destructive-foreground/32\",\n ],\n secondary: [\n \"[--btn-bg:var(--secondary)]\",\n \"[--btn-fg:var(--secondary-foreground)]\",\n \"focus-visible:border-primary\",\n ],\n ghost: [\n \"[--btn-bg:transparent]\",\n \"[--btn-fg:var(--foreground)]\",\n \"focus-visible:border-primary\",\n ],\n // The one variant that opts OUT of the shared hover, because a link's hover is an underline\n // and not a wash. Same modifier and same utility group as the base's wash, so\n // `tailwind-variants` merges the two and the variant wins — which is what makes opting out\n // one class rather than an exception somewhere else.\n link: [\n \"[--btn-bg:transparent]\",\n \"[--btn-fg:var(--primary)]\",\n \"underline-offset-4\",\n \"hover:bg-transparent hover:underline active:bg-transparent\",\n \"focus-visible:border-primary\",\n ],\n },\n /**\n * Padding, gaps and icon sizes. A size variant only **moves `--size`** — the height that reads\n * it is on the `base` — so a tenant can ask for compact controls, through `--size-field`,\n * without asking for tighter text.\n *\n * The floor is still measured rather than chosen. `xs` and `icon-xs` were both `1.5rem`, and\n * every size is a `rem` against a root the density axis sets — 16px default, 14px compact, 18px\n * comfortable — so they measured **21px in compact**, under the 24×24 WCAG 2.5.8 states in CSS\n * pixels. `sm` is the smallest size that clears the bar in all three densities. No call site was\n * failing: the spacing exception saves a clustered control, and every measured `xs` was in a\n * cluster. The variant went because its NAME promised a size it could not deliver at one\n * density, and nothing was published to break.\n */\n size: {\n sm: [controlSizes.sm, \"px-2.5\", \"gap-1.5\", \"[&_svg:not([class*='size-'])]:size-3.5\"],\n md: [controlSizes.md, \"px-3\", \"py-2\"],\n lg: [controlSizes.lg, \"px-3.5\"],\n xl: [\"[--size:calc(var(--size-field)*10)]\", \"text-base\", \"px-4\"],\n // Square, and the width is stated rather than left to `aspect-ratio`: a caller who writes\n // `w-fit` on top of an aspect ratio gets the icon's own width and a control twice as tall.\n \"icon-sm\": [controlSizes.sm, \"w-(--size)\", \"px-0\"],\n \"icon-md\": [controlSizes.md, \"w-(--size)\", \"px-0\"],\n \"icon-lg\": [controlSizes.lg, \"w-(--size)\", \"px-0\"],\n \"icon-xl\": [\n \"[--size:calc(var(--size-field)*10)]\",\n \"w-(--size)\",\n \"px-0\",\n \"[&_svg:not([class*='size-'])]:size-5\",\n ],\n },\n clickEffect: {\n true: \"active:not-aria-[haspopup]:scale-[0.98]\",\n },\n pill: {\n true: [\n \"rounded-full\",\n \"has-[>svg]:data-[size=sm]:pe-3.5\",\n \"has-[>svg]:data-[size=md]:pe-4\",\n \"has-[>svg]:data-[size=lg]:pe-4.5\",\n \"has-[>svg]:data-[size=xl]:pe-5\",\n ],\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"md\",\n clickEffect: true,\n pill: false,\n },\n});\n\nexport interface ButtonProps\n extends React.ComponentProps<typeof ark.button>,\n VariantProps<typeof buttonVariants> {\n /**\n * Apply a click effect to the button\n *\n * @default true\n */\n clickEffect?: boolean;\n /**\n * Show a loading indicator\n *\n * @default false\n */\n isLoading?: boolean;\n}\n\nexport const Button = (props: ButtonProps) => {\n const {\n variant = \"default\",\n size = \"md\",\n clickEffect = true,\n pill = false,\n isLoading = false,\n className,\n children,\n slot,\n ...rest\n } = props;\n\n return (\n <ark.button\n className={cn(\n buttonVariants({ variant, size, clickEffect, pill }),\n className\n )}\n data-size={size}\n data-state={isLoading ? \"loading\" : \"idle\"}\n data-variant={variant}\n type=\"button\"\n {...rest}\n data-slot={slot ?? \"button\"}\n aria-busy={isLoading}\n aria-disabled={isLoading}\n >\n {isLoading ? (\n <>\n <span aria-hidden className=\"invisible\">\n {children}\n </span>\n\n <span className=\"sr-only\">{children}</span>\n\n <span className=\"absolute inset-0 flex items-center justify-center\">\n <Spinner aria-hidden />\n </span>\n </>\n ) : (\n children\n )}\n </ark.button>\n );\n};\n"],"names":["buttonVariants","tv","controlHeight","controlSizes","Button","props","variant","size","clickEffect","pill","isLoading","className","children","slot","rest","jsx","ark","cn","jsxs","Fragment","Spinner"],"mappings":";;;;;;AA4BO,MAAMA,IAAiBC,EAAG;AAAA,EAC/B,MAAM;AAAA,IACJ;AAAA;AAAA;AAAA,IAGAC;AAAA;AAAA;AAAA;AAAA,IAIA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAWA;AAAA,IACA;AAAA,IACA;AAAA;AAAA;AAAA;AAAA;AAAA,IAKA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA;AAAA;AAAA;AAAA;AAAA,IAIA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAWA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAAA,EAEF,UAAU;AAAA,IACR,SAAS;AAAA,MACP,SAAS;AAAA,QACP;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,MAEF,SAAS;AAAA,QACP;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,MAEF,aAAa;AAAA,QACX;AAAA;AAAA;AAAA,QAGA;AAAA,QACA;AAAA,MAAA;AAAA,MAEF,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,MAEF,OAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAMF,MAAM;AAAA,QACJ;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,IACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAeF,MAAM;AAAA,MACJ,IAAI,CAACC,EAAa,IAAI,UAAU,WAAW,wCAAwC;AAAA,MACnF,IAAI,CAACA,EAAa,IAAI,QAAQ,MAAM;AAAA,MACpC,IAAI,CAACA,EAAa,IAAI,QAAQ;AAAA,MAC9B,IAAI,CAAC,uCAAuC,aAAa,MAAM;AAAA;AAAA;AAAA,MAG/D,WAAW,CAACA,EAAa,IAAI,cAAc,MAAM;AAAA,MACjD,WAAW,CAACA,EAAa,IAAI,cAAc,MAAM;AAAA,MACjD,WAAW,CAACA,EAAa,IAAI,cAAc,MAAM;AAAA,MACjD,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,IACF;AAAA,IAEF,aAAa;AAAA,MACX,MAAM;AAAA,IAAA;AAAA,IAER,MAAM;AAAA,MACJ,MAAM;AAAA,QACJ;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,IACF;AAAA,EACF;AAAA,EAEF,iBAAiB;AAAA,IACf,SAAS;AAAA,IACT,MAAM;AAAA,IACN,aAAa;AAAA,IACb,MAAM;AAAA,EAAA;AAEV,CAAC,GAmBYC,IAAS,CAACC,MAAuB;AAC5C,QAAM;AAAA,IACJ,SAAAC,IAAU;AAAA,IACV,MAAAC,IAAO;AAAA,IACP,aAAAC,IAAc;AAAA,IACd,MAAAC,IAAO;AAAA,IACP,WAAAC,IAAY;AAAA,IACZ,WAAAC;AAAA,IACA,UAAAC;AAAA,IACA,MAAAC;AAAA,IACA,GAAGC;AAAA,EAAA,IACDT;AAEJ,SACE,gBAAAU;AAAA,IAACC,EAAI;AAAA,IAAJ;AAAA,MACC,WAAWC;AAAA,QACTjB,EAAe,EAAE,SAAAM,GAAS,MAAAC,GAAM,aAAAC,GAAa,MAAAC,GAAM;AAAA,QACnDE;AAAA,MAAA;AAAA,MAEF,aAAWJ;AAAA,MACX,cAAYG,IAAY,YAAY;AAAA,MACpC,gBAAcJ;AAAA,MACd,MAAK;AAAA,MACJ,GAAGQ;AAAA,MACJ,aAAWD,KAAQ;AAAA,MACnB,aAAWH;AAAA,MACX,iBAAeA;AAAA,MAEd,cACC,gBAAAQ,EAAAC,GAAA,EACE,UAAA;AAAA,QAAA,gBAAAJ,EAAC,QAAA,EAAK,eAAW,IAAC,WAAU,aACzB,UAAAH,GACH;AAAA,QAEA,gBAAAG,EAAC,QAAA,EAAK,WAAU,WAAW,UAAAH,EAAA,CAAS;AAAA,QAEpC,gBAAAG,EAAC,UAAK,WAAU,qDACd,4BAACK,GAAA,EAAQ,eAAW,IAAC,EAAA,CACvB;AAAA,MAAA,EAAA,CACF,IAEAR;AAAA,IAAA;AAAA,EAAA;AAIR;"}
|
|
1
|
+
{"version":3,"file":"button.js","sources":["../../src/simples/button.tsx"],"sourcesContent":["import { ark } from \"@ark-ui/react/factory\";\nimport type React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\nimport { cn } from \"../lib/cn\";\nimport { controlHeight, controlSizes } from \"../lib/control-size\";\nimport { Spinner } from \"./spinner\";\n\n/**\n * The whole control: the fill, the ink, the edge, the lift, both washes and the height, all on the\n * `base` and all written once in terms of three locals a variant assigns.\n *\n * **The shape is daisyUI's.** Their `.btn-primary` is two custom-property assignments over a `.btn`\n * that derives every state; ours was six variants of five to six utilities each, naming eight\n * different tokens between them, with the hover of each written separately. A hover rule that lives\n * in six places is a hover rule that drifts in six places.\n *\n * **The placement is daisyUI's too, and that half arrived late.** The derivations sat in\n * the stylesheet under `[data-slot=\"button\"]`, which is the one attribute this library invites a\n * caller to RENAME — every part takes `slot?: string`. So every component of ours that renames a\n * `Button` got the variant's three assignments and no rule that read them: `AlertDialogAction` and\n * `AlertDialogCancel` reported their `data-variant` correctly and `background-color:\n * rgba(0, 0, 0, 0)` in a live document, and twenty-nine more call sites sat on the same fault.\n * `05f3a0a` had already moved the *height* here for exactly this reason and left the colour behind,\n * on the argument that losing a colour on a rename is a caller asking for a different look — which\n * is true of a caller and was never true of us. `no-paint-on-a-renameable-slot.test.ts` holds it.\n *\n * What a variant may set: `--btn-bg`, `--btn-fg`, `--btn-bd`. Nothing else here paints.\n */\nexport const buttonVariants = tv({\n base: [\n \"relative\",\n // Nothing below may be keyed on `data-slot`. See the docblock: `--size` was moved here first,\n // in `05f3a0a`, and the paint followed it once the same rename was measured against the colour.\n controlHeight,\n // The only one of the three locals a variant may leave unset — `outline` is the sole variant\n // that assigns `--btn-bd`, while all six assign the fill and the ink. A default for those two\n // would be a line that is overwritten every single render.\n \"[--btn-bd:transparent]\",\n // Derived, never assigned by a variant. `--relief: 0` collapses the edge and the lift to\n // nothing, which is what the library looks like today; `1` gives it relief. Same classes.\n //\n // daisyUI darkens a fill with `color-mix(in oklab, var(--btn-bg), #000 5%)`. Mixing toward\n // `--foreground` instead is a divergence taken on a reason: `black` is only \"darker\" in a light\n // theme, and on a dark one the hover of a pale button should move toward white. One formula\n // reads as \"push this fill away from the page and toward the ink\" in both, and it follows a\n // tenant's document instead of a constant — which is also why `--relief` can be a single number.\n // Their half-pixel inset gloss needs a colour that is toward the light side in both modes and\n // neither `--foreground` nor `--background` is that, so it waits for a reason to exist.\n \"[--btn-edge:color-mix(in_oklab,var(--btn-bd),var(--foreground)_calc(var(--relief)*8%))]\",\n \"[--btn-lift:color-mix(in_oklab,var(--btn-bg)_calc(var(--relief)*30%),transparent)]\",\n \"bg-(--btn-bg) text-(--btn-fg)\",\n // The grain, and the whole of the conditional that switches it off: at `--noise: 0` the layer\n // is sized to zero and never painted, at `1` it tiles. No variant — which is the property that\n // lets it be a value a tenant sets rather than a look somebody has to write. It defaults to `0`\n // here rather than in every theme, because a theme that wants no texture is saying nothing.\n \"bg-(image:--fx-noise) bg-size-[calc(var(--noise,0)*100%)]\",\n // `rounded-[var(--radius-field)]` and not `rounded-field`, which is the same radius and the\n // spelling every other recipe here uses: `tailwind-merge` knows nothing about a theme key it\n // was never configured with, so `rounded-field` and a caller's `rounded-full` both survive the\n // merge and the winner is whichever Tailwind happened to emit last. The `pill` variant is that\n // caller. An arbitrary value is in a group the merge does know.\n \"border-[length:var(--stroke)] border-(--btn-edge) rounded-[var(--radius-field)]\",\n // As `shadow-*` rather than a raw `box-shadow`, so it composes with `focus-visible:ring-[3px]`\n // through Tailwind's shadow chain instead of being replaced by it. At `--relief: 0` the lift is\n // transparent and nothing is drawn either way, which is why that was invisible before.\n \"shadow-[0_3px_2px_-2px_var(--btn-lift),0_4px_3px_-2px_var(--btn-lift)]\",\n // One hover and one active for every variant, solid and transparent alike — and the transparent\n // case is the tell that the formula is right. Mixing `transparent` with the foreground at 10%\n // IS the foreground at 10% alpha, which is exactly the wash a ghost button wants.\n //\n // The CSS rule guarded these with `:not(:disabled, [aria-disabled=\"true\"], [data-disabled])`.\n // All three spellings already carry `pointer-events-none` on this same base and an element with\n // `pointer-events: none` can match neither `:hover` nor `:active`, so the guard was\n // belt-and-braces. Dropping it is not tidiness: with the plain modifier, `tailwind-merge` can\n // see a variant's own `hover:bg-*` as the same utility and let it win — which is how `link`\n // opts out in one class, instead of losing to a longer selector on specificity.\n \"hover:bg-[color-mix(in_oklab,var(--btn-bg),var(--foreground)_10%)]\",\n \"active:bg-[color-mix(in_oklab,var(--btn-bg),var(--foreground)_16%)]\",\n \"inline-flex shrink-0 items-center justify-center gap-2\",\n \"whitespace-nowrap font-medium text-sm\",\n \"border-solid\",\n \"transition-all\",\n \"outline-none focus-visible:ring-[3px] focus-visible:ring-ring\",\n \"disabled:pointer-events-none disabled:opacity-64\",\n \"data-disabled:pointer-events-none data-disabled:opacity-64\",\n \"aria-disabled:pointer-events-none aria-disabled:opacity-64\",\n \"data-[state=loading]:pointer-events-none\",\n \"aria-invalid:border-destructive aria-invalid:ring-destructive/24\",\n \"[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n \"motion-reduce:transition-none!\",\n ],\n variants: {\n variant: {\n default: [\n \"[--btn-bg:var(--primary)]\",\n \"[--btn-fg:var(--primary-foreground)]\",\n \"focus-visible:border-background\",\n ],\n outline: [\n \"[--btn-bg:transparent]\",\n \"[--btn-fg:var(--foreground)]\",\n \"[--btn-bd:var(--input,var(--border))]\",\n \"focus-visible:border-primary\",\n ],\n destructive: [\n \"[--btn-bg:var(--destructive)]\",\n // Was `text-white` at 3.81 on red-500. The fill moved to `-600` and the ink became a token,\n // because no choice of ink rescued red-500: white 3.81, near-black 4.15, AA needs 4.5.\n \"[--btn-fg:var(--destructive-content)]\",\n \"focus-visible:border-background focus-visible:ring-destructive-foreground/32\",\n ],\n secondary: [\n \"[--btn-bg:var(--secondary)]\",\n \"[--btn-fg:var(--secondary-foreground,var(--foreground))]\",\n \"focus-visible:border-primary\",\n ],\n ghost: [\n \"[--btn-bg:transparent]\",\n \"[--btn-fg:var(--foreground)]\",\n \"focus-visible:border-primary\",\n ],\n // The one variant that opts OUT of the shared hover, because a link's hover is an underline\n // and not a wash. Same modifier and same utility group as the base's wash, so\n // `tailwind-variants` merges the two and the variant wins — which is what makes opting out\n // one class rather than an exception somewhere else.\n link: [\n \"[--btn-bg:transparent]\",\n \"[--btn-fg:var(--primary)]\",\n \"underline-offset-4\",\n \"hover:bg-transparent hover:underline active:bg-transparent\",\n \"focus-visible:border-primary\",\n ],\n },\n /**\n * Padding, gaps and icon sizes. A size variant only **moves `--size`** — the height that reads\n * it is on the `base` — so a tenant can ask for compact controls, through `--size-field`,\n * without asking for tighter text.\n *\n * The floor is still measured rather than chosen. `xs` and `icon-xs` were both `1.5rem`, and\n * every size is a `rem` against a root the density axis sets — 16px default, 14px compact, 18px\n * comfortable — so they measured **21px in compact**, under the 24×24 WCAG 2.5.8 states in CSS\n * pixels. `sm` is the smallest size that clears the bar in all three densities. No call site was\n * failing: the spacing exception saves a clustered control, and every measured `xs` was in a\n * cluster. The variant went because its NAME promised a size it could not deliver at one\n * density, and nothing was published to break.\n */\n size: {\n sm: [controlSizes.sm, \"px-2.5\", \"gap-1.5\", \"[&_svg:not([class*='size-'])]:size-3.5\"],\n md: [controlSizes.md, \"px-3\", \"py-2\"],\n lg: [controlSizes.lg, \"px-3.5\"],\n xl: [\"[--size:calc(var(--size-field)*10)]\", \"text-base\", \"px-4\"],\n // Square, and the width is stated rather than left to `aspect-ratio`: a caller who writes\n // `w-fit` on top of an aspect ratio gets the icon's own width and a control twice as tall.\n \"icon-sm\": [controlSizes.sm, \"w-(--size)\", \"px-0\"],\n \"icon-md\": [controlSizes.md, \"w-(--size)\", \"px-0\"],\n \"icon-lg\": [controlSizes.lg, \"w-(--size)\", \"px-0\"],\n \"icon-xl\": [\n \"[--size:calc(var(--size-field)*10)]\",\n \"w-(--size)\",\n \"px-0\",\n \"[&_svg:not([class*='size-'])]:size-5\",\n ],\n },\n clickEffect: {\n true: \"active:not-aria-[haspopup]:scale-[0.98]\",\n },\n pill: {\n true: [\n \"rounded-full\",\n \"has-[>svg]:data-[size=sm]:pe-3.5\",\n \"has-[>svg]:data-[size=md]:pe-4\",\n \"has-[>svg]:data-[size=lg]:pe-4.5\",\n \"has-[>svg]:data-[size=xl]:pe-5\",\n ],\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"md\",\n clickEffect: true,\n pill: false,\n },\n});\n\nexport interface ButtonProps\n extends React.ComponentProps<typeof ark.button>,\n VariantProps<typeof buttonVariants> {\n /**\n * Apply a click effect to the button\n *\n * @default true\n */\n clickEffect?: boolean;\n /**\n * Show a loading indicator\n *\n * @default false\n */\n isLoading?: boolean;\n}\n\nexport const Button = (props: ButtonProps) => {\n const {\n variant = \"default\",\n size = \"md\",\n clickEffect = true,\n pill = false,\n isLoading = false,\n className,\n children,\n slot,\n ...rest\n } = props;\n\n return (\n <ark.button\n className={cn(\n buttonVariants({ variant, size, clickEffect, pill }),\n className\n )}\n data-size={size}\n data-state={isLoading ? \"loading\" : \"idle\"}\n data-variant={variant}\n type=\"button\"\n {...rest}\n data-slot={slot ?? \"button\"}\n aria-busy={isLoading}\n aria-disabled={isLoading}\n >\n {isLoading ? (\n <>\n <span aria-hidden className=\"invisible\">\n {children}\n </span>\n\n <span className=\"sr-only\">{children}</span>\n\n <span className=\"absolute inset-0 flex items-center justify-center\">\n <Spinner aria-hidden />\n </span>\n </>\n ) : (\n children\n )}\n </ark.button>\n );\n};\n"],"names":["buttonVariants","tv","controlHeight","controlSizes","Button","props","variant","size","clickEffect","pill","isLoading","className","children","slot","rest","jsx","ark","cn","jsxs","Fragment","Spinner"],"mappings":"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"}
|
package/dist/simples/command.js
CHANGED
|
@@ -155,7 +155,7 @@ const q = y, J = (o) => {
|
|
|
155
155
|
"flex items-center justify-between gap-2",
|
|
156
156
|
"-m-2 mt-2 px-4 py-3",
|
|
157
157
|
"text-muted-foreground text-xs",
|
|
158
|
-
"rounded-b-[calc(var(--radius-2xl
|
|
158
|
+
"rounded-b-[calc(var(--radius-2xl)-1px)] border-t",
|
|
159
159
|
t
|
|
160
160
|
),
|
|
161
161
|
...r,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command.js","sources":["../../src/simples/command.tsx"],"sourcesContent":["import { Portal } from \"@ark-ui/react\";\nimport { Combobox as ArkCombobox } from \"@ark-ui/react/combobox\";\nimport { Dialog as ArkDialog } from \"@ark-ui/react/dialog\";\nimport { SearchIcon } from \"lucide-react\";\nimport type React from \"react\";\nimport { cn } from \"../lib/cn\";\nimport {\n Combobox,\n ComboboxControl,\n ComboboxEmpty,\n ComboboxGroup,\n type ComboboxItem,\n ComboboxList,\n comboboxItemVariants,\n} from \"./combobox\";\nimport {\n Dialog,\n type DialogContent,\n DialogHeader,\n DialogOverlay,\n DialogPositioner,\n dialogContentVariants,\n} from \"./dialog\";\nimport type { InputProps } from \"./input\";\nimport {\n InputGroup,\n InputGroupAddon,\n InputGroupInput,\n} from \"./input-group\";\nimport { MenuShortcut } from \"./menu\";\nimport { Separator } from \"./separator\";\n\nexport const CommandDialog = Dialog;\n\n// `CommandDialog` *is* `Dialog`, so its trigger is `DialogTrigger` — a renamed alias only made\n// the two look like different machines.\ninterface CommandDialogContentProps\n extends React.ComponentProps<typeof DialogContent> {\n /**\n * The description of the dialog\n *\n * @default \"Search for a command to run...\"\n */\n description?: string;\n /**\n * The title of the dialog\n *\n * @default \"Command Palette\"\n */\n title?: string;\n}\n\nexport const CommandDialogContent = (props: CommandDialogContentProps) => {\n const {\n size = \"lg\",\n title = \"Command Palette\",\n description = \"Search for a command to run...\",\n className,\n children,\n slot,\n ...rest\n } = props;\n\n return (\n <Portal>\n <DialogOverlay />\n\n <DialogPositioner>\n <ArkDialog.Content\n className={cn(\n \"max-sm:row-start-1\",\n dialogContentVariants({ size }),\n \"border-0 p-0\",\n className\n )}\n {...rest}\n data-slot={slot ?? \"command-dialog-content\"}\n >\n <DialogHeader\n className=\"sr-only\"\n description={description}\n title={title}\n />\n\n {children}\n </ArkDialog.Content>\n </DialogPositioner>\n </Portal>\n );\n};\n\nexport const Command: ArkCombobox.RootComponent = (props) => {\n const { lazyMount = true, unmountOnExit = true, className, ...rest } = props;\n\n return (\n <Combobox\n className={cn(\n \"isolate\",\n \"flex min-h-0 flex-1 flex-col\",\n \"p-2\",\n \"bg-popover\",\n \"text-popover-foreground\",\n \"rounded-2xl border\",\n className\n )}\n closeOnSelect={false}\n disableLayer\n inputBehavior=\"autohighlight\"\n lazyMount={lazyMount}\n loopFocus={false}\n open\n selectionBehavior=\"clear\"\n unmountOnExit={unmountOnExit}\n {...rest}\n />\n );\n};\n\ninterface CommandInputProps\n extends Omit<React.ComponentProps<typeof ArkCombobox.Input>, \"size\"> {\n /**\n * The size of the input\n *\n * @default \"md\"\n */\n size?: InputProps[\"size\"];\n}\n\nexport const CommandContent = (\n props: React.ComponentProps<typeof ArkCombobox.Content>\n) => {\n const { className, slot, ...rest } = props;\n\n return (\n <ArkCombobox.Content\n className={cn(\n \"flex flex-1 flex-col\",\n \"max-h-(--available-height) min-h-0\",\n \"-me-2\",\n \"outline-none\",\n \"scrollbar-thin scrollbar-track-transparent scrollbar-thumb-foreground/20 overflow-auto overscroll-contain\",\n \"[:not(.has-[+[data-slot=command-footer]])]:rounded-b-2xl [:not(.has-[+[data-slot=command-footer]])]:border-b\",\n className\n )}\n {...rest}\n data-slot={slot ?? \"command-content\"}\n />\n );\n};\n\nexport const CommandInput = (props: CommandInputProps) => {\n const { size = \"md\", className, ...rest } = props;\n\n return (\n <ComboboxControl className=\"mb-2\">\n <InputGroup\n className={cn(\"rounded-xl bg-field\", className)}\n size={size}\n {...rest}\n >\n <InputGroupAddon>\n <SearchIcon aria-hidden className=\"opacity-64\" />\n </InputGroupAddon>\n <ArkCombobox.Input asChild>\n <InputGroupInput autoFocus slot=\"command-input\" />\n </ArkCombobox.Input>\n </InputGroup>\n </ComboboxControl>\n );\n};\n\ninterface CommandListProps extends React.ComponentProps<typeof ComboboxList> {}\n\nexport const CommandList = (props: CommandListProps) => {\n const { className, slot, ...rest } = props;\n\n return (\n <div className=\"max-h-72 min-h-0 flex-1\">\n <ComboboxList\n className={cn(\"flex-1 pe-2.5\", className)}\n {...rest}\n slot={slot ?? \"command-list\"}\n />\n </div>\n );\n};\n\nexport const CommandEmpty = (\n props: React.ComponentProps<typeof ComboboxEmpty>\n) => {\n const { className, children, slot, ...rest } = props;\n\n return (\n <ComboboxEmpty\n className={cn(\"py-6 text-center text-sm\", className)}\n {...rest}\n slot={slot ?? \"command-empty\"}\n >\n {children || \"No results found.\"}\n </ComboboxEmpty>\n );\n};\n\n// A group's label comes from `heading`, which `ComboboxGroup` renders itself — so the renamed\n// `CommandGroupLabel` was redundant twice over and is gone.\nexport const CommandGroup = (\n { slot, ...rest }: React.ComponentProps<typeof ComboboxGroup>\n) => <ComboboxGroup {...rest} slot={slot ?? \"command-group\"} />;\n\nexport const CommandItem = (\n props: React.ComponentProps<typeof ComboboxItem>\n) => {\n const { className, slot, ...rest } = props;\n\n return (\n <ArkCombobox.Item\n className={cn(comboboxItemVariants({ showIndicator: false }), className)}\n persistFocus\n {...rest}\n data-slot={slot ?? \"command-item\"}\n />\n );\n};\n\nexport const CommandSeparator = (props: React.ComponentProps<\"div\">) => {\n const { className, slot, ...rest } = props;\n\n return (\n <Separator\n className={cn(\"my-2\", className)}\n {...rest}\n slot={slot ?? \"command-separator\"}\n />\n );\n};\n\nexport const CommandShortcut = (\n { slot, ...rest }: React.ComponentProps<typeof MenuShortcut>\n) => <MenuShortcut {...rest} slot={slot ?? \"command-shortcut\"} />;\n\nexport const CommandFooter = (props: React.ComponentProps<\"div\">) => {\n const { className, slot, ...rest } = props;\n\n return (\n <div\n className={cn(\n \"z-10\",\n \"flex items-center justify-between gap-2\",\n \"-m-2 mt-2 px-4 py-3\",\n \"text-muted-foreground text-xs\",\n \"rounded-b-[calc(var(--radius-2xl,1rem)-1px)] border-t\",\n className\n )}\n {...rest}\n data-slot={slot ?? \"command-footer\"}\n />\n );\n};\n"],"names":["CommandDialog","Dialog","CommandDialogContent","props","size","title","description","className","children","slot","rest","Portal","jsx","DialogOverlay","DialogPositioner","jsxs","ArkDialog","cn","dialogContentVariants","DialogHeader","Command","lazyMount","unmountOnExit","Combobox","CommandContent","ArkCombobox","CommandInput","ComboboxControl","InputGroup","InputGroupAddon","SearchIcon","InputGroupInput","CommandList","ComboboxList","CommandEmpty","ComboboxEmpty","CommandGroup","ComboboxGroup","CommandItem","comboboxItemVariants","CommandSeparator","Separator","CommandShortcut","MenuShortcut","CommandFooter"],"mappings":";;;;;;;;;;;AAgCO,MAAMA,IAAgBC,GAoBhBC,IAAuB,CAACC,MAAqC;AACxE,QAAM;AAAA,IACJ,MAAAC,IAAO;AAAA,IACP,OAAAC,IAAQ;AAAA,IACR,aAAAC,IAAc;AAAA,IACd,WAAAC;AAAA,IACA,UAAAC;AAAA,IACA,MAAAC;AAAA,IACA,GAAGC;AAAA,EAAA,IACDP;AAEJ,2BACGQ,GAAA,EACC,UAAA;AAAA,IAAA,gBAAAC,EAACC,GAAA,EAAc;AAAA,sBAEdC,GAAA,EACC,UAAA,gBAAAC;AAAA,MAACC,EAAU;AAAA,MAAV;AAAA,QACC,WAAWC;AAAA,UACT;AAAA,UACAC,EAAsB,EAAE,MAAAd,GAAM;AAAA,UAC9B;AAAA,UACAG;AAAA,QAAA;AAAA,QAED,GAAGG;AAAA,QACJ,aAAWD,KAAQ;AAAA,QAEnB,UAAA;AAAA,UAAA,gBAAAG;AAAA,YAACO;AAAA,YAAA;AAAA,cACC,WAAU;AAAA,cACV,aAAAb;AAAA,cACA,OAAAD;AAAA,YAAA;AAAA,UAAA;AAAA,UAGDG;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA,EACH,CACF;AAAA,EAAA,GACF;AAEJ,GAEaY,IAAqC,CAACjB,MAAU;AAC3D,QAAM,EAAE,WAAAkB,IAAY,IAAM,eAAAC,IAAgB,IAAM,WAAAf,GAAW,GAAGG,MAASP;AAEvE,SACE,gBAAAS;AAAA,IAACW;AAAA,IAAA;AAAA,MACC,WAAWN;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACAV;AAAA,MAAA;AAAA,MAEF,eAAe;AAAA,MACf,cAAY;AAAA,MACZ,eAAc;AAAA,MACd,WAAAc;AAAA,MACA,WAAW;AAAA,MACX,MAAI;AAAA,MACJ,mBAAkB;AAAA,MAClB,eAAAC;AAAA,MACC,GAAGZ;AAAA,IAAA;AAAA,EAAA;AAGV,GAYac,IAAiB,CAC5BrB,MACG;AACH,QAAM,EAAE,WAAAI,GAAW,MAAAE,GAAM,GAAGC,MAASP;AAErC,SACE,gBAAAS;AAAA,IAACa,EAAY;AAAA,IAAZ;AAAA,MACC,WAAWR;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACAV;AAAA,MAAA;AAAA,MAED,GAAGG;AAAA,MACJ,aAAWD,KAAQ;AAAA,IAAA;AAAA,EAAA;AAGzB,GAEaiB,IAAe,CAACvB,MAA6B;AACxD,QAAM,EAAE,MAAAC,IAAO,MAAM,WAAAG,GAAW,GAAGG,MAASP;AAE5C,SACE,gBAAAS,EAACe,GAAA,EAAgB,WAAU,QACzB,UAAA,gBAAAZ;AAAA,IAACa;AAAA,IAAA;AAAA,MACC,WAAWX,EAAG,uBAAuBV,CAAS;AAAA,MAC9C,MAAAH;AAAA,MACC,GAAGM;AAAA,MAEJ,UAAA;AAAA,QAAA,gBAAAE,EAACiB,KACC,UAAA,gBAAAjB,EAACkB,GAAA,EAAW,eAAW,IAAC,WAAU,cAAa,EAAA,CACjD;AAAA,QACA,gBAAAlB,EAACa,EAAY,OAAZ,EAAkB,SAAO,IACxB,UAAA,gBAAAb,EAACmB,GAAA,EAAgB,WAAS,IAAC,MAAK,gBAAA,CAAgB,EAAA,CAClD;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA,GAEJ;AAEJ,GAIaC,IAAc,CAAC7B,MAA4B;AACtD,QAAM,EAAE,WAAAI,GAAW,MAAAE,GAAM,GAAGC,MAASP;AAErC,SACE,gBAAAS,EAAC,OAAA,EAAI,WAAU,2BACb,UAAA,gBAAAA;AAAA,IAACqB;AAAA,IAAA;AAAA,MACC,WAAWhB,EAAG,iBAAiBV,CAAS;AAAA,MACvC,GAAGG;AAAA,MACJ,MAAMD,KAAQ;AAAA,IAAA;AAAA,EAAA,GAElB;AAEJ,GAEayB,IAAe,CAC1B/B,MACG;AACH,QAAM,EAAE,WAAAI,GAAW,UAAAC,GAAU,MAAAC,GAAM,GAAGC,MAASP;AAE/C,SACE,gBAAAS;AAAA,IAACuB;AAAA,IAAA;AAAA,MACC,WAAWlB,EAAG,4BAA4BV,CAAS;AAAA,MAClD,GAAGG;AAAA,MACJ,MAAMD,KAAQ;AAAA,MAEb,UAAAD,KAAY;AAAA,IAAA;AAAA,EAAA;AAGnB,GAIa4B,IAAe,CAC1B,EAAE,MAAA3B,GAAM,GAAGC,EAAA,MACR,gBAAAE,EAACyB,GAAA,EAAe,GAAG3B,GAAM,MAAMD,KAAQ,gBAAA,CAAiB,GAEhD6B,IAAc,CACzBnC,MACG;AACH,QAAM,EAAE,WAAAI,GAAW,MAAAE,GAAM,GAAGC,MAASP;AAErC,SACE,gBAAAS;AAAA,IAACa,EAAY;AAAA,IAAZ;AAAA,MACC,WAAWR,EAAGsB,EAAqB,EAAE,eAAe,GAAA,CAAO,GAAGhC,CAAS;AAAA,MACvE,cAAY;AAAA,MACX,GAAGG;AAAA,MACJ,aAAWD,KAAQ;AAAA,IAAA;AAAA,EAAA;AAGzB,GAEa+B,IAAmB,CAACrC,MAAuC;AACtE,QAAM,EAAE,WAAAI,GAAW,MAAAE,GAAM,GAAGC,MAASP;AAErC,SACE,gBAAAS;AAAA,IAAC6B;AAAA,IAAA;AAAA,MACC,WAAWxB,EAAG,QAAQV,CAAS;AAAA,MAC9B,GAAGG;AAAA,MACJ,MAAMD,KAAQ;AAAA,IAAA;AAAA,EAAA;AAGpB,GAEaiC,IAAkB,CAC7B,EAAE,MAAAjC,GAAM,GAAGC,EAAA,MACR,gBAAAE,EAAC+B,GAAA,EAAc,GAAGjC,GAAM,MAAMD,KAAQ,mBAAA,CAAoB,GAElDmC,IAAgB,CAACzC,MAAuC;AACnE,QAAM,EAAE,WAAAI,GAAW,MAAAE,GAAM,GAAGC,MAASP;AAErC,SACE,gBAAAS;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAWK;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACAV;AAAA,MAAA;AAAA,MAED,GAAGG;AAAA,MACJ,aAAWD,KAAQ;AAAA,IAAA;AAAA,EAAA;AAGzB;"}
|
|
1
|
+
{"version":3,"file":"command.js","sources":["../../src/simples/command.tsx"],"sourcesContent":["import { Portal } from \"@ark-ui/react\";\nimport { Combobox as ArkCombobox } from \"@ark-ui/react/combobox\";\nimport { Dialog as ArkDialog } from \"@ark-ui/react/dialog\";\nimport { SearchIcon } from \"lucide-react\";\nimport type React from \"react\";\nimport { cn } from \"../lib/cn\";\nimport {\n Combobox,\n ComboboxControl,\n ComboboxEmpty,\n ComboboxGroup,\n type ComboboxItem,\n ComboboxList,\n comboboxItemVariants,\n} from \"./combobox\";\nimport {\n Dialog,\n type DialogContent,\n DialogHeader,\n DialogOverlay,\n DialogPositioner,\n dialogContentVariants,\n} from \"./dialog\";\nimport type { InputProps } from \"./input\";\nimport {\n InputGroup,\n InputGroupAddon,\n InputGroupInput,\n} from \"./input-group\";\nimport { MenuShortcut } from \"./menu\";\nimport { Separator } from \"./separator\";\n\nexport const CommandDialog = Dialog;\n\n// `CommandDialog` *is* `Dialog`, so its trigger is `DialogTrigger` — a renamed alias only made\n// the two look like different machines.\ninterface CommandDialogContentProps\n extends React.ComponentProps<typeof DialogContent> {\n /**\n * The description of the dialog\n *\n * @default \"Search for a command to run...\"\n */\n description?: string;\n /**\n * The title of the dialog\n *\n * @default \"Command Palette\"\n */\n title?: string;\n}\n\nexport const CommandDialogContent = (props: CommandDialogContentProps) => {\n const {\n size = \"lg\",\n title = \"Command Palette\",\n description = \"Search for a command to run...\",\n className,\n children,\n slot,\n ...rest\n } = props;\n\n return (\n <Portal>\n <DialogOverlay />\n\n <DialogPositioner>\n <ArkDialog.Content\n className={cn(\n \"max-sm:row-start-1\",\n dialogContentVariants({ size }),\n \"border-0 p-0\",\n className\n )}\n {...rest}\n data-slot={slot ?? \"command-dialog-content\"}\n >\n <DialogHeader\n className=\"sr-only\"\n description={description}\n title={title}\n />\n\n {children}\n </ArkDialog.Content>\n </DialogPositioner>\n </Portal>\n );\n};\n\nexport const Command: ArkCombobox.RootComponent = (props) => {\n const { lazyMount = true, unmountOnExit = true, className, ...rest } = props;\n\n return (\n <Combobox\n className={cn(\n \"isolate\",\n \"flex min-h-0 flex-1 flex-col\",\n \"p-2\",\n \"bg-popover\",\n \"text-popover-foreground\",\n \"rounded-2xl border\",\n className\n )}\n closeOnSelect={false}\n disableLayer\n inputBehavior=\"autohighlight\"\n lazyMount={lazyMount}\n loopFocus={false}\n open\n selectionBehavior=\"clear\"\n unmountOnExit={unmountOnExit}\n {...rest}\n />\n );\n};\n\ninterface CommandInputProps\n extends Omit<React.ComponentProps<typeof ArkCombobox.Input>, \"size\"> {\n /**\n * The size of the input\n *\n * @default \"md\"\n */\n size?: InputProps[\"size\"];\n}\n\nexport const CommandContent = (\n props: React.ComponentProps<typeof ArkCombobox.Content>\n) => {\n const { className, slot, ...rest } = props;\n\n return (\n <ArkCombobox.Content\n className={cn(\n \"flex flex-1 flex-col\",\n \"max-h-(--available-height) min-h-0\",\n \"-me-2\",\n \"outline-none\",\n \"scrollbar-thin scrollbar-track-transparent scrollbar-thumb-foreground/20 overflow-auto overscroll-contain\",\n \"[:not(.has-[+[data-slot=command-footer]])]:rounded-b-2xl [:not(.has-[+[data-slot=command-footer]])]:border-b\",\n className\n )}\n {...rest}\n data-slot={slot ?? \"command-content\"}\n />\n );\n};\n\nexport const CommandInput = (props: CommandInputProps) => {\n const { size = \"md\", className, ...rest } = props;\n\n return (\n <ComboboxControl className=\"mb-2\">\n <InputGroup\n className={cn(\"rounded-xl bg-field\", className)}\n size={size}\n {...rest}\n >\n <InputGroupAddon>\n <SearchIcon aria-hidden className=\"opacity-64\" />\n </InputGroupAddon>\n <ArkCombobox.Input asChild>\n <InputGroupInput autoFocus slot=\"command-input\" />\n </ArkCombobox.Input>\n </InputGroup>\n </ComboboxControl>\n );\n};\n\ninterface CommandListProps extends React.ComponentProps<typeof ComboboxList> {}\n\nexport const CommandList = (props: CommandListProps) => {\n const { className, slot, ...rest } = props;\n\n return (\n <div className=\"max-h-72 min-h-0 flex-1\">\n <ComboboxList\n className={cn(\"flex-1 pe-2.5\", className)}\n {...rest}\n slot={slot ?? \"command-list\"}\n />\n </div>\n );\n};\n\nexport const CommandEmpty = (\n props: React.ComponentProps<typeof ComboboxEmpty>\n) => {\n const { className, children, slot, ...rest } = props;\n\n return (\n <ComboboxEmpty\n className={cn(\"py-6 text-center text-sm\", className)}\n {...rest}\n slot={slot ?? \"command-empty\"}\n >\n {children || \"No results found.\"}\n </ComboboxEmpty>\n );\n};\n\n// A group's label comes from `heading`, which `ComboboxGroup` renders itself — so the renamed\n// `CommandGroupLabel` was redundant twice over and is gone.\nexport const CommandGroup = (\n { slot, ...rest }: React.ComponentProps<typeof ComboboxGroup>\n) => <ComboboxGroup {...rest} slot={slot ?? \"command-group\"} />;\n\nexport const CommandItem = (\n props: React.ComponentProps<typeof ComboboxItem>\n) => {\n const { className, slot, ...rest } = props;\n\n return (\n <ArkCombobox.Item\n className={cn(comboboxItemVariants({ showIndicator: false }), className)}\n persistFocus\n {...rest}\n data-slot={slot ?? \"command-item\"}\n />\n );\n};\n\nexport const CommandSeparator = (props: React.ComponentProps<\"div\">) => {\n const { className, slot, ...rest } = props;\n\n return (\n <Separator\n className={cn(\"my-2\", className)}\n {...rest}\n slot={slot ?? \"command-separator\"}\n />\n );\n};\n\nexport const CommandShortcut = (\n { slot, ...rest }: React.ComponentProps<typeof MenuShortcut>\n) => <MenuShortcut {...rest} slot={slot ?? \"command-shortcut\"} />;\n\nexport const CommandFooter = (props: React.ComponentProps<\"div\">) => {\n const { className, slot, ...rest } = props;\n\n return (\n <div\n className={cn(\n \"z-10\",\n \"flex items-center justify-between gap-2\",\n \"-m-2 mt-2 px-4 py-3\",\n \"text-muted-foreground text-xs\",\n \"rounded-b-[calc(var(--radius-2xl)-1px)] border-t\",\n className\n )}\n {...rest}\n data-slot={slot ?? \"command-footer\"}\n />\n );\n};\n"],"names":["CommandDialog","Dialog","CommandDialogContent","props","size","title","description","className","children","slot","rest","Portal","jsx","DialogOverlay","DialogPositioner","jsxs","ArkDialog","cn","dialogContentVariants","DialogHeader","Command","lazyMount","unmountOnExit","Combobox","CommandContent","ArkCombobox","CommandInput","ComboboxControl","InputGroup","InputGroupAddon","SearchIcon","InputGroupInput","CommandList","ComboboxList","CommandEmpty","ComboboxEmpty","CommandGroup","ComboboxGroup","CommandItem","comboboxItemVariants","CommandSeparator","Separator","CommandShortcut","MenuShortcut","CommandFooter"],"mappings":"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"}
|
package/dist/simples/dialog.js
CHANGED
|
@@ -101,7 +101,7 @@ const H = (t) => {
|
|
|
101
101
|
"max-sm:max-h-[calc(100svh-3rem)]",
|
|
102
102
|
"max-sm:max-w-none",
|
|
103
103
|
"max-sm:rounded-none max-sm:rounded-t-2xl max-sm:border-x-0 max-sm:border-t max-sm:border-b-0",
|
|
104
|
-
"max-sm:opacity-[calc(1-min(var(--nested-
|
|
104
|
+
"max-sm:opacity-[calc(1-min(var(--nested-layer-count),1))]",
|
|
105
105
|
"max-sm:data-[state=closed]:slide-out-to-bottom-5 max-sm:data-[state=open]:slide-in-from-bottom-5",
|
|
106
106
|
"max-sm:data-[state=closed]:zoom-out-100 max-sm:data-[state=open]:zoom-in-100"
|
|
107
107
|
]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dialog.js","sources":["../../src/simples/dialog.tsx"],"sourcesContent":["\"use client\";\n\nimport { Dialog as ArkDialog, useDialogContext } from \"@ark-ui/react/dialog\";\nimport { ark } from \"@ark-ui/react/factory\";\nimport { Portal } from \"@ark-ui/react/portal\";\nimport { XIcon } from \"lucide-react\";\nimport React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\nimport { cn } from \"../lib/cn\";\nimport { Button } from \"./button\";\nimport { ScrollArea } from \"./scroll-area\";\n\nexport const useDialog = useDialogContext;\n\ninterface DialogContextProps {\n /**\n * Used internally to show or hide overlay\n *\n * @default true\n */\n modal?: boolean;\n}\n\nconst DialogContext = React.createContext<DialogContextProps | null>(null);\nDialogContext.displayName = \"DialogContext\";\n\nexport const Dialog = (props: React.ComponentProps<typeof ArkDialog.Root>) => {\n const {\n modal = true,\n lazyMount = true,\n unmountOnExit = true,\n ...rest\n } = props;\n\n return (\n <DialogContext.Provider value={{ modal }}>\n <ArkDialog.Root\n lazyMount={lazyMount}\n modal={modal}\n unmountOnExit={unmountOnExit}\n {...rest}\n />\n </DialogContext.Provider>\n );\n};\n\nexport const DialogTrigger = (\n props: React.ComponentProps<typeof ArkDialog.Trigger>\n) => <ArkDialog.Trigger {...props} />;\n\nexport const dialogOverlayVariants = tv({\n base: [\n \"fixed inset-0 z-50\",\n \"bg-black/32 backdrop-blur-xs\",\n \"duration-200\",\n \"peer peer-data-[slot=dialog-overlay]:hidden\",\n \"data-[state=open]:fade-in-0 data-[state=open]:animate-in\",\n \"data-[state=closed]:fade-out-0 data-[state=closed]:animate-out\",\n \"motion-reduce:animate-none!\",\n ],\n});\n\nexport const DialogOverlay = (\n props: React.ComponentProps<typeof ArkDialog.Backdrop>\n) => {\n const { className, slot, ...rest } = props;\n\n const { modal } = _useDialog();\n\n if (!modal) {\n return null;\n }\n\n return (\n <ArkDialog.Backdrop\n className={cn(dialogOverlayVariants(), className)}\n {...rest}\n data-slot={slot ?? \"dialog-overlay\"}\n />\n );\n};\n\nexport const DialogPositioner = (\n props: React.ComponentProps<typeof ArkDialog.Positioner>\n) => {\n const { className, slot, ...rest } = props;\n\n return (\n <ArkDialog.Positioner\n className={cn(\n \"fixed inset-0 z-50\",\n \"h-svh w-screen\",\n \"grid grid-rows-[1fr_auto_3fr] justify-items-center\",\n \"p-4\",\n className\n )}\n {...rest}\n data-slot={slot ?? \"dialog-positioner\"}\n />\n );\n};\n\nexport const dialogContentVariants = tv({\n base: [\n \"[--space:--spacing(6)]\",\n \"z-[calc(50+var(--layer-index,0))]\",\n \"relative\",\n \"row-start-2\",\n \"max-h-[calc(100svh-2rem)] min-h-0 w-full min-w-0\",\n \"flex flex-col overflow-hidden\",\n \"bg-popover\",\n \"text-popover-foreground\",\n \"rounded-2xl border shadow-lg/5\",\n \"outline-none\",\n \"translate-y-[calc(-1.25rem*var(--nested-layer-count))]\",\n \"transition-[scale,opacity,translate] duration-200 ease-in-out will-change-transform\",\n \"data-[nested=dialog]:data-[state=closed]:slide-in-from-bottom-10 data-[nested=dialog]:data-[state=open]:slide-in-from-bottom-10 data-[has-nested=dialog]:origin-top\",\n \"scale-[calc(1-0.1*var(--nested-layer-count))] opacity-[calc(1-0.1*var(--nested-layer-count))]\",\n \"data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-[98%] data-[state=closed]:animate-out\",\n \"data-[state=open]:fade-in-0 data-[state=open]:zoom-in-[98%] data-[state=open]:animate-in\",\n \"motion-reduce:animate-none! motion-reduce:transition-none!\",\n ],\n variants: {\n size: {\n sm: [\"max-w-md\"],\n md: [\"max-w-lg\"],\n lg: [\"max-w-xl\"],\n xl: [\"max-w-2xl\"],\n \"2xl\": [\"max-w-3xl\"],\n \"3xl\": [\"max-w-4xl\"],\n \"4xl\": [\"max-w-5xl\"],\n \"5xl\": [\"max-w-6xl\"],\n \"6xl\": [\"max-w-7xl\"],\n fullscreen: [\"size-full\"],\n },\n bottomStickOnMobile: {\n true: [\n \"max-sm:max-h-[calc(100svh-3rem)]\",\n \"max-sm:max-w-none\",\n \"max-sm:rounded-none max-sm:rounded-t-2xl max-sm:border-x-0 max-sm:border-t max-sm:border-b-0\",\n \"max-sm:opacity-[calc(1-min(var(--nested-dialogs),1))]\",\n \"max-sm:data-[state=closed]:slide-out-to-bottom-5 max-sm:data-[state=open]:slide-in-from-bottom-5\",\n \"max-sm:data-[state=closed]:zoom-out-100 max-sm:data-[state=open]:zoom-in-100\",\n ],\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n});\n\ninterface DialogContentProps\n extends React.ComponentProps<typeof ArkDialog.Content>,\n VariantProps<typeof dialogContentVariants> {\n /**\n * Stick the dialog to the bottom of the screen on mobile\n *\n * @default true\n */\n bottomStickOnMobile?: boolean;\n /**\n * Show close button at the top right corner\n *\n * @default true\n */\n showCloseButton?: boolean;\n}\n\nexport const DialogContent = (props: DialogContentProps) => {\n const {\n showCloseButton = true,\n bottomStickOnMobile = true,\n size = \"md\",\n className,\n children,\n slot,\n ...rest\n } = props;\n\n return (\n <Portal>\n <DialogOverlay />\n\n <DialogPositioner\n className={cn(\n bottomStickOnMobile &&\n \"max-sm:grid-rows-[1fr_auto] max-sm:p-0 max-sm:pt-12\"\n )}\n >\n <ArkDialog.Content\n className={cn(\n dialogContentVariants({ size, bottomStickOnMobile }),\n className\n )}\n {...rest}\n data-slot={slot ?? \"dialog-content\"}\n >\n {children}\n\n {!!showCloseButton && (\n <DialogClose asChild>\n <Button\n aria-label=\"Close\"\n // Aligned to the content padding: the button sits at 10px so the 16px icon\n // (6px inset inside the 28px icon-sm button) lands its corner on the 16px\n // `--space` grid — mirroring the header title on the opposite side.\n className=\"absolute inset-e-2.5 top-2.5 opacity-64 hover:opacity-100\"\n size=\"icon-sm\"\n variant=\"ghost\"\n >\n <XIcon />\n </Button>\n </DialogClose>\n )}\n </ArkDialog.Content>\n </DialogPositioner>\n </Portal>\n );\n};\n\ninterface DialogBodyProps extends React.ComponentProps<typeof ark.div> {\n /**\n * Add a fade effect to the scroll area\n *\n * @default false\n */\n scrollFade?: boolean;\n}\n\nexport const DialogBody = (props: DialogBodyProps) => {\n const { scrollFade = false, className, slot, ...rest } = props;\n\n return (\n <ScrollArea className=\"min-h-0 flex-1\" scrollFade={scrollFade}>\n <ark.div\n className={cn(\n \"p-(--space)\",\n \"in-[[data-slot=dialog-content]:has([data-slot=dialog-header])]:pt-0\",\n \"in-[[data-slot=dialog-content]:has([data-slot=dialog-footer]:not(.border-t))]:pb-1\",\n className\n )}\n {...rest}\n data-slot={slot ?? \"dialog-body\"}\n />\n </ScrollArea>\n );\n};\n\ninterface DialogHeaderProps extends React.ComponentProps<typeof ark.div> {\n /**\n * The description of the dialog\n */\n description?: string;\n /**\n * The title of the dialog\n */\n title?: string;\n}\n\nexport const DialogHeader = (props: DialogHeaderProps) => {\n const { className, title, description, children, slot, ...rest } = props;\n\n return (\n <ark.div\n className={cn(\n \"shrink-0\",\n \"p-(--space)\",\n \"flex flex-col gap-2\",\n \"in-[[data-slot=dialog-content]:has([data-slot=dialog-body])]:pb-3\",\n className\n )}\n {...rest}\n data-slot={slot ?? \"dialog-header\"}\n >\n {!!title && <DialogTitle>{title}</DialogTitle>}\n\n {!!description && <DialogDescription>{description}</DialogDescription>}\n\n {!title && typeof children === \"string\" ? (\n <DialogTitle>{children}</DialogTitle>\n ) : (\n children\n )}\n </ark.div>\n );\n};\n\nexport const DialogTitle = (\n props: React.ComponentProps<typeof ArkDialog.Title>\n) => {\n const { className, slot, ...rest } = props;\n\n return (\n <ArkDialog.Title\n className={cn(\n \"font-heading font-semibold text-lg leading-none\",\n // Reserve inline-end room so the absolutely-positioned close X never overlaps the title.\n \"in-[[data-slot=dialog-content]:has([data-slot=dialog-close-trigger])]:pe-8\",\n className\n )}\n {...rest}\n data-slot={slot ?? \"dialog-title\"}\n />\n );\n};\n\nexport const DialogDescription = (\n props: React.ComponentProps<typeof ArkDialog.Description>\n) => {\n const { className, slot, ...rest } = props;\n\n return (\n <ArkDialog.Description\n className={cn(\"text-muted-foreground text-sm\", className)}\n {...rest}\n data-slot={slot ?? \"dialog-description\"}\n />\n );\n};\n\nexport const DialogClose = ({\n slot,\n ...rest\n}: React.ComponentProps<typeof ArkDialog.CloseTrigger>) => (\n <ArkDialog.CloseTrigger\n {...rest}\n data-slot={slot ?? \"dialog-close-trigger\"}\n />\n);\n\nexport const DialogFooter = (props: React.ComponentProps<typeof ark.div>) => {\n const { className, slot, ...rest } = props;\n\n return (\n <ark.div\n className={cn(\n \"shrink-0\",\n \"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end\",\n \"sm:rounded-b-[calc(var(--radius-2xl)-1px)]\",\n \"px-(--space) py-4\",\n \"border-t\",\n className\n )}\n {...rest}\n data-slot={slot ?? \"dialog-footer\"}\n />\n );\n};\n\nconst _useDialog = () => {\n const context = React.useContext(DialogContext);\n\n if (!context) {\n throw new Error(\"useDialog must be used within a DialogProvider\");\n }\n\n return context;\n};\n"],"names":["DialogContext"],"mappings":";;;;;;;;;;;AAYO;AAYPA;AAEO;AACL;AAAM;AACI;AACI;AACI;AACb;AAGL;AAEI;AAAW;AAAV;AACC;AACA;AACA;AACI;AAAA;AAIZ;AAMwC;AAChC;AACJ;AACA;AACA;AACA;AACA;AACA;AACA;AAEJ;AAKE;AAIA;AAKE;AAAW;AAAV;AACiD;AAC5C;AACe;AAAA;AAGzB;AAKE;AAEA;AACE;AAAW;AAAV;AACY;AACT;AACA;AACA;AACA;AACA;AAAA;AAEE;AACe;AAAA;AAGzB;AAEwC;AAChC;AACJ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAEQ;AACF;AACW;AACA;AACA;AACC;AACG;AACA;AACA;AACA;AACA;AACK;AAAA;AAEL;AACb;AACJ;AACA;AACA;AACA;AACA;AACA;AAAA;AACF;AACF;AAEe;AACT;AAEV;AAoBE;AAAM;AACc;AACI;AACf;AACP;AACA;AACA;AACG;AAGL;AAEI;AAAe;AAEf;AAAC;AAAA;AACY;AAEP;AAAA;AAGJ;AAAW;AAAV;AACY;AAC0C;AACnD;AAAA;AAEE;AACe;AAElB;AAAA;AAIG;AAAC;AAAA;AACY;AAID;AACL;AACG;AAED;AAAA;AAEX;AAAA;AAAA;AAAA;AAEJ;AAAA;AAIR;AAYE;AAEA;AAEI;AAAK;AAAJ;AACY;AACT;AACA;AACA;AACA;AAAA;AAEE;AACe;AAAA;AAI3B;AAcE;AAEA;AACE;AAAK;AAAJ;AACY;AACT;AACA;AACA;AACA;AACA;AAAA;AAEE;AACe;AAElB;AAA+B;AAEkB;AAKhD;AAAA;AAAA;AAIR;AAKE;AAEA;AACE;AAAW;AAAV;AACY;AACT;AAAA;AAEA;AACA;AAAA;AAEE;AACe;AAAA;AAGzB;AAKE;AAEA;AACE;AAAW;AAAV;AACyD;AACpD;AACe;AAAA;AAGzB;AAE4B;AAC1B;AAEF;AACE;AAAW;AAAV;AACK;AACe;AACrB;AAIA;AAEA;AACE;AAAK;AAAJ;AACY;AACT;AACA;AACA;AACA;AACA;AACA;AAAA;AAEE;AACe;AAAA;AAGzB;AAGE;AAEA;AACE;AAGF;AACF;;;;;;;;;;;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"dialog.js","sources":["../../src/simples/dialog.tsx"],"sourcesContent":["\"use client\";\n\nimport { Dialog as ArkDialog, useDialogContext } from \"@ark-ui/react/dialog\";\nimport { ark } from \"@ark-ui/react/factory\";\nimport { Portal } from \"@ark-ui/react/portal\";\nimport { XIcon } from \"lucide-react\";\nimport React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\nimport { cn } from \"../lib/cn\";\nimport { Button } from \"./button\";\nimport { ScrollArea } from \"./scroll-area\";\n\nexport const useDialog = useDialogContext;\n\ninterface DialogContextProps {\n /**\n * Used internally to show or hide overlay\n *\n * @default true\n */\n modal?: boolean;\n}\n\nconst DialogContext = React.createContext<DialogContextProps | null>(null);\nDialogContext.displayName = \"DialogContext\";\n\nexport const Dialog = (props: React.ComponentProps<typeof ArkDialog.Root>) => {\n const {\n modal = true,\n lazyMount = true,\n unmountOnExit = true,\n ...rest\n } = props;\n\n return (\n <DialogContext.Provider value={{ modal }}>\n <ArkDialog.Root\n lazyMount={lazyMount}\n modal={modal}\n unmountOnExit={unmountOnExit}\n {...rest}\n />\n </DialogContext.Provider>\n );\n};\n\nexport const DialogTrigger = (\n props: React.ComponentProps<typeof ArkDialog.Trigger>\n) => <ArkDialog.Trigger {...props} />;\n\nexport const dialogOverlayVariants = tv({\n base: [\n \"fixed inset-0 z-50\",\n \"bg-black/32 backdrop-blur-xs\",\n \"duration-200\",\n \"peer peer-data-[slot=dialog-overlay]:hidden\",\n \"data-[state=open]:fade-in-0 data-[state=open]:animate-in\",\n \"data-[state=closed]:fade-out-0 data-[state=closed]:animate-out\",\n \"motion-reduce:animate-none!\",\n ],\n});\n\nexport const DialogOverlay = (\n props: React.ComponentProps<typeof ArkDialog.Backdrop>\n) => {\n const { className, slot, ...rest } = props;\n\n const { modal } = _useDialog();\n\n if (!modal) {\n return null;\n }\n\n return (\n <ArkDialog.Backdrop\n className={cn(dialogOverlayVariants(), className)}\n {...rest}\n data-slot={slot ?? \"dialog-overlay\"}\n />\n );\n};\n\nexport const DialogPositioner = (\n props: React.ComponentProps<typeof ArkDialog.Positioner>\n) => {\n const { className, slot, ...rest } = props;\n\n return (\n <ArkDialog.Positioner\n className={cn(\n \"fixed inset-0 z-50\",\n \"h-svh w-screen\",\n \"grid grid-rows-[1fr_auto_3fr] justify-items-center\",\n \"p-4\",\n className\n )}\n {...rest}\n data-slot={slot ?? \"dialog-positioner\"}\n />\n );\n};\n\nexport const dialogContentVariants = tv({\n base: [\n \"[--space:--spacing(6)]\",\n \"z-[calc(50+var(--layer-index,0))]\",\n \"relative\",\n \"row-start-2\",\n \"max-h-[calc(100svh-2rem)] min-h-0 w-full min-w-0\",\n \"flex flex-col overflow-hidden\",\n \"bg-popover\",\n \"text-popover-foreground\",\n \"rounded-2xl border shadow-lg/5\",\n \"outline-none\",\n \"translate-y-[calc(-1.25rem*var(--nested-layer-count))]\",\n \"transition-[scale,opacity,translate] duration-200 ease-in-out will-change-transform\",\n \"data-[nested=dialog]:data-[state=closed]:slide-in-from-bottom-10 data-[nested=dialog]:data-[state=open]:slide-in-from-bottom-10 data-[has-nested=dialog]:origin-top\",\n \"scale-[calc(1-0.1*var(--nested-layer-count))] opacity-[calc(1-0.1*var(--nested-layer-count))]\",\n \"data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-[98%] data-[state=closed]:animate-out\",\n \"data-[state=open]:fade-in-0 data-[state=open]:zoom-in-[98%] data-[state=open]:animate-in\",\n \"motion-reduce:animate-none! motion-reduce:transition-none!\",\n ],\n variants: {\n size: {\n sm: [\"max-w-md\"],\n md: [\"max-w-lg\"],\n lg: [\"max-w-xl\"],\n xl: [\"max-w-2xl\"],\n \"2xl\": [\"max-w-3xl\"],\n \"3xl\": [\"max-w-4xl\"],\n \"4xl\": [\"max-w-5xl\"],\n \"5xl\": [\"max-w-6xl\"],\n \"6xl\": [\"max-w-7xl\"],\n fullscreen: [\"size-full\"],\n },\n bottomStickOnMobile: {\n true: [\n \"max-sm:max-h-[calc(100svh-3rem)]\",\n \"max-sm:max-w-none\",\n \"max-sm:rounded-none max-sm:rounded-t-2xl max-sm:border-x-0 max-sm:border-t max-sm:border-b-0\",\n \"max-sm:opacity-[calc(1-min(var(--nested-layer-count),1))]\",\n \"max-sm:data-[state=closed]:slide-out-to-bottom-5 max-sm:data-[state=open]:slide-in-from-bottom-5\",\n \"max-sm:data-[state=closed]:zoom-out-100 max-sm:data-[state=open]:zoom-in-100\",\n ],\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n});\n\ninterface DialogContentProps\n extends React.ComponentProps<typeof ArkDialog.Content>,\n VariantProps<typeof dialogContentVariants> {\n /**\n * Stick the dialog to the bottom of the screen on mobile\n *\n * @default true\n */\n bottomStickOnMobile?: boolean;\n /**\n * Show close button at the top right corner\n *\n * @default true\n */\n showCloseButton?: boolean;\n}\n\nexport const DialogContent = (props: DialogContentProps) => {\n const {\n showCloseButton = true,\n bottomStickOnMobile = true,\n size = \"md\",\n className,\n children,\n slot,\n ...rest\n } = props;\n\n return (\n <Portal>\n <DialogOverlay />\n\n <DialogPositioner\n className={cn(\n bottomStickOnMobile &&\n \"max-sm:grid-rows-[1fr_auto] max-sm:p-0 max-sm:pt-12\"\n )}\n >\n <ArkDialog.Content\n className={cn(\n dialogContentVariants({ size, bottomStickOnMobile }),\n className\n )}\n {...rest}\n data-slot={slot ?? \"dialog-content\"}\n >\n {children}\n\n {!!showCloseButton && (\n <DialogClose asChild>\n <Button\n aria-label=\"Close\"\n // Aligned to the content padding: the button sits at 10px so the 16px icon\n // (6px inset inside the 28px icon-sm button) lands its corner on the 16px\n // `--space` grid — mirroring the header title on the opposite side.\n className=\"absolute inset-e-2.5 top-2.5 opacity-64 hover:opacity-100\"\n size=\"icon-sm\"\n variant=\"ghost\"\n >\n <XIcon />\n </Button>\n </DialogClose>\n )}\n </ArkDialog.Content>\n </DialogPositioner>\n </Portal>\n );\n};\n\ninterface DialogBodyProps extends React.ComponentProps<typeof ark.div> {\n /**\n * Add a fade effect to the scroll area\n *\n * @default false\n */\n scrollFade?: boolean;\n}\n\nexport const DialogBody = (props: DialogBodyProps) => {\n const { scrollFade = false, className, slot, ...rest } = props;\n\n return (\n <ScrollArea className=\"min-h-0 flex-1\" scrollFade={scrollFade}>\n <ark.div\n className={cn(\n \"p-(--space)\",\n \"in-[[data-slot=dialog-content]:has([data-slot=dialog-header])]:pt-0\",\n \"in-[[data-slot=dialog-content]:has([data-slot=dialog-footer]:not(.border-t))]:pb-1\",\n className\n )}\n {...rest}\n data-slot={slot ?? \"dialog-body\"}\n />\n </ScrollArea>\n );\n};\n\ninterface DialogHeaderProps extends React.ComponentProps<typeof ark.div> {\n /**\n * The description of the dialog\n */\n description?: string;\n /**\n * The title of the dialog\n */\n title?: string;\n}\n\nexport const DialogHeader = (props: DialogHeaderProps) => {\n const { className, title, description, children, slot, ...rest } = props;\n\n return (\n <ark.div\n className={cn(\n \"shrink-0\",\n \"p-(--space)\",\n \"flex flex-col gap-2\",\n \"in-[[data-slot=dialog-content]:has([data-slot=dialog-body])]:pb-3\",\n className\n )}\n {...rest}\n data-slot={slot ?? \"dialog-header\"}\n >\n {!!title && <DialogTitle>{title}</DialogTitle>}\n\n {!!description && <DialogDescription>{description}</DialogDescription>}\n\n {!title && typeof children === \"string\" ? (\n <DialogTitle>{children}</DialogTitle>\n ) : (\n children\n )}\n </ark.div>\n );\n};\n\nexport const DialogTitle = (\n props: React.ComponentProps<typeof ArkDialog.Title>\n) => {\n const { className, slot, ...rest } = props;\n\n return (\n <ArkDialog.Title\n className={cn(\n \"font-heading font-semibold text-lg leading-none\",\n // Reserve inline-end room so the absolutely-positioned close X never overlaps the title.\n \"in-[[data-slot=dialog-content]:has([data-slot=dialog-close-trigger])]:pe-8\",\n className\n )}\n {...rest}\n data-slot={slot ?? \"dialog-title\"}\n />\n );\n};\n\nexport const DialogDescription = (\n props: React.ComponentProps<typeof ArkDialog.Description>\n) => {\n const { className, slot, ...rest } = props;\n\n return (\n <ArkDialog.Description\n className={cn(\"text-muted-foreground text-sm\", className)}\n {...rest}\n data-slot={slot ?? \"dialog-description\"}\n />\n );\n};\n\nexport const DialogClose = ({\n slot,\n ...rest\n}: React.ComponentProps<typeof ArkDialog.CloseTrigger>) => (\n <ArkDialog.CloseTrigger\n {...rest}\n data-slot={slot ?? \"dialog-close-trigger\"}\n />\n);\n\nexport const DialogFooter = (props: React.ComponentProps<typeof ark.div>) => {\n const { className, slot, ...rest } = props;\n\n return (\n <ark.div\n className={cn(\n \"shrink-0\",\n \"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end\",\n \"sm:rounded-b-[calc(var(--radius-2xl)-1px)]\",\n \"px-(--space) py-4\",\n \"border-t\",\n className\n )}\n {...rest}\n data-slot={slot ?? \"dialog-footer\"}\n />\n );\n};\n\nconst _useDialog = () => {\n const context = React.useContext(DialogContext);\n\n if (!context) {\n throw new Error(\"useDialog must be used within a DialogProvider\");\n }\n\n return context;\n};\n"],"names":["DialogContext"],"mappings":";;;;;;;;;;;AAYO;AAYPA;AAEO;AACL;AAAM;AACI;AACI;AACI;AACb;AAGL;AAEI;AAAW;AAAV;AACC;AACA;AACA;AACI;AAAA;AAIZ;AAMwC;AAChC;AACJ;AACA;AACA;AACA;AACA;AACA;AACA;AAEJ;AAKE;AAIA;AAKE;AAAW;AAAV;AACiD;AAC5C;AACe;AAAA;AAGzB;AAKE;AAEA;AACE;AAAW;AAAV;AACY;AACT;AACA;AACA;AACA;AACA;AAAA;AAEE;AACe;AAAA;AAGzB;AAEwC;AAChC;AACJ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAEQ;AACF;AACW;AACA;AACA;AACC;AACG;AACA;AACA;AACA;AACA;AACK;AAAA;AAEL;AACb;AACJ;AACA;AACA;AACA;AACA;AACA;AAAA;AACF;AACF;AAEe;AACT;AAEV;AAoBE;AAAM;AACc;AACI;AACf;AACP;AACA;AACA;AACG;AAGL;AAEI;AAAe;AAEf;AAAC;AAAA;AACY;AAEP;AAAA;AAGJ;AAAW;AAAV;AACY;AAC0C;AACnD;AAAA;AAEE;AACe;AAElB;AAAA;AAIG;AAAC;AAAA;AACY;AAID;AACL;AACG;AAED;AAAA;AAEX;AAAA;AAAA;AAAA;AAEJ;AAAA;AAIR;AAYE;AAEA;AAEI;AAAK;AAAJ;AACY;AACT;AACA;AACA;AACA;AAAA;AAEE;AACe;AAAA;AAI3B;AAcE;AAEA;AACE;AAAK;AAAJ;AACY;AACT;AACA;AACA;AACA;AACA;AAAA;AAEE;AACe;AAElB;AAA+B;AAEkB;AAKhD;AAAA;AAAA;AAIR;AAKE;AAEA;AACE;AAAW;AAAV;AACY;AACT;AAAA;AAEA;AACA;AAAA;AAEE;AACe;AAAA;AAGzB;AAKE;AAEA;AACE;AAAW;AAAV;AACyD;AACpD;AACe;AAAA;AAGzB;AAE4B;AAC1B;AAEF;AACE;AAAW;AAAV;AACK;AACe;AACrB;AAIA;AAEA;AACE;AAAK;AAAJ;AACY;AACT;AACA;AACA;AACA;AACA;AACA;AAAA;AAEE;AACe;AAAA;AAGzB;AAGE;AAEA;AACE;AAGF;AACF;;;;;;;;;;;;;;;;;"}
|