@voithos-labs/aragonite 0.10.2 → 0.10.3
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/README.md +6 -21
- package/THIRD-PARTY-NOTICES.md +25 -0
- package/dist/a11y-strings.d.ts +18 -0
- package/dist/a11y-strings.js +18 -0
- package/dist/action-contracts.d.ts +7 -1
- package/dist/ambient/ambient-dom.js +5 -1
- package/dist/block-component.d.ts +14 -0
- package/dist/components/BlockDragHandle.svelte +35 -24
- package/dist/components/BlockHost.svelte +16 -9
- package/dist/components/Editor.svelte +381 -198
- package/dist/components/Editor.svelte.d.ts +1 -1
- package/dist/components/SelectionOverlay.svelte +17 -3
- package/dist/components/TailInsert.svelte +107 -0
- package/dist/components/TailInsert.svelte.d.ts +17 -0
- package/dist/components/block-content-selector.d.ts +6 -2
- package/dist/components/block-content-selector.js +6 -2
- package/dist/components/blocks/ThematicBreakBlock.svelte +19 -6
- package/dist/components/blocks/ThematicBreakBlock.svelte.d.ts +1 -0
- package/dist/components/blocks/code/CodeBlock.svelte +211 -30
- package/dist/components/blocks/code/CodeBlockRail.svelte +686 -0
- package/dist/components/blocks/code/CodeBlockRail.svelte.d.ts +26 -0
- package/dist/components/blocks/code/code-bootstrap.js +4 -0
- package/dist/components/blocks/code/code-context-actions.d.ts +1 -0
- package/dist/components/blocks/code/code-context-actions.js +24 -0
- package/dist/components/blocks/code/code-fence-exit.d.ts +15 -0
- package/dist/components/blocks/code/code-fence-exit.js +26 -0
- package/dist/components/blocks/code/code-languages.d.ts +6 -0
- package/dist/components/blocks/code/code-languages.js +11 -0
- package/dist/components/blocks/code/code-renderer.js +11 -0
- package/dist/components/blocks/directive/DirectiveContainerBlock.svelte +1 -1
- package/dist/components/blocks/editable-leaf.d.ts +37 -6
- package/dist/components/blocks/editable-leaf.js +243 -29
- package/dist/components/blocks/editable-surface.d.ts +9 -0
- package/dist/components/blocks/editable-surface.js +22 -3
- package/dist/components/blocks/list/ListItemBlock.svelte +3 -3
- package/dist/components/blocks/list/task-checkbox.d.ts +2 -0
- package/dist/components/blocks/list/task-checkbox.js +11 -2
- package/dist/components/blocks/surface-wiring.svelte.d.ts +4 -0
- package/dist/components/blocks/surface-wiring.svelte.js +8 -1
- package/dist/components/blocks/table/TableActionMenu.svelte +210 -86
- package/dist/components/blocks/table/TableActionMenu.svelte.d.ts +5 -0
- package/dist/components/blocks/table/TableBlock.svelte +205 -186
- package/dist/components/blocks/table/TableBlock.svelte.d.ts +1 -0
- package/dist/components/blocks/table/TableCellBlock.svelte +87 -22
- package/dist/components/blocks/table/TableRowBlock.svelte +4 -18
- package/dist/components/blocks/table/TableRowBlock.svelte.d.ts +0 -5
- package/dist/components/blocks/table/cell-clipboard.d.ts +10 -0
- package/dist/components/blocks/table/cell-clipboard.js +34 -1
- package/dist/components/blocks/table/cell-keydown-plan.d.ts +1 -1
- package/dist/components/blocks/table/cell-keydown-plan.js +3 -1
- package/dist/components/blocks/table/table-cell-paste.js +2 -1
- package/dist/components/blocks/table/table-menu-model.d.ts +35 -8
- package/dist/components/blocks/table/table-menu-model.js +36 -17
- package/dist/components/blocks/text/TextEditableBlock.svelte +58 -11
- package/dist/components/blocks/text/delimiter-autopair.d.ts +71 -0
- package/dist/components/blocks/text/delimiter-autopair.js +216 -0
- package/dist/components/blocks/text/edge-policy-dispatch.d.ts +4 -0
- package/dist/components/blocks/text/edge-policy-dispatch.js +56 -2
- package/dist/components/blocks/text/live-selection-edit.js +27 -0
- package/dist/components/blocks/text/text-keydown.d.ts +7 -1
- package/dist/components/blocks/text/text-keydown.js +11 -1
- package/dist/components/blocks/text/text-render.d.ts +1 -1
- package/dist/components/blocks/text/text-render.js +3 -1
- package/dist/components/blocks/text/widget-interaction.d.ts +3 -0
- package/dist/components/blocks/text/widget-interaction.js +144 -22
- package/dist/components/drag-handle.d.ts +35 -0
- package/dist/components/drag-handle.js +126 -0
- package/dist/components/editor-root-focus.d.ts +19 -0
- package/dist/components/editor-root-focus.js +67 -0
- package/dist/components/editor-root-geometry.d.ts +39 -0
- package/dist/components/editor-root-geometry.js +91 -0
- package/dist/components/editor-root-keydown.d.ts +1 -1
- package/dist/components/editor-root-keydown.js +12 -3
- package/dist/components/editor-root-listeners.d.ts +10 -6
- package/dist/components/editor-root-listeners.js +23 -22
- package/dist/components/editor-root-mode-flip.d.ts +36 -0
- package/dist/components/editor-root-mode-flip.js +92 -0
- package/dist/components/image/ImageOverlayHost.svelte +14 -6
- package/dist/components/image/ImageProperties.svelte +512 -70
- package/dist/components/image/ImageProperties.svelte.d.ts +6 -1
- package/dist/components/image/ImageResizeHandles.svelte +59 -34
- package/dist/components/image/image-crop.d.ts +39 -0
- package/dist/components/image/image-crop.js +74 -0
- package/dist/components/image/image-edit-commit.d.ts +1 -0
- package/dist/components/image/image-edit-commit.js +21 -5
- package/dist/components/image/image-source-bytes.js +10 -3
- package/dist/components/image/image-widget-editing.js +1 -0
- package/dist/components/image/widget-dom.js +5 -1
- package/dist/components/link-card/link-card-commit.js +1 -1
- package/dist/components/lrd-map-gate.js +1 -1
- package/dist/components/menu/BlockMenu.svelte +315 -0
- package/dist/components/menu/BlockMenu.svelte.d.ts +34 -0
- package/dist/components/menu/MenuIcon.svelte +153 -0
- package/dist/components/menu/MenuIcon.svelte.d.ts +51 -0
- package/dist/components/menu/clipboard-actions.d.ts +12 -0
- package/dist/components/menu/clipboard-actions.js +42 -0
- package/dist/components/menu/default-context-actions.d.ts +15 -0
- package/dist/components/menu/default-context-actions.js +76 -0
- package/dist/components/menu/flyout-placement.d.ts +6 -0
- package/dist/components/menu/flyout-placement.js +25 -0
- package/dist/core/inline/format-toggle.d.ts +12 -4
- package/dist/core/inline/format-toggle.js +94 -40
- package/dist/core/inline/image-dimensions.d.ts +3 -0
- package/dist/core/inline/image-dimensions.js +46 -10
- package/dist/core/inline/inline-widgets.d.ts +11 -0
- package/dist/core/inline/scan/brackets.js +1 -0
- package/dist/core/inline/scan/plugin-syntax.d.ts +8 -0
- package/dist/core/inline/scan/plugin-syntax.js +15 -1
- package/dist/core/inline-render.d.ts +6 -0
- package/dist/core/inline-render.js +32 -0
- package/dist/core/nodes.d.ts +14 -0
- package/dist/cursor/edge-affinity.js +2 -1
- package/dist/cursor/overlay-remeasure.js +8 -0
- package/dist/cursor/reveal-source.js +7 -2
- package/dist/cursor/widget-offset.d.ts +6 -0
- package/dist/cursor/widget-offset.js +61 -4
- package/dist/debug/interaction-trace.d.ts +4 -0
- package/dist/debug/interaction-trace.js +15 -0
- package/dist/decorations/decoration-state.svelte.js +1 -1
- package/dist/editor-actions/ancestry-folds.d.ts +2 -2
- package/dist/editor-actions/ancestry-folds.js +1 -1
- package/dist/editor-actions/block-edit-scope.js +1 -1
- package/dist/editor-actions/commit/text-batch.d.ts +3 -2
- package/dist/editor-actions/commit/text-batch.js +1 -1
- package/dist/editor-actions/commit/undo-controller.js +4 -2
- package/dist/editor-actions/container-edit.js +2 -1
- package/dist/editor-actions/enter-completion.d.ts +2 -0
- package/dist/editor-actions/enter-completion.js +23 -2
- package/dist/editor-actions/inline-range-commit.js +1 -1
- package/dist/editor-actions/reorder-action.js +19 -10
- package/dist/editor-actions/reorder-drag.js +29 -1
- package/dist/editor-actions/replacement-focus.d.ts +1 -1
- package/dist/editor-actions/replacement-focus.js +1 -1
- package/dist/editor-actions/search-replace.js +1 -1
- package/dist/editor-actions/table-context.d.ts +4 -1
- package/dist/editor-actions/table-context.js +57 -1
- package/dist/editor-events.d.ts +3 -0
- package/dist/editor-keys.d.ts +33 -0
- package/dist/editor-props.d.ts +21 -12
- package/dist/index.d.ts +1 -1
- package/dist/plugin.d.ts +6 -0
- package/dist/plugin.js +11 -0
- package/dist/plugins/latex/BlockMath.svelte +264 -29
- package/dist/plugins/latex/BlockMath.svelte.d.ts +2 -0
- package/dist/plugins/latex/index.d.ts +2 -1
- package/dist/plugins/latex/latex-kind.js +36 -3
- package/dist/plugins/latex/math-completion.js +4 -1
- package/dist/plugins/latex/math-layout.d.ts +15 -0
- package/dist/plugins/latex/math-layout.js +13 -0
- package/dist/plugins/latex/math-source.d.ts +19 -0
- package/dist/plugins/latex/math-source.js +97 -0
- package/dist/plugins/latex/register.d.ts +11 -2
- package/dist/plugins/latex/register.js +3 -1
- package/dist/plugins/latex/renderer.d.ts +3 -3
- package/dist/plugins/latex/renderer.js +13 -6
- package/dist/reactivity/list-windowing.svelte.d.ts +8 -8
- package/dist/reactivity/list-windowing.svelte.js +49 -24
- package/dist/schema/block-completions.d.ts +8 -0
- package/dist/schema/block-completions.js +11 -0
- package/dist/schema/context-actions.d.ts +31 -0
- package/dist/schema/context-actions.js +23 -0
- package/dist/schema/fenced-code-raw.js +31 -1
- package/dist/schema/operations.d.ts +8 -1
- package/dist/schema/reserved-chords.js +25 -4
- package/dist/schema/table-cell-raw.d.ts +1 -1
- package/dist/schema/table-cell-raw.js +1 -1
- package/dist/selection/block-hit-test.js +3 -2
- package/dist/selection/char-endpoint-snap.js +1 -1
- package/dist/selection/clipboard-text.js +6 -1
- package/dist/selection/covered-block.d.ts +10 -0
- package/dist/selection/covered-block.js +24 -0
- package/dist/selection/cross-block/dispatch.d.ts +3 -0
- package/dist/selection/cross-block/dispatch.js +13 -1
- package/dist/selection/cross-block/format-range.d.ts +1 -1
- package/dist/selection/cross-block/format-range.js +4 -15
- package/dist/selection/cross-block/format-toggle.js +1 -1
- package/dist/selection/cross-block/keydown.js +1 -1
- package/dist/selection/cross-block/ops.js +1 -1
- package/dist/selection/cross-block/paste.js +32 -30
- package/dist/selection/cross-block/type-replace.d.ts +3 -2
- package/dist/selection/cross-block/type-replace.js +62 -13
- package/dist/selection/dead-space-caret.d.ts +10 -0
- package/dist/selection/dead-space-caret.js +47 -1
- package/dist/selection/double-click-trim.d.ts +17 -0
- package/dist/selection/double-click-trim.js +57 -0
- package/dist/selection/drag-pointer.d.ts +7 -2
- package/dist/selection/drag-pointer.js +61 -2
- package/dist/selection/gap-caret.js +1 -1
- package/dist/selection/keyboard-extend.js +1 -1
- package/dist/selection/path-lookup.js +1 -1
- package/dist/selection/range-delete-ceremony.js +4 -2
- package/dist/selection/range-delete-chrome.js +2 -1
- package/dist/selection/range-delete-table-coverage.js +2 -1
- package/dist/selection/range-delete-table.js +3 -2
- package/dist/selection/range-delete.js +30 -2
- package/dist/selection/selection-restore.js +1 -1
- package/dist/selection/selection-state.svelte.d.ts +6 -0
- package/dist/selection/selection-state.svelte.js +36 -1
- package/dist/selection/table-endpoint-snap.js +1 -1
- package/dist/selection/table-rect-extend.js +1 -1
- package/dist/styles/editor-theme.css +57 -28
- package/dist/styles/editor.css +217 -18
- package/dist/testing/container-conformance.js +2 -2
- package/dist/testing/inline-conformance.js +2 -1
- package/dist/tree-operations/blockquote.js +1 -1
- package/dist/tree-operations/chain-rebuild.d.ts +63 -0
- package/dist/tree-operations/chain-rebuild.js +142 -0
- package/dist/tree-operations/children.d.ts +1 -1
- package/dist/tree-operations/children.js +1 -1
- package/dist/tree-operations/cleanup.js +1 -1
- package/dist/tree-operations/content-write.d.ts +50 -0
- package/dist/tree-operations/content-write.js +263 -0
- package/dist/tree-operations/index.d.ts +8 -3
- package/dist/tree-operations/index.js +6 -2
- package/dist/tree-operations/list/exit-replacement.js +1 -1
- package/dist/tree-operations/list/unwrap-merge.js +3 -3
- package/dist/tree-operations/node-ops.d.ts +17 -234
- package/dist/tree-operations/node-ops.js +47 -1113
- package/dist/tree-operations/node-primitives.d.ts +75 -0
- package/dist/tree-operations/node-primitives.js +117 -0
- package/dist/tree-operations/paste/apply.js +1 -1
- package/dist/tree-operations/paste/body-write.d.ts +1 -1
- package/dist/tree-operations/paste/body-write.js +2 -2
- package/dist/tree-operations/paste/container-match.js +4 -2
- package/dist/tree-operations/paste/dispatch.js +2 -1
- package/dist/tree-operations/paste/find-enclosing-list.js +1 -1
- package/dist/tree-operations/paste/focus-target.d.ts +1 -1
- package/dist/tree-operations/paste/list-absorb.js +1 -1
- package/dist/tree-operations/paste/list-break-out.js +1 -1
- package/dist/tree-operations/paste/parent-scope.js +1 -1
- package/dist/tree-operations/paste/paste-replacement.js +1 -1
- package/dist/tree-operations/paste/replace-block-at-parent.js +1 -1
- package/dist/tree-operations/path-mutate.d.ts +1 -1
- package/dist/tree-operations/path-mutate.js +2 -1
- package/dist/tree-operations/reorder-unit.js +1 -1
- package/dist/tree-operations/reorder.d.ts +5 -2
- package/dist/tree-operations/reorder.js +55 -2
- package/dist/tree-operations/settle.d.ts +105 -0
- package/dist/tree-operations/settle.js +660 -0
- package/dist/tree-operations/table-grid-clipboard.d.ts +21 -0
- package/dist/tree-operations/table-grid-clipboard.js +90 -0
- package/dist/tree-operations/unshare.d.ts +15 -77
- package/dist/tree-operations/unshare.js +15 -166
- package/docs/guide/consumer-guide.md +126 -96
- package/docs/guide/plugin-api.md +31 -3
- package/docs/guide/plugin-guide.md +26 -3
- package/package.json +4 -2
- package/dist/components/blocks/code/CodeLanguageChip.svelte +0 -127
- package/dist/components/blocks/code/CodeLanguageChip.svelte.d.ts +0 -14
- package/dist/components/blocks/table/TableGrip.svelte +0 -91
- package/dist/components/blocks/table/TableGrip.svelte.d.ts +0 -8
- package/dist/components/blocks/table/table-drop-target.d.ts +0 -1
- package/dist/components/blocks/table/table-drop-target.js +0 -16
- package/dist/components/blocks/table/table-reorder-drag.d.ts +0 -78
- package/dist/components/blocks/table/table-reorder-drag.js +0 -97
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# <img alt="header" src="docs/assets/header.png" />
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Made for [Limestone Notes](https://limestone.app/) ([source](https://github.com/voithos-labs/limestone)), by [Voithos Labs](https://voithoslabs.com/).
|
|
4
|
+
|
|
5
|
+
This project is an effort (perhaps in vain) to create a markdown editor that is both open source and not crap. In my book, this means that it has to be lossless, extensible, fast, have a graceful ui/ux, and have a hella good plugin interface. So you know, just some simplistic and easy to achieve goals [^1] [^2].
|
|
4
6
|
|
|
5
7
|
Note that aragonite is a work in progress [^3]. It's written in typescript and svelte [^4] [^5] [^6] [^7], and tested on chromium browsers (chrome and edge) [^8]. Yes, there are plans to port to different frontend frameworks and test in different browsers. No, not right now, sometime in the future.
|
|
6
8
|
|
|
@@ -189,27 +191,12 @@ The one deliberate byte write a plugin makes is its own kind's raw rebuild, whic
|
|
|
189
191
|
|
|
190
192
|
A plugin component that throws takes down its own block, which degrades to a readable fallback while its siblings keep working. Uninstall a plugin and every document written with it still round trips byte for byte, because unknown syntax is handled gracefully.
|
|
191
193
|
|
|
192
|
-
Also, shipping a kind forces the boring questions up front: the registration type requires declaring how the kind behaves under all nine cross-cutting subsystems (round-tripping, focus, merge, selection, search, reordering, undo, clipboard, and how the fuzzer drives it), and registering enrolls it in a conformance battery
|
|
194
|
+
Also, shipping a kind forces the boring questions up front: the registration type requires declaring how the kind behaves under all nine cross-cutting subsystems (round-tripping, focus, merge, selection, search, reordering, undo, clipboard, and how the fuzzer drives it), and registering enrolls it in a conformance battery: the headless half runs in your own test suite, the browser half in this repo's e2e sweep.
|
|
193
195
|
|
|
194
196
|
This is the bet. Aragonite cannot top Obsidian in plugin count (in the short term, at least), but what it can try to do is trade plugin count for plugin quality. Score it against my three criterias: reach is the whole own a kind story above, safety is the lossless promise doing double duty, and ergonomics is the part I haven't argued yet, so here it is: svelte and typescript end to end, the entire authoring surface on one import path (`@voithos-labs/aragonite/plugin`), and a public testing seam so your plugin's own test suite isn't an afterthought.
|
|
195
197
|
|
|
196
198
|
Does the design actually work in practice? Well, the nine bundled first party plugins (admonitions, details, footnotes, emoji, math, diagrams, table of contents, occurrence highlighting, and a party parrot) are built on the exact surface third parties get, so I would describe it as "so far, so good".
|
|
197
199
|
|
|
198
|
-
# Lean
|
|
199
|
-
|
|
200
|
-
Let's start by establishing the right context: most editors ship as a toolkit, and you assemble the editor yourself. CodeMirror is seven `@codemirror/*` packages plus a Lezer grammar; ProseMirror is `prosemirror-model` and `-state` and `-view` and `-transform` and however much glue you write to make them a product. Aragonite, on the other hand, is one library you import, with the parser, serializer, block editing, windowing, undo, selection, decorations, presentation modes, and the plugin platform already wired to each other.
|
|
201
|
-
|
|
202
|
-
(Yes, there is a small set of dependencies. Two hard runtime dependencies: highlight.js, for code-block syntax colors, and esm-env, a few bytes of bundler-agnostic dev-flag resolution that svelte itself already depends on. Svelte is a peer you already have, and compiles to far less runtime than a virtual-DOM framework; katex and mermaid are optional peers, pulled in only if you use the math or diagram plugins. That is the whole tree.)
|
|
203
|
-
|
|
204
|
-
Currently, the codebase lands at around 69k lines of typescript and svelte for the shipped library, roughly 6k of which is the nine bundled plugins [^13]. Here is where the lines actually went:
|
|
205
|
-
|
|
206
|
-
<picture>
|
|
207
|
-
<source media="(prefers-color-scheme: dark)" srcset="docs/assets/loc-dark.svg">
|
|
208
|
-
<img alt="Horizontal bar chart of the shipped library's lines of code by area: block UIs and rendering is the largest slice, then editing/commits/undo, the parser and serializer, selection, and the bundled plugins; the schema registry, invariants, public API, decorations, and windowing each take progressively smaller slices." src="docs/assets/loc-light.svg">
|
|
209
|
-
</picture>
|
|
210
|
-
|
|
211
|
-
I guess the number itself is nothing special, but the ratio turns out to be quite dense. A whole block editor (a full markdown parser and serializer, structural editing, windowing, cross-block selection, undo, decorations, five presentation modes, and a plugin platform) fits in a codebase one person can still realistically read end to end. The test suite, meanwhile, is ard 2.4x the size of the library (~168k lines), which says more about my paranoia than the leanness.
|
|
212
|
-
|
|
213
200
|
# Fast
|
|
214
201
|
|
|
215
202
|
Aragonite is fast, and its this way due to one main reason: the editor only mounts what you can see. A 10KB note and a 10MB document have the same number of live components on screen, so typing costs the same in both. This is, as people call it, windowing, and is also one of the reasons why the block editor model earned its keep.
|
|
@@ -310,9 +297,9 @@ Now, the numbers here depend on the machine; however, the scale of the numbers a
|
|
|
310
297
|
|
|
311
298
|
Now, here's a conundrum Finn and I faced early on: we wanted Notion's uiux, but we (by we, I meant Finn) wanted a document, not a pile of blocks. In summary, we wanted the benefits of Notion's uiux in our uiux, not necessarily its look. Which is why even though under the hood aragonite is as much a block editor as Notion is, on the surface it reads like a document you are writing, not a fucking game of tetris.
|
|
312
299
|
|
|
313
|
-
Notion never lets you forget you are in a builder. Hover any block and a drag grip and a plus button fade into the gutter; the surface is a scaffold, and a stray click + drag can rearrange the page. Obsidian sits at the other pole, and reads as a calm plain document, because under the hood it is one (a text buffer, with all of the limitations I mentioned before). Aragonite wants the best of both world: the calm surface and a real structure.
|
|
300
|
+
Notion never lets you forget you are in a builder. Hover any block, a plain paragraph included, and a drag grip and a plus button fade into the gutter; the surface is a scaffold, and a stray click + drag can rearrange the page. Obsidian sits at the other pole, and reads as a calm plain document, because under the hood it is one (a text buffer, with all of the limitations I mentioned before). Aragonite wants the best of both world: the calm surface and a real structure.
|
|
314
301
|
|
|
315
|
-
So, if you open aragonite, the blocks are there, mostly invisible. No card chrome, no per block outline,
|
|
302
|
+
So, if you open aragonite, the blocks are there, mostly invisible. No card chrome, no per block outline, and prose never grows a handle. The blocks you would pick up whole (a table, a picture, a code block, a list item, a divider) get one on hover, and you turn that off easily (`blockDragHandles={false}`); keyboard reorder is always available and shows nothing until you use it.
|
|
316
303
|
|
|
317
304
|
Oh, live preview? You think I forgot about it? nah. By default, markdown syntax stays visible but dimmed. Aragonite provide the `presentationMode` prop, which dials the same document along a spectrum, from the raw side to the rendered side:
|
|
318
305
|
|
|
@@ -361,8 +348,6 @@ Aragonite is free software, released under [AGPL-3.0-or-later](./LICENSE): use i
|
|
|
361
348
|
|
|
362
349
|
[^12]: ProseMirror friends: yes, this means no `StateField`. The forward-mapping problem it solves is downstream of positions being integers into a flat sequence. Ours aren't.
|
|
363
350
|
|
|
364
|
-
[^13]: counted from the tracked `src/lib` source, excluding the tests; `scripts/render-loc-chart.mjs` does the counting and draws the chart above. Give or take a refactor.
|
|
365
|
-
|
|
366
351
|
[^14]: before anyone suggests it: CSS `content-visibility` is not this. It skips paint and layout but leaves the components mounted, and the cost that matters here is script, not layout.
|
|
367
352
|
|
|
368
353
|
[^15]: don't you dare try it lol
|
package/THIRD-PARTY-NOTICES.md
CHANGED
|
@@ -63,6 +63,31 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
63
63
|
SOFTWARE.
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
+
## Lucide (ISC)
|
|
67
|
+
|
|
68
|
+
The editor's own chrome embeds icon path data from Lucide: the code block's rail
|
|
69
|
+
(`src/lib/components/blocks/code/CodeBlockRail.svelte`) and the menus, the image toolbar and the
|
|
70
|
+
demo's selection bar (`src/lib/components/menu/MenuIcon.svelte`). The paths are inlined rather
|
|
71
|
+
than taken as a dependency; no Lucide code ships.
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
ISC License
|
|
75
|
+
|
|
76
|
+
Copyright (c) 2026 Lucide Icons and Contributors
|
|
77
|
+
|
|
78
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
79
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
80
|
+
copyright notice and this permission notice appear in all copies.
|
|
81
|
+
|
|
82
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
83
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
84
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
85
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
86
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
87
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
88
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
89
|
+
```
|
|
90
|
+
|
|
66
91
|
## WHATWG HTML Standard (CC BY 4.0)
|
|
67
92
|
|
|
68
93
|
`src/lib/core/inline/html-entities.ts` vendors the named-character-reference table from <https://html.spec.whatwg.org/entities.json>.
|
package/dist/a11y-strings.d.ts
CHANGED
|
@@ -9,11 +9,23 @@ export declare const FAILED_BLOCK_LABEL = "Block failed to render";
|
|
|
9
9
|
export declare const GAP_CARET_LABEL = "Insertion point between blocks";
|
|
10
10
|
export declare const WHOLE_BLOCK_INPUT_LABEL = "Focused block input";
|
|
11
11
|
export declare const IMAGE_PROPERTIES_LABEL = "Image properties";
|
|
12
|
+
export declare const IMAGE_ALT_FIELD = "Alt text";
|
|
13
|
+
export declare const IMAGE_ALT_PLACEHOLDER = "Describe the image";
|
|
14
|
+
export declare const IMAGE_CROP = "Crop image";
|
|
15
|
+
export declare const IMAGE_CROP_APPLY = "Apply crop";
|
|
16
|
+
export declare const IMAGE_CROP_CANCEL = "Cancel crop";
|
|
17
|
+
export declare const IMAGE_REMOVE = "Remove image";
|
|
12
18
|
export declare const LINK_CARD_LABEL = "Link properties";
|
|
13
19
|
export declare const LINK_CARD_URL = "Link URL";
|
|
14
20
|
export declare const LINK_CARD_OPEN = "Open link";
|
|
15
21
|
export declare const LINK_CARD_REMOVE = "Remove link";
|
|
16
22
|
export declare const CODE_LANGUAGE_FIELD = "Code block language";
|
|
23
|
+
export declare const CODE_RUN_LABEL = "Run code block";
|
|
24
|
+
export declare const CODE_COPY_LABEL = "Copy code";
|
|
25
|
+
export declare const CODE_COPIED_LABEL = "Code copied";
|
|
26
|
+
export declare const CODE_MENU_LABEL = "Code block actions";
|
|
27
|
+
export declare const CODE_RAIL_LABEL = "Code block controls";
|
|
28
|
+
export declare const CODE_LANGUAGE_LIST = "Code block languages";
|
|
17
29
|
/** Chrome, not an announcement: the language chip's accessible name. */
|
|
18
30
|
export declare function codeLanguageLabel(language: string): string;
|
|
19
31
|
export declare const SEARCH_TOGGLE_REPLACE = "Toggle replace";
|
|
@@ -30,6 +42,12 @@ export declare const SEARCH_CLOSE_TITLE = "Close";
|
|
|
30
42
|
export declare const SEARCH_CLOSE_LABEL = "Close search";
|
|
31
43
|
export declare const TABLE_ACTIONS = "Table actions";
|
|
32
44
|
export declare const COLUMN_ALIGNMENT = "Column alignment";
|
|
45
|
+
export declare const ADD_ROW_BELOW = "Add row";
|
|
46
|
+
export declare const ADD_COLUMN_RIGHT = "Add column";
|
|
47
|
+
export declare const TAIL_ADD_ROW = "Add a line below";
|
|
48
|
+
export declare const TAIL_ADD_BLOCK = "Add a block";
|
|
49
|
+
export declare const BLOCK_MENU_LABEL = "Insert a block";
|
|
50
|
+
export declare const BLOCK_ACTIONS_LABEL = "Block actions";
|
|
33
51
|
export declare const ALIGN_LEFT = "Left";
|
|
34
52
|
export declare const ALIGN_CENTER = "Center";
|
|
35
53
|
export declare const ALIGN_RIGHT = "Right";
|
package/dist/a11y-strings.js
CHANGED
|
@@ -9,11 +9,23 @@ export const FAILED_BLOCK_LABEL = 'Block failed to render';
|
|
|
9
9
|
export const GAP_CARET_LABEL = 'Insertion point between blocks';
|
|
10
10
|
export const WHOLE_BLOCK_INPUT_LABEL = 'Focused block input';
|
|
11
11
|
export const IMAGE_PROPERTIES_LABEL = 'Image properties';
|
|
12
|
+
export const IMAGE_ALT_FIELD = 'Alt text';
|
|
13
|
+
export const IMAGE_ALT_PLACEHOLDER = 'Describe the image';
|
|
14
|
+
export const IMAGE_CROP = 'Crop image';
|
|
15
|
+
export const IMAGE_CROP_APPLY = 'Apply crop';
|
|
16
|
+
export const IMAGE_CROP_CANCEL = 'Cancel crop';
|
|
17
|
+
export const IMAGE_REMOVE = 'Remove image';
|
|
12
18
|
export const LINK_CARD_LABEL = 'Link properties';
|
|
13
19
|
export const LINK_CARD_URL = 'Link URL';
|
|
14
20
|
export const LINK_CARD_OPEN = 'Open link';
|
|
15
21
|
export const LINK_CARD_REMOVE = 'Remove link';
|
|
16
22
|
export const CODE_LANGUAGE_FIELD = 'Code block language';
|
|
23
|
+
export const CODE_RUN_LABEL = 'Run code block';
|
|
24
|
+
export const CODE_COPY_LABEL = 'Copy code';
|
|
25
|
+
export const CODE_COPIED_LABEL = 'Code copied';
|
|
26
|
+
export const CODE_MENU_LABEL = 'Code block actions';
|
|
27
|
+
export const CODE_RAIL_LABEL = 'Code block controls';
|
|
28
|
+
export const CODE_LANGUAGE_LIST = 'Code block languages';
|
|
17
29
|
/** Chrome, not an announcement: the language chip's accessible name. */
|
|
18
30
|
export function codeLanguageLabel(language) {
|
|
19
31
|
return `Code language: ${language}`;
|
|
@@ -35,6 +47,12 @@ export const SEARCH_CLOSE_LABEL = 'Close search';
|
|
|
35
47
|
// ── Table action menu ────────────────────────────────────────────────────────
|
|
36
48
|
export const TABLE_ACTIONS = 'Table actions';
|
|
37
49
|
export const COLUMN_ALIGNMENT = 'Column alignment';
|
|
50
|
+
export const ADD_ROW_BELOW = 'Add row';
|
|
51
|
+
export const ADD_COLUMN_RIGHT = 'Add column';
|
|
52
|
+
export const TAIL_ADD_ROW = 'Add a line below';
|
|
53
|
+
export const TAIL_ADD_BLOCK = 'Add a block';
|
|
54
|
+
export const BLOCK_MENU_LABEL = 'Insert a block';
|
|
55
|
+
export const BLOCK_ACTIONS_LABEL = 'Block actions';
|
|
38
56
|
export const ALIGN_LEFT = 'Left';
|
|
39
57
|
export const ALIGN_CENTER = 'Center';
|
|
40
58
|
export const ALIGN_RIGHT = 'Right';
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import type { CstNode, TableAlignment } from './core/nodes';
|
|
6
6
|
import type { NodeView } from './core/node-views';
|
|
7
7
|
import type { StructuralChange } from './tree-operations/structural-change';
|
|
8
|
-
import type { TrackedPosition } from './tree-operations/
|
|
8
|
+
import type { TrackedPosition } from './tree-operations/settle';
|
|
9
9
|
import type { SharingState } from './tree-operations/sharing';
|
|
10
10
|
import type { BlockComponent, FocusPosition } from './block-component';
|
|
11
11
|
import type { ScopedOpDescriptor } from './schema/operations';
|
|
@@ -290,6 +290,12 @@ export interface TableContext {
|
|
|
290
290
|
cycleAlignment(colIdx: number): Promise<void>;
|
|
291
291
|
/** Set a column's alignment directly — distinct from the cycle step. */
|
|
292
292
|
setColumnAlignment(colIdx: number, alignment: TableAlignment): Promise<void>;
|
|
293
|
+
/** Write a grid of cell texts from `origin`, appending the rows and columns it needs, as one
|
|
294
|
+
* commit: a spreadsheet-like paste. Empty grids are a no-op. */
|
|
295
|
+
pasteGrid(origin: {
|
|
296
|
+
rowIdx: number;
|
|
297
|
+
colIdx: number;
|
|
298
|
+
}, grid: string[][]): Promise<void>;
|
|
293
299
|
}
|
|
294
300
|
/**
|
|
295
301
|
* The TableContext mutations addressed by ONE axis index, the vocabulary the affordance
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* DOM construction and lookup for the ambient marker span — the read-only
|
|
3
3
|
* prefix container blocks contribute to their first prose child's textContent.
|
|
4
4
|
*/
|
|
5
|
+
import { DRAG_ANCHOR_ATTR } from '../components/block-content-selector';
|
|
5
6
|
import { domDescendants } from '../cursor/dom-walk';
|
|
6
7
|
import { isAtomicInlineWidget, isHiddenMarkerText } from '../cursor/widget-offset';
|
|
7
8
|
import { devWarn } from '../dev-warn';
|
|
@@ -10,7 +11,8 @@ export function buildAmbientSpan(prefix) {
|
|
|
10
11
|
const outer = document.createElement('span');
|
|
11
12
|
outer.className = 'md-marker';
|
|
12
13
|
outer.setAttribute('contenteditable', 'false');
|
|
13
|
-
|
|
14
|
+
// Document order, whatever order the host listed them in: the walk below is a single pass.
|
|
15
|
+
const ranges = [...(normalized.interactive ?? [])].sort((a, b) => a.start - b.start);
|
|
14
16
|
let cursor = 0;
|
|
15
17
|
for (const range of ranges) {
|
|
16
18
|
if (range.start < 0 || range.end > normalized.text.length || range.start >= range.end) {
|
|
@@ -30,6 +32,8 @@ export function buildAmbientSpan(prefix) {
|
|
|
30
32
|
if (range.ariaChecked !== undefined) {
|
|
31
33
|
inner.setAttribute('aria-checked', String(range.ariaChecked));
|
|
32
34
|
}
|
|
35
|
+
if (range.dragAnchor)
|
|
36
|
+
inner.setAttribute(DRAG_ANCHOR_ATTR, '');
|
|
33
37
|
inner.textContent = normalized.text.slice(range.start, range.end);
|
|
34
38
|
inner.addEventListener('click', range.onClick);
|
|
35
39
|
outer.appendChild(inner);
|
|
@@ -71,6 +71,8 @@ export interface AmbientInteractiveRange {
|
|
|
71
71
|
className: string;
|
|
72
72
|
role?: 'checkbox';
|
|
73
73
|
ariaChecked?: boolean;
|
|
74
|
+
/** The block's drag grip centres on this span's box rather than on its text line. */
|
|
75
|
+
dragAnchor?: boolean;
|
|
74
76
|
/** The click lands on the range's own span, before the leaf's caret handling; a handler
|
|
75
77
|
* reading the chord (`isWidgetActivationClick`) stops propagation to keep the gesture. */
|
|
76
78
|
onClick: (e: MouseEvent) => void;
|
|
@@ -78,6 +80,11 @@ export interface AmbientInteractiveRange {
|
|
|
78
80
|
export type AmbientPrefix = string | {
|
|
79
81
|
text: string;
|
|
80
82
|
interactive?: AmbientInteractiveRange[];
|
|
83
|
+
/**
|
|
84
|
+
* The hanging indent the rendered prefix needs, when its painted width is not its text
|
|
85
|
+
* width (a task item's `- [ ] ` paints as one box). Defaults to one `ch` per character.
|
|
86
|
+
*/
|
|
87
|
+
indent?: string;
|
|
81
88
|
};
|
|
82
89
|
/**
|
|
83
90
|
* The props BlockHost passes every block component; a registry `extraProps` may add
|
|
@@ -145,6 +152,13 @@ export interface BlockComponent {
|
|
|
145
152
|
* surfaces with no such snap.
|
|
146
153
|
*/
|
|
147
154
|
snapCaretToPoint?(clientX: number, clientY: number): void;
|
|
155
|
+
/**
|
|
156
|
+
* A press beside the block (the editor's margin, the host's own padding) that may become a
|
|
157
|
+
* drag: start the block's OWN drag anchored at the leaf nearest the point — a table's cell
|
|
158
|
+
* rectangle — exactly as a press on that leaf would. True when it did; false leaves the press
|
|
159
|
+
* to the editor's generic drag.
|
|
160
|
+
*/
|
|
161
|
+
startDragAtPoint?(clientX: number, clientY: number, event: PointerEvent): boolean;
|
|
148
162
|
/**
|
|
149
163
|
* Descend child indices to the BlockComponent at the leaf, or null if the path
|
|
150
164
|
* doesn't resolve. Empty `path` returns this component. Containers implement it.
|
|
@@ -1,30 +1,35 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { DRAG_HANDLE_TITLE } from '../a11y-strings';
|
|
3
|
+
import MenuIcon from './menu/MenuIcon.svelte';
|
|
4
|
+
import { alignDragHandle } from './drag-handle';
|
|
3
5
|
</script>
|
|
4
6
|
|
|
5
7
|
<!-- aria-hidden + non-focusable: keyboard reorder (Alt+Arrow) is the operable,
|
|
6
8
|
screen-reader-visible path, so this mouse-only grip stays out of the tab/SR flow. -->
|
|
7
|
-
<span
|
|
8
|
-
|
|
9
|
+
<span
|
|
10
|
+
class="block-drag-handle"
|
|
11
|
+
aria-hidden="true"
|
|
12
|
+
title={DRAG_HANDLE_TITLE}
|
|
13
|
+
{@attach alignDragHandle}
|
|
14
|
+
>
|
|
15
|
+
<span class="grip"><MenuIcon name="grip-vertical" size={16} /></span>
|
|
9
16
|
</span>
|
|
10
17
|
|
|
11
18
|
<style>
|
|
12
19
|
.block-drag-handle {
|
|
13
20
|
position: absolute;
|
|
14
|
-
/* Sits inside the editor's own
|
|
21
|
+
/* Sits inside the editor's own left padding, so the grip clears the block's left
|
|
15
22
|
border instead of being clipped behind it (overflow-x:auto). */
|
|
16
|
-
left: -
|
|
23
|
+
left: -1.25rem;
|
|
17
24
|
/* Spans gutter to content-left (width === |left|) so a pointer gliding from the
|
|
18
25
|
block never crosses an un-hovered gap, which would hide the handle and — being
|
|
19
26
|
pointer-events:none once hidden — strand it. Stopping AT content-left keeps
|
|
20
27
|
line-start caret/marker clicks from being hijacked into a drag. */
|
|
21
|
-
width:
|
|
28
|
+
width: 1.25rem;
|
|
22
29
|
/* Full-height hit strip so the handle is reachable at ANY height; the visible
|
|
23
|
-
grip
|
|
30
|
+
grip sits on the block's first line. */
|
|
24
31
|
top: 0;
|
|
25
32
|
bottom: 0;
|
|
26
|
-
display: flex;
|
|
27
|
-
align-items: flex-start;
|
|
28
33
|
opacity: 0;
|
|
29
34
|
pointer-events: none;
|
|
30
35
|
cursor: grab;
|
|
@@ -32,35 +37,41 @@
|
|
|
32
37
|
color: var(--color-ui-muted, #a4a4a4);
|
|
33
38
|
}
|
|
34
39
|
|
|
35
|
-
/*
|
|
36
|
-
|
|
40
|
+
/* Flush left in the strip (the glyph ends a few px clear of the content), centred on the
|
|
41
|
+
measured band that `alignDragHandle` writes as an inline `top`.
|
|
42
|
+
|
|
43
|
+
ALWAYS hittable, unlike the strip around it: a grip reachable only by first hovering its
|
|
44
|
+
block is a flyout you traverse the block to get to. Its own box, not the full-height
|
|
45
|
+
strip, which would swallow every gutter click the block has. */
|
|
37
46
|
.grip {
|
|
47
|
+
position: absolute;
|
|
48
|
+
left: 0;
|
|
49
|
+
width: 100%;
|
|
50
|
+
height: 1.25rem;
|
|
51
|
+
top: 0.5lh;
|
|
52
|
+
transform: translateY(-50%);
|
|
38
53
|
display: flex;
|
|
39
54
|
align-items: center;
|
|
40
|
-
|
|
55
|
+
justify-content: flex-start;
|
|
56
|
+
pointer-events: auto;
|
|
41
57
|
}
|
|
42
58
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
59
|
+
/* The hit target is bigger than the glyph: a 16px box in a gutter is a target the pointer
|
|
60
|
+
misses between two rows, and a miss here reads as the grip belonging to the block above.
|
|
61
|
+
Never past the strip's right edge, which is the content's first character. */
|
|
62
|
+
.grip::before {
|
|
63
|
+
content: '';
|
|
64
|
+
position: absolute;
|
|
65
|
+
inset: -8px 0 -8px -4px;
|
|
49
66
|
}
|
|
50
67
|
|
|
51
|
-
/* Touch never fires the hover reveal, so the handle shows unasked.
|
|
52
|
-
grip rather than the full-height strip, which would make the whole gutter unscrollable. */
|
|
68
|
+
/* Touch never fires the hover reveal, so the handle shows unasked. */
|
|
53
69
|
@media (hover: none) {
|
|
54
70
|
.block-drag-handle {
|
|
55
71
|
opacity: 1;
|
|
56
72
|
}
|
|
57
73
|
.grip {
|
|
58
|
-
pointer-events: auto;
|
|
59
74
|
touch-action: none;
|
|
60
|
-
/* The whole gutter slot, not just the dots: 1rem of editor padding is all the width
|
|
61
|
-
there is, and any more would reach over the line's first character. */
|
|
62
|
-
width: 100%;
|
|
63
|
-
justify-content: center;
|
|
64
75
|
}
|
|
65
76
|
}
|
|
66
77
|
</style>
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import SelectionOverlay from './SelectionOverlay.svelte';
|
|
14
14
|
import DecorationOverlay from './DecorationOverlay.svelte';
|
|
15
15
|
import BlockDragHandle from './BlockDragHandle.svelte';
|
|
16
|
+
import { showsDragHandle } from './drag-handle';
|
|
16
17
|
import TextEditableBlock from './blocks/text/TextEditableBlock.svelte';
|
|
17
18
|
import { defaultRegistryView } from '../schema/registry-view';
|
|
18
19
|
import { FAILED_BLOCK_LABEL } from '../a11y-strings';
|
|
@@ -59,12 +60,16 @@
|
|
|
59
60
|
const getDoc = getContext<EditorDoc | undefined>(EDITOR_DOC_KEY)?.doc;
|
|
60
61
|
// Stable object, so a plain read rather than a getter.
|
|
61
62
|
const rects = services?.rects;
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
)?.blockDragHandles;
|
|
63
|
+
const policies = getContext<EditorPolicies | undefined>(EDITOR_POLICIES_KEY);
|
|
64
|
+
const getDragHandles = policies?.blockDragHandles;
|
|
65
65
|
// $derived, not a mount-time snapshot: a runtime prop toggle must reach blocks
|
|
66
66
|
// that window in after the change.
|
|
67
67
|
const dragHandles = $derived(getDragHandles?.() ?? false);
|
|
68
|
+
// The affordance opt-in already folds reading mode in, but a picture's grip does not wait
|
|
69
|
+
// for that opt-in, so reading mode is asked here too.
|
|
70
|
+
const isReading = $derived(policies?.presentationMode?.() === 'reading');
|
|
71
|
+
// A reorder unit without a grip (a paragraph) is still a drop neighbour and keyboard-movable.
|
|
72
|
+
const showsHandle = $derived(reorderable && !isReading && showsDragHandle(node, dragHandles));
|
|
68
73
|
|
|
69
74
|
let myPath = $derived([...parentPath, index]);
|
|
70
75
|
|
|
@@ -167,7 +172,7 @@
|
|
|
167
172
|
|
|
168
173
|
// A block can grow after mount without its `raw` changing (async content decoding
|
|
169
174
|
// in), which the effect above never sees, and overflow-anchor is off so the growth
|
|
170
|
-
// would slide the viewport. The scope gates on the height it
|
|
175
|
+
// would slide the viewport. The scope gates on the height it last applied.
|
|
171
176
|
$effect(() => {
|
|
172
177
|
if (!hostEl || !measureChannel) return;
|
|
173
178
|
const observer = new ResizeObserver((entries) => {
|
|
@@ -233,7 +238,7 @@
|
|
|
233
238
|
<div
|
|
234
239
|
class={[
|
|
235
240
|
'block-host',
|
|
236
|
-
{ 'reorder-host': reorderable && dragHandles },
|
|
241
|
+
{ 'reorder-host': reorderable && dragHandles, 'handle-host': showsHandle },
|
|
237
242
|
...blockDecs.flatMap((d) => d.class ?? [])
|
|
238
243
|
]}
|
|
239
244
|
data-block-path={JSON.stringify(myPath)}
|
|
@@ -291,7 +296,7 @@
|
|
|
291
296
|
/>
|
|
292
297
|
<!-- Rendered LAST so the block-el lookup still resolves block content as its
|
|
293
298
|
first match. -->
|
|
294
|
-
{#if
|
|
299
|
+
{#if showsHandle}
|
|
295
300
|
<BlockDragHandle />
|
|
296
301
|
{/if}
|
|
297
302
|
</div>
|
|
@@ -302,9 +307,11 @@
|
|
|
302
307
|
}
|
|
303
308
|
|
|
304
309
|
/* Pure-CSS hover reveal: no per-block reactive state on a path whose cost scales
|
|
305
|
-
with mounted-component count. Global because
|
|
306
|
-
...))` reveals the innermost hovered handle, not a staircase of ancestors.
|
|
307
|
-
|
|
310
|
+
with mounted-component count. Global because handle hosts nest; the `:not(:has(
|
|
311
|
+
...))` reveals the innermost hovered handle, not a staircase of ancestors. A gripless
|
|
312
|
+
unit (a paragraph in a quote) is no host, so hovering it reveals its container's. */
|
|
313
|
+
:global(.handle-host:hover:not(:has(.handle-host:hover)) > .block-drag-handle),
|
|
314
|
+
:global(.block-drag-handle:hover) {
|
|
308
315
|
opacity: 1;
|
|
309
316
|
pointer-events: auto;
|
|
310
317
|
}
|