@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
|
@@ -64,10 +64,25 @@
|
|
|
64
64
|
return getWidgetEl()?.querySelector('img') ?? null;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
/**
|
|
68
|
+
* What the drag measures and previews on. A cropped image is a FRAME (the widget) with the
|
|
69
|
+
* picture panned inside it, so the `<img>` is neither the size being changed nor the box the
|
|
70
|
+
* user is dragging: previewing on it bulges the picture out of its frame, and at a zoom above
|
|
71
|
+
* 1 the drag would start from the painted image's width and jump.
|
|
72
|
+
*/
|
|
73
|
+
function isCropped(): boolean {
|
|
74
|
+
return getWidgetEl()?.classList.contains('md-image-cropped') ?? false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function previewEl(): HTMLElement | null {
|
|
78
|
+
return isCropped() ? getWidgetEl() : imgEl();
|
|
79
|
+
}
|
|
80
|
+
|
|
67
81
|
function startDrag(e: PointerEvent) {
|
|
68
82
|
const img = imgEl();
|
|
69
|
-
|
|
70
|
-
|
|
83
|
+
const preview = previewEl();
|
|
84
|
+
if (!img || !preview) return;
|
|
85
|
+
const { width: startWidth, height: startHeight } = preview.getBoundingClientRect();
|
|
71
86
|
// Unmeasurable width makes every snap run against 0 and commit a tiny image
|
|
72
87
|
// whichever way the drag goes; bail before pointer capture.
|
|
73
88
|
if (startWidth < MIN_WIDTH || editorContentWidth < MIN_WIDTH) return;
|
|
@@ -79,7 +94,10 @@
|
|
|
79
94
|
startWidth,
|
|
80
95
|
startHeight,
|
|
81
96
|
naturalWidth: img.naturalWidth,
|
|
82
|
-
|
|
97
|
+
// A crop's frame keeps its shape through a resize (`commitImageResize` derives the
|
|
98
|
+
// height from it), so Shift has nothing to unlock here; the crop session's corner
|
|
99
|
+
// brackets are where a frame's aspect changes.
|
|
100
|
+
aspectLocked: isCropped() || !e.shiftKey,
|
|
83
101
|
currentWidth: startWidth
|
|
84
102
|
};
|
|
85
103
|
}
|
|
@@ -95,12 +113,13 @@
|
|
|
95
113
|
const clamped = clampWidth(proposed, editorContentWidth);
|
|
96
114
|
const snapped = snapWidth(clamped, editorContentWidth, SNAP_THRESHOLD_PX);
|
|
97
115
|
dragState.currentWidth = snapped;
|
|
98
|
-
const
|
|
99
|
-
if (!
|
|
100
|
-
|
|
116
|
+
const preview = previewEl();
|
|
117
|
+
if (!preview) return;
|
|
118
|
+
preview.style.width = `${snapped}px`;
|
|
101
119
|
// Shift unlocks the aspect: the height stays where the user found it and the image
|
|
102
|
-
// distorts. Locked, the
|
|
103
|
-
|
|
120
|
+
// distorts. Locked, the frame's own `aspect-ratio` (cropped) or the stylesheet's
|
|
121
|
+
// `height: auto` (plain) derives it from the new width.
|
|
122
|
+
preview.style.height = dragState.aspectLocked ? '' : `${dragState.startHeight}px`;
|
|
104
123
|
}
|
|
105
124
|
|
|
106
125
|
function endDrag(e: PointerEvent) {
|
|
@@ -126,26 +145,36 @@
|
|
|
126
145
|
dx: e.clientX - startX,
|
|
127
146
|
editorContentWidth,
|
|
128
147
|
naturalWidth,
|
|
129
|
-
imgRectWidth:
|
|
148
|
+
imgRectWidth: previewEl()?.getBoundingClientRect().width,
|
|
130
149
|
imgWidthAttr: imgEl()?.getAttribute('width')
|
|
131
150
|
});
|
|
132
151
|
}
|
|
133
152
|
onCommit(Math.round(finalWidth), resolveDraggedHeight(aspectLocked, startHeight));
|
|
134
153
|
}
|
|
135
154
|
|
|
155
|
+
/** The frame's committed width, from the `|WxH` hint the widget was built with. */
|
|
156
|
+
function restoreFrameWidth(widget: HTMLElement): void {
|
|
157
|
+
const width = imgEl()?.getAttribute('width');
|
|
158
|
+
if (width) widget.style.width = `${width}px`;
|
|
159
|
+
}
|
|
160
|
+
|
|
136
161
|
function cancelDrag(e: PointerEvent) {
|
|
137
162
|
if (!dragState) return;
|
|
138
|
-
// Fall back to the widget's
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
163
|
+
// Fall back to the committed geometry: the widget's width/height attributes, or for a
|
|
164
|
+
// cropped image the frame width `applyCropToWidget` wrote.
|
|
165
|
+
const preview = previewEl();
|
|
166
|
+
if (preview) {
|
|
167
|
+
preview.style.height = '';
|
|
168
|
+
if (preview === imgEl()) preview.style.width = '';
|
|
169
|
+
else restoreFrameWidth(preview);
|
|
143
170
|
}
|
|
144
171
|
dragState = null;
|
|
145
172
|
(e.target as HTMLElement).releasePointerCapture(e.pointerId);
|
|
146
173
|
}
|
|
147
174
|
</script>
|
|
148
175
|
|
|
176
|
+
<!-- One grip, on the right edge: width is the only thing a drag sets (Shift unlocks the
|
|
177
|
+
aspect), so a second corner grip was the same gesture twice. -->
|
|
149
178
|
{#if !isBroken}
|
|
150
179
|
<div
|
|
151
180
|
class="md-resize-handle md-resize-handle-right"
|
|
@@ -155,34 +184,30 @@
|
|
|
155
184
|
onpointerup={endDrag}
|
|
156
185
|
onpointercancel={cancelDrag}
|
|
157
186
|
></div>
|
|
158
|
-
<div
|
|
159
|
-
class="md-resize-handle md-resize-handle-corner"
|
|
160
|
-
role="presentation"
|
|
161
|
-
onpointerdown={startDrag}
|
|
162
|
-
onpointermove={moveDrag}
|
|
163
|
-
onpointerup={endDrag}
|
|
164
|
-
onpointercancel={cancelDrag}
|
|
165
|
-
></div>
|
|
166
187
|
{/if}
|
|
167
188
|
|
|
168
189
|
<style>
|
|
190
|
+
/* An accent pill straddling the edge with a white rim, so it reads on any picture. The hit
|
|
191
|
+
strip is wider than the paint. */
|
|
169
192
|
.md-resize-handle {
|
|
170
193
|
position: absolute;
|
|
171
|
-
|
|
172
|
-
|
|
194
|
+
right: -3px;
|
|
195
|
+
top: 50%;
|
|
196
|
+
width: 6px;
|
|
197
|
+
height: 44px;
|
|
198
|
+
max-height: 60%;
|
|
199
|
+
transform: translateY(-50%);
|
|
200
|
+
border-radius: 3px;
|
|
173
201
|
background: var(--color-accent, #567b67);
|
|
174
|
-
|
|
202
|
+
box-shadow:
|
|
203
|
+
0 0 0 1.5px rgba(255, 255, 255, 0.9),
|
|
204
|
+
0 1px 3px rgba(0, 0, 0, 0.3);
|
|
175
205
|
z-index: 10;
|
|
176
206
|
cursor: ew-resize;
|
|
177
207
|
}
|
|
178
|
-
.md-resize-handle
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
.md-resize-handle-corner {
|
|
184
|
-
right: -4px;
|
|
185
|
-
bottom: -4px;
|
|
186
|
-
cursor: nwse-resize;
|
|
208
|
+
.md-resize-handle::before {
|
|
209
|
+
content: '';
|
|
210
|
+
position: absolute;
|
|
211
|
+
inset: -6px -8px;
|
|
187
212
|
}
|
|
188
213
|
</style>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure math for the image crop: how a `{x, y, z}` window maps to the styles that paint it, and
|
|
3
|
+
* how a pan or a wheel turn moves it. No DOM here beyond a style bag; the properties surface
|
|
4
|
+
* owns the pointer events and `widget-dom.ts` paints a committed crop from the same styles.
|
|
5
|
+
*
|
|
6
|
+
* The model is CSS `background-size: cover` at `z` times the fill scale, with the image point
|
|
7
|
+
* (`x`%, `y`%) pinned to that point of the frame, so `{50, 50, 1}` is a centred cover fit.
|
|
8
|
+
*/
|
|
9
|
+
import type { ImageCrop } from '../../core/nodes';
|
|
10
|
+
export declare const DEFAULT_CROP: ImageCrop;
|
|
11
|
+
export interface Size {
|
|
12
|
+
width: number;
|
|
13
|
+
height: number;
|
|
14
|
+
}
|
|
15
|
+
export declare function clampCrop(crop: ImageCrop): ImageCrop;
|
|
16
|
+
/**
|
|
17
|
+
* The styles an `<img>` inside an `overflow: hidden` frame takes. The image box is `z` times
|
|
18
|
+
* the frame with `object-fit: cover`, offset so the pinned point stays put: at `z` 1 the offset
|
|
19
|
+
* is zero and `object-position` alone pans the cover overflow.
|
|
20
|
+
*/
|
|
21
|
+
export declare function cropImageStyle(crop: ImageCrop): {
|
|
22
|
+
width: string;
|
|
23
|
+
height: string;
|
|
24
|
+
left: string;
|
|
25
|
+
top: string;
|
|
26
|
+
objectPosition: string;
|
|
27
|
+
};
|
|
28
|
+
/** The painted image size at this crop: cover-scaled to the frame, then times `z`. */
|
|
29
|
+
export declare function paintedSize(frame: Size, natural: Size, z: number): Size;
|
|
30
|
+
/**
|
|
31
|
+
* A drag of (`dx`, `dy`) px moves the window the other way, by the share of the overflow the
|
|
32
|
+
* pointer covered; an axis with nothing to pan on ignores its component.
|
|
33
|
+
*/
|
|
34
|
+
export declare function panCrop(crop: ImageCrop, dx: number, dy: number, frame: Size, natural: Size): ImageCrop;
|
|
35
|
+
/** A wheel turn zooms about the pinned point; positive `deltaY` (scrolling down) zooms out. */
|
|
36
|
+
export declare function zoomCrop(crop: ImageCrop, deltaY: number): ImageCrop;
|
|
37
|
+
export declare function isDefaultCrop(crop: ImageCrop): boolean;
|
|
38
|
+
/** Paint a crop onto a widget and its image, the way a committed one renders. */
|
|
39
|
+
export declare function applyCropToWidget(widget: HTMLElement, img: HTMLImageElement, frame: Size, crop: ImageCrop): void;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure math for the image crop: how a `{x, y, z}` window maps to the styles that paint it, and
|
|
3
|
+
* how a pan or a wheel turn moves it. No DOM here beyond a style bag; the properties surface
|
|
4
|
+
* owns the pointer events and `widget-dom.ts` paints a committed crop from the same styles.
|
|
5
|
+
*
|
|
6
|
+
* The model is CSS `background-size: cover` at `z` times the fill scale, with the image point
|
|
7
|
+
* (`x`%, `y`%) pinned to that point of the frame, so `{50, 50, 1}` is a centred cover fit.
|
|
8
|
+
*/
|
|
9
|
+
import { MAX_CROP_ZOOM } from '../../core/inline/image-dimensions';
|
|
10
|
+
export const DEFAULT_CROP = { x: 50, y: 50, z: 1 };
|
|
11
|
+
export function clampCrop(crop) {
|
|
12
|
+
return {
|
|
13
|
+
x: Math.min(100, Math.max(0, crop.x)),
|
|
14
|
+
y: Math.min(100, Math.max(0, crop.y)),
|
|
15
|
+
z: Math.min(MAX_CROP_ZOOM, Math.max(1, crop.z))
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The styles an `<img>` inside an `overflow: hidden` frame takes. The image box is `z` times
|
|
20
|
+
* the frame with `object-fit: cover`, offset so the pinned point stays put: at `z` 1 the offset
|
|
21
|
+
* is zero and `object-position` alone pans the cover overflow.
|
|
22
|
+
*/
|
|
23
|
+
export function cropImageStyle(crop) {
|
|
24
|
+
const c = clampCrop(crop);
|
|
25
|
+
const pct = (n) => `${Math.round(n * 1000) / 1000}%`;
|
|
26
|
+
return {
|
|
27
|
+
width: pct(c.z * 100),
|
|
28
|
+
height: pct(c.z * 100),
|
|
29
|
+
left: pct(-(c.z - 1) * c.x),
|
|
30
|
+
top: pct(-(c.z - 1) * c.y),
|
|
31
|
+
objectPosition: `${pct(c.x)} ${pct(c.y)}`
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** The painted image size at this crop: cover-scaled to the frame, then times `z`. */
|
|
35
|
+
export function paintedSize(frame, natural, z) {
|
|
36
|
+
if (natural.width <= 0 || natural.height <= 0)
|
|
37
|
+
return { width: frame.width * z, height: frame.height * z };
|
|
38
|
+
const scale = Math.max(frame.width / natural.width, frame.height / natural.height) * z;
|
|
39
|
+
return { width: natural.width * scale, height: natural.height * scale };
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* A drag of (`dx`, `dy`) px moves the window the other way, by the share of the overflow the
|
|
43
|
+
* pointer covered; an axis with nothing to pan on ignores its component.
|
|
44
|
+
*/
|
|
45
|
+
export function panCrop(crop, dx, dy, frame, natural) {
|
|
46
|
+
const painted = paintedSize(frame, natural, crop.z);
|
|
47
|
+
const overX = painted.width - frame.width;
|
|
48
|
+
const overY = painted.height - frame.height;
|
|
49
|
+
return clampCrop({
|
|
50
|
+
x: overX > 0.5 ? crop.x - (dx / overX) * 100 : crop.x,
|
|
51
|
+
y: overY > 0.5 ? crop.y - (dy / overY) * 100 : crop.y,
|
|
52
|
+
z: crop.z
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
/** A wheel turn zooms about the pinned point; positive `deltaY` (scrolling down) zooms out. */
|
|
56
|
+
export function zoomCrop(crop, deltaY) {
|
|
57
|
+
return clampCrop({ ...crop, z: crop.z - deltaY * 0.002 });
|
|
58
|
+
}
|
|
59
|
+
export function isDefaultCrop(crop) {
|
|
60
|
+
return crop.x === 50 && crop.y === 50 && crop.z === 1;
|
|
61
|
+
}
|
|
62
|
+
/** Paint a crop onto a widget and its image, the way a committed one renders. */
|
|
63
|
+
export function applyCropToWidget(widget, img, frame, crop) {
|
|
64
|
+
widget.classList.add('md-image-cropped');
|
|
65
|
+
widget.style.width = `${Math.round(frame.width)}px`;
|
|
66
|
+
widget.style.aspectRatio = `${Math.round(frame.width)} / ${Math.round(frame.height)}`;
|
|
67
|
+
const style = cropImageStyle(crop);
|
|
68
|
+
img.style.width = style.width;
|
|
69
|
+
img.style.height = style.height;
|
|
70
|
+
img.style.left = style.left;
|
|
71
|
+
img.style.top = style.top;
|
|
72
|
+
img.style.objectPosition = style.objectPosition;
|
|
73
|
+
img.style.aspectRatio = '';
|
|
74
|
+
}
|
|
@@ -30,6 +30,7 @@ export interface ImageEditCommitter {
|
|
|
30
30
|
* popover's dirty check compares against these. */
|
|
31
31
|
buildEditBytes(target: WidgetTarget, newFields: ImageFields): string | null;
|
|
32
32
|
commitImageResize(newWidth: number, newHeight: number | undefined): void;
|
|
33
|
+
removeImage(target: WidgetTarget): void;
|
|
33
34
|
dismissImagePopover(): void;
|
|
34
35
|
getEditorContentWidth(): number;
|
|
35
36
|
attachWidgetSelectListener(): () => void;
|
|
@@ -2,7 +2,7 @@ import { resolvedInlineContent } from '../../core/inline/inline-cache';
|
|
|
2
2
|
import { flattenInlineWidgets } from '../../core/inline/inline-widgets';
|
|
3
3
|
import { createInlineRangeCommit } from '../../editor-actions/inline-range-commit';
|
|
4
4
|
import { FALLBACK_CONTENT_WIDTH } from '../../cursor/typography-estimates';
|
|
5
|
-
import { blockNodeAt } from '../../tree-operations/node-
|
|
5
|
+
import { blockNodeAt } from '../../tree-operations/node-primitives';
|
|
6
6
|
import { buildImageEditBytes } from './image-source-bytes';
|
|
7
7
|
export function createImageEditCommitter(deps) {
|
|
8
8
|
const { getDoc, getEditorEl, widgetSelection, controller, events } = deps;
|
|
@@ -71,15 +71,30 @@ export function createImageEditCommitter(deps) {
|
|
|
71
71
|
const ctx = getSelectedImageFields();
|
|
72
72
|
if (!ctx)
|
|
73
73
|
return;
|
|
74
|
+
const { image } = ctx;
|
|
75
|
+
// A cropped frame keeps its shape through a resize: the height follows the width.
|
|
76
|
+
const framedHeight = image.crop && image.width !== undefined && image.height !== undefined
|
|
77
|
+
? Math.round((image.height * newWidth) / image.width)
|
|
78
|
+
: newHeight;
|
|
74
79
|
const newFields = {
|
|
75
|
-
alt:
|
|
76
|
-
url:
|
|
77
|
-
...(
|
|
80
|
+
alt: image.alt ?? '',
|
|
81
|
+
url: image.url ?? '',
|
|
82
|
+
...(image.title !== undefined ? { title: image.title } : {}),
|
|
78
83
|
width: newWidth,
|
|
79
|
-
...(
|
|
84
|
+
...(framedHeight !== undefined ? { height: framedHeight } : {}),
|
|
85
|
+
...(image.crop !== undefined && framedHeight !== undefined ? { crop: image.crop } : {})
|
|
80
86
|
};
|
|
81
87
|
commitImageEdit(sel, newFields);
|
|
82
88
|
}
|
|
89
|
+
/** The whole `` span goes; the caret lands where the image began. */
|
|
90
|
+
function removeImage(target) {
|
|
91
|
+
const paragraph = blockNodeAt(getDoc(), target.paragraphPath);
|
|
92
|
+
const image = paragraph && findImageInParagraph(paragraph, target.sourceStart);
|
|
93
|
+
if (!image)
|
|
94
|
+
return;
|
|
95
|
+
widgetSelection.clear();
|
|
96
|
+
void inlineRange.commitInlineRange(target.paragraphPath, image.start, image.end, '', 0);
|
|
97
|
+
}
|
|
83
98
|
function dismissImagePopover() {
|
|
84
99
|
widgetSelection.clear();
|
|
85
100
|
}
|
|
@@ -151,6 +166,7 @@ export function createImageEditCommitter(deps) {
|
|
|
151
166
|
commitImageEdit,
|
|
152
167
|
buildEditBytes,
|
|
153
168
|
commitImageResize,
|
|
169
|
+
removeImage,
|
|
154
170
|
dismissImagePopover,
|
|
155
171
|
getEditorContentWidth,
|
|
156
172
|
attachWidgetSelectListener,
|
|
@@ -31,6 +31,7 @@ export function imageFieldsFromInline(image) {
|
|
|
31
31
|
...(image.title !== undefined ? { title: image.title } : {}),
|
|
32
32
|
...(image.width !== undefined ? { width: image.width } : {}),
|
|
33
33
|
...(image.height !== undefined ? { height: image.height } : {}),
|
|
34
|
+
...(image.crop !== undefined ? { crop: image.crop } : {}),
|
|
34
35
|
...(image.label !== undefined ? { label: image.label } : {})
|
|
35
36
|
};
|
|
36
37
|
}
|
|
@@ -38,7 +39,7 @@ export function imageFieldsFromInline(image) {
|
|
|
38
39
|
/** The built-in grammar's inverse. Reach it through `buildImageEditBytes`, the only
|
|
39
40
|
* caller entitled to decide these bytes are GFM's to write. */
|
|
40
41
|
export function buildImageSourceBytes(fields) {
|
|
41
|
-
const dimSuffix = buildDimSuffix(fields.width, fields.height);
|
|
42
|
+
const dimSuffix = buildDimSuffix(fields.width, fields.height, fields.crop);
|
|
42
43
|
const altSegment = escapeAlt(fields.alt) + dimSuffix;
|
|
43
44
|
// Reference form: the dimension hint rides in the alt, and url/title are not
|
|
44
45
|
// written — they belong to the LRD.
|
|
@@ -48,12 +49,18 @@ export function buildImageSourceBytes(fields) {
|
|
|
48
49
|
const titleSegment = fields.title !== undefined ? ` "${escapeTitle(fields.title)}"` : '';
|
|
49
50
|
return `}${titleSegment})`;
|
|
50
51
|
}
|
|
51
|
-
function buildDimSuffix(width, height) {
|
|
52
|
+
function buildDimSuffix(width, height, crop) {
|
|
52
53
|
if (width === undefined)
|
|
53
54
|
return '';
|
|
54
55
|
if (height === undefined)
|
|
55
56
|
return `|${width}`;
|
|
56
|
-
return `|${width}x${height}`;
|
|
57
|
+
return `|${width}x${height}${crop ? buildCropTail(crop) : ''}`;
|
|
58
|
+
}
|
|
59
|
+
/** Whole percents; the zoom only when it is one worth writing, trimmed to what it needs. */
|
|
60
|
+
function buildCropTail(crop) {
|
|
61
|
+
const z = Math.round(crop.z * 100) / 100;
|
|
62
|
+
const zoom = z === 1 ? '' : `,${String(z)}`;
|
|
63
|
+
return `@${Math.round(crop.x)},${Math.round(crop.y)}${zoom}`;
|
|
57
64
|
}
|
|
58
65
|
// Alt sits inside `[...]`, where an unescaped bracket closes the scan early. Unlike
|
|
59
66
|
// `title` and `url` it carries RAW label bytes, so a blanket escape doubles every
|
|
@@ -28,6 +28,7 @@ export function imageWidgetOnSelectedKey(e, ctx) {
|
|
|
28
28
|
...(inline.height !== undefined
|
|
29
29
|
? { height: Math.round((newWidth / currentWidth) * inline.height) }
|
|
30
30
|
: {}),
|
|
31
|
+
...(inline.crop !== undefined ? { crop: inline.crop } : {}),
|
|
31
32
|
...(inline.label !== undefined ? { label: inline.label } : {})
|
|
32
33
|
};
|
|
33
34
|
const newBytes = buildImageEditBytes(inline, node.raw, newFields);
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { isAllowedImageSrcScheme } from '../../core/url-policy';
|
|
5
5
|
import { findSurfacePathForElement } from '../../selection/path-lookup';
|
|
6
6
|
import { devWarn } from '../../dev-warn';
|
|
7
|
+
import { applyCropToWidget } from './image-crop';
|
|
7
8
|
export function buildImageWidget(node, _raw, opts) {
|
|
8
9
|
const widget = document.createElement('span');
|
|
9
10
|
widget.className = 'md-image-widget';
|
|
@@ -63,9 +64,12 @@ export function buildImageWidget(node, _raw, opts) {
|
|
|
63
64
|
img.setAttribute('height', String(node.height));
|
|
64
65
|
// A declared `|WxH` box belongs to the author, so it both reserves space before the bytes
|
|
65
66
|
// arrive and survives the decode: the attribute pair alone loses to the natural ratio the
|
|
66
|
-
// moment `height: auto` has one to read.
|
|
67
|
+
// moment `height: auto` has one to read. With a crop the box is a frame the image pans in.
|
|
67
68
|
if (node.width !== undefined && node.height !== undefined) {
|
|
68
69
|
img.style.aspectRatio = `${node.width} / ${node.height}`;
|
|
70
|
+
if (node.crop) {
|
|
71
|
+
applyCropToWidget(widget, img, { width: node.width, height: node.height }, node.crop);
|
|
72
|
+
}
|
|
69
73
|
}
|
|
70
74
|
const markBroken = () => {
|
|
71
75
|
opts.brokenUrlCache.add(resolvedUrl);
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { wireOverlayRemeasure } from '../../cursor/overlay-remeasure';
|
|
7
7
|
import { createInlineRangeCommit } from '../../editor-actions/inline-range-commit';
|
|
8
|
-
import { isBlockNode, nodeAt } from '../../tree-operations/node-
|
|
8
|
+
import { isBlockNode, nodeAt } from '../../tree-operations/node-primitives';
|
|
9
9
|
import { linkConstructAt } from '../blocks/text/link-at-point';
|
|
10
10
|
import { buildLinkEditBytes, buildLinkUnwrapBytes, buildLinkWrapBytes, linkFieldsFromInline } from '../blocks/text/link-source-bytes';
|
|
11
11
|
export function createLinkCardCommitter(deps) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { nodeAt } from '../tree-operations/node-
|
|
1
|
+
import { nodeAt } from '../tree-operations/node-primitives';
|
|
2
2
|
/**
|
|
3
3
|
* Whether a commit could change the LRD set, keeping the O(nodes) map rebuild off the keystroke
|
|
4
4
|
* hot path. Any op that is not a kind-stable `input`/`metadataUpdate` could add or remove a
|