@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
|
@@ -1,52 +1,66 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { untrack } from 'svelte';
|
|
3
|
-
import type { ImageFields } from '../../core/nodes';
|
|
3
|
+
import type { ImageCrop, ImageFields } from '../../core/nodes';
|
|
4
4
|
import { IMAGE_CHROME_SELECTOR, type WidgetTarget } from './widget-selection-state.svelte';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
IMAGE_ALT_FIELD,
|
|
7
|
+
IMAGE_ALT_PLACEHOLDER,
|
|
8
|
+
IMAGE_CROP,
|
|
9
|
+
IMAGE_CROP_APPLY,
|
|
10
|
+
IMAGE_CROP_CANCEL,
|
|
11
|
+
IMAGE_PROPERTIES_LABEL,
|
|
12
|
+
IMAGE_REMOVE
|
|
13
|
+
} from '../../a11y-strings';
|
|
14
|
+
import MenuIcon from '../menu/MenuIcon.svelte';
|
|
15
|
+
import { DEFAULT_CROP, applyCropToWidget, panCrop, zoomCrop, type Size } from './image-crop';
|
|
6
16
|
|
|
7
17
|
let {
|
|
8
18
|
target,
|
|
9
19
|
fields,
|
|
20
|
+
getWidgetEl,
|
|
10
21
|
buildBytes,
|
|
11
22
|
onCommit,
|
|
12
|
-
|
|
23
|
+
onRemove,
|
|
24
|
+
onDismiss,
|
|
25
|
+
maxFrameWidth,
|
|
26
|
+
cropping = $bindable(false)
|
|
13
27
|
}: {
|
|
14
28
|
target: WidgetTarget;
|
|
15
29
|
fields: ImageFields;
|
|
30
|
+
getWidgetEl: () => HTMLElement | null;
|
|
16
31
|
/** The popover's dirty check is a byte comparison, and the bytes are the write
|
|
17
32
|
* seam's to decide. */
|
|
18
33
|
buildBytes: (target: WidgetTarget, fields: ImageFields) => string | null;
|
|
19
34
|
onCommit: (target: WidgetTarget, newFields: ImageFields) => void;
|
|
35
|
+
onRemove: (target: WidgetTarget) => void;
|
|
20
36
|
onDismiss: () => void;
|
|
37
|
+
/** The column width: the widest frame a corner drag can open. */
|
|
38
|
+
maxFrameWidth: () => number;
|
|
39
|
+
cropping?: boolean;
|
|
21
40
|
} = $props();
|
|
22
41
|
|
|
23
|
-
|
|
42
|
+
// The URL has no field: a user who wants to retarget an image does it in source mode.
|
|
24
43
|
let alt = $state(untrack(() => fields.alt));
|
|
25
|
-
let
|
|
26
|
-
let
|
|
27
|
-
let
|
|
44
|
+
let fieldOpen = $state(false);
|
|
45
|
+
let rootEl: HTMLDivElement | undefined = $state();
|
|
46
|
+
let fieldInput: HTMLInputElement | undefined = $state();
|
|
28
47
|
|
|
29
48
|
// The seed is the BYTES, not the fields object: a rebuild mints a fresh one per render, so
|
|
30
49
|
// identity says nothing about whether the image moved.
|
|
31
50
|
let seedBytes = $state(untrack(() => buildBytes(target, fields)));
|
|
32
51
|
|
|
33
|
-
// The
|
|
52
|
+
// The surface follows the document while it is open: an undo — or any write landing from
|
|
34
53
|
// outside this gesture — moves the image past the draft, and the dismiss commit would put the
|
|
35
54
|
// old bytes back. The in-flight draft is discarded rather than a committed change reverted.
|
|
36
55
|
$effect(() => {
|
|
37
56
|
const live = buildBytes(target, fields);
|
|
38
57
|
if (live === seedBytes) return;
|
|
39
58
|
seedBytes = live;
|
|
40
|
-
url = fields.url;
|
|
41
59
|
alt = fields.alt;
|
|
42
|
-
titleInput = fields.title ?? '';
|
|
43
|
-
titleTouched = false;
|
|
44
60
|
});
|
|
45
61
|
|
|
46
|
-
// The commit runs in $effect cleanup so dismiss, image-switch (key change) and
|
|
47
|
-
// programmatic clear all commit through one seam.
|
|
48
62
|
$effect(() => {
|
|
49
|
-
if (!
|
|
63
|
+
if (!rootEl) return;
|
|
50
64
|
const handler = (e: PointerEvent) => {
|
|
51
65
|
const target = e.target as Element | null;
|
|
52
66
|
if (target?.closest(IMAGE_CHROME_SELECTOR)) return;
|
|
@@ -56,98 +70,526 @@
|
|
|
56
70
|
return () => document.removeEventListener('pointerdown', handler, true);
|
|
57
71
|
});
|
|
58
72
|
|
|
73
|
+
// The commit runs in $effect cleanup so dismiss, image-switch (key change) and
|
|
74
|
+
// programmatic clear all commit through one seam. A crop still open is abandoned, not
|
|
75
|
+
// committed: only the tick writes one.
|
|
59
76
|
$effect(() => {
|
|
60
77
|
return () => {
|
|
78
|
+
if (cropping) cancelCrop();
|
|
61
79
|
commitIfChanged();
|
|
62
80
|
};
|
|
63
81
|
});
|
|
64
82
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
83
|
+
$effect(() => {
|
|
84
|
+
if (fieldOpen && fieldInput) fieldInput.focus();
|
|
85
|
+
});
|
|
68
86
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const next: ImageFields = {
|
|
87
|
+
// ── Alt ────────────────────────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
// The title (``) has no field: it is a tooltip nobody reads in an editor,
|
|
90
|
+
// and an existing one rides through every commit untouched, as do the url, frame and crop.
|
|
91
|
+
function draftFields(): ImageFields {
|
|
92
|
+
return {
|
|
76
93
|
alt,
|
|
77
|
-
url,
|
|
78
|
-
...(
|
|
94
|
+
url: fields.url,
|
|
95
|
+
...(fields.title !== undefined ? { title: fields.title } : {}),
|
|
79
96
|
...(fields.width !== undefined ? { width: fields.width } : {}),
|
|
80
|
-
...(fields.height !== undefined ? { height: fields.height } : {})
|
|
97
|
+
...(fields.height !== undefined ? { height: fields.height } : {}),
|
|
98
|
+
...(fields.crop !== undefined ? { crop: fields.crop } : {})
|
|
81
99
|
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function commitIfChanged() {
|
|
103
|
+
const next = draftFields();
|
|
82
104
|
const newBytes = buildBytes(target, next);
|
|
83
105
|
if (newBytes === seedBytes) return;
|
|
106
|
+
// Seeded now rather than when the write lands, so a blur and the unmount that follows
|
|
107
|
+
// it cannot commit the same draft twice.
|
|
108
|
+
seedBytes = newBytes;
|
|
84
109
|
onCommit(target, next);
|
|
85
110
|
}
|
|
86
111
|
|
|
87
|
-
function
|
|
88
|
-
if (
|
|
112
|
+
function toggleField() {
|
|
113
|
+
if (fieldOpen) closeField(true);
|
|
114
|
+
else fieldOpen = true;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function closeField(commit: boolean) {
|
|
118
|
+
if (commit) commitIfChanged();
|
|
119
|
+
else alt = fields.alt;
|
|
120
|
+
fieldOpen = false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function onFieldKeydown(e: KeyboardEvent) {
|
|
124
|
+
if (e.key === 'Enter') {
|
|
125
|
+
e.preventDefault();
|
|
126
|
+
closeField(true);
|
|
127
|
+
} else if (e.key === 'Escape') {
|
|
128
|
+
e.preventDefault();
|
|
129
|
+
e.stopPropagation();
|
|
130
|
+
closeField(false);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ── Crop ───────────────────────────────────────────────────────────────────
|
|
135
|
+
// limestone's cover crop: drag pans, the wheel zooms, corner brackets mark the frame, and
|
|
136
|
+
// the tick writes it. The frame is the box the image has right now, so an unframed image
|
|
137
|
+
// gains `|WxH` on its first crop.
|
|
138
|
+
|
|
139
|
+
let draft = $state<ImageCrop>(DEFAULT_CROP);
|
|
140
|
+
let frame: Size = { width: 0, height: 0 };
|
|
141
|
+
let natural: Size = { width: 0, height: 0 };
|
|
142
|
+
let snapshot: {
|
|
143
|
+
widget: HTMLElement;
|
|
144
|
+
img: HTMLImageElement;
|
|
145
|
+
widgetStyle: string | null;
|
|
146
|
+
imgStyle: string | null;
|
|
147
|
+
cropped: boolean;
|
|
148
|
+
} | null = null;
|
|
149
|
+
let lastPointer: { x: number; y: number } | null = null;
|
|
150
|
+
|
|
151
|
+
function startCrop() {
|
|
152
|
+
const widget = getWidgetEl();
|
|
153
|
+
const img = widget?.querySelector('img') ?? null;
|
|
154
|
+
if (!widget || !img || img.naturalWidth === 0) return;
|
|
155
|
+
if (fieldOpen) closeField(true);
|
|
156
|
+
const box = widget.getBoundingClientRect();
|
|
157
|
+
frame = { width: box.width, height: box.height };
|
|
158
|
+
natural = { width: img.naturalWidth, height: img.naturalHeight };
|
|
159
|
+
snapshot = {
|
|
160
|
+
widget,
|
|
161
|
+
img,
|
|
162
|
+
widgetStyle: widget.getAttribute('style'),
|
|
163
|
+
imgStyle: img.getAttribute('style'),
|
|
164
|
+
cropped: widget.classList.contains('md-image-cropped')
|
|
165
|
+
};
|
|
166
|
+
draft = fields.crop ? { ...fields.crop } : { ...DEFAULT_CROP };
|
|
167
|
+
applyCropToWidget(widget, img, frame, draft);
|
|
168
|
+
cropping = true;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function paintDraft() {
|
|
172
|
+
if (snapshot) applyCropToWidget(snapshot.widget, snapshot.img, frame, draft);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function restoreSnapshot() {
|
|
176
|
+
if (!snapshot) return;
|
|
177
|
+
const { widget, img, widgetStyle, imgStyle, cropped } = snapshot;
|
|
178
|
+
if (widget.isConnected) {
|
|
179
|
+
setStyle(widget, widgetStyle);
|
|
180
|
+
setStyle(img, imgStyle);
|
|
181
|
+
widget.classList.toggle('md-image-cropped', cropped);
|
|
182
|
+
}
|
|
183
|
+
snapshot = null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function setStyle(el: HTMLElement, style: string | null) {
|
|
187
|
+
if (style === null) el.removeAttribute('style');
|
|
188
|
+
else el.setAttribute('style', style);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function cancelCrop() {
|
|
192
|
+
restoreSnapshot();
|
|
193
|
+
cropping = false;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function applyCrop() {
|
|
197
|
+
const next: ImageFields = {
|
|
198
|
+
...draftFields(),
|
|
199
|
+
width: Math.round(frame.width),
|
|
200
|
+
height: Math.round(frame.height),
|
|
201
|
+
crop: draft
|
|
202
|
+
};
|
|
203
|
+
// The commit rebuilds the widget from the bytes; the preview styles go with the old DOM.
|
|
204
|
+
snapshot = null;
|
|
205
|
+
cropping = false;
|
|
206
|
+
seedBytes = buildBytes(target, next);
|
|
207
|
+
onCommit(target, next);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function onCropPointerDown(e: PointerEvent) {
|
|
211
|
+
if (e.button !== 0) return;
|
|
212
|
+
e.preventDefault();
|
|
213
|
+
(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);
|
|
214
|
+
lastPointer = { x: e.clientX, y: e.clientY };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function onCropPointerMove(e: PointerEvent) {
|
|
218
|
+
if (!lastPointer) return;
|
|
219
|
+
draft = panCrop(draft, e.clientX - lastPointer.x, e.clientY - lastPointer.y, frame, natural);
|
|
220
|
+
lastPointer = { x: e.clientX, y: e.clientY };
|
|
221
|
+
paintDraft();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function onCropPointerUp() {
|
|
225
|
+
lastPointer = null;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function onCropWheel(e: WheelEvent) {
|
|
229
|
+
e.preventDefault();
|
|
230
|
+
draft = zoomCrop(draft, e.deltaY);
|
|
231
|
+
paintDraft();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// The corner brackets resize the frame itself, which is what changes its aspect. The frame
|
|
235
|
+
// is anchored where the image sits in the flow, so every corner moves the far edges: a
|
|
236
|
+
// left-hand corner dragged inward shrinks the width the way the right-hand one dragged
|
|
237
|
+
// inward does.
|
|
238
|
+
type Corner = 'tl' | 'tr' | 'bl' | 'br';
|
|
239
|
+
const MIN_FRAME = 32;
|
|
240
|
+
let cornerDrag: { corner: Corner; startX: number; startY: number; start: Size } | null = null;
|
|
241
|
+
|
|
242
|
+
function onCornerPointerDown(e: PointerEvent, corner: Corner) {
|
|
243
|
+
if (e.button !== 0) return;
|
|
244
|
+
e.preventDefault();
|
|
245
|
+
e.stopPropagation();
|
|
246
|
+
(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);
|
|
247
|
+
cornerDrag = { corner, startX: e.clientX, startY: e.clientY, start: { ...frame } };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function onCornerPointerMove(e: PointerEvent) {
|
|
251
|
+
if (!cornerDrag) return;
|
|
252
|
+
const { corner, startX, startY, start } = cornerDrag;
|
|
253
|
+
const dx = (e.clientX - startX) * (corner === 'tl' || corner === 'bl' ? -1 : 1);
|
|
254
|
+
const dy = (e.clientY - startY) * (corner === 'tl' || corner === 'tr' ? -1 : 1);
|
|
255
|
+
const maxWidth = Math.max(MIN_FRAME, maxFrameWidth());
|
|
256
|
+
frame = {
|
|
257
|
+
width: Math.min(maxWidth, Math.max(MIN_FRAME, start.width + dx)),
|
|
258
|
+
height: Math.max(MIN_FRAME, start.height + dy)
|
|
259
|
+
};
|
|
260
|
+
paintDraft();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function onCornerPointerUp(e: PointerEvent) {
|
|
264
|
+
if (!cornerDrag) return;
|
|
265
|
+
e.stopPropagation();
|
|
266
|
+
cornerDrag = null;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// A double click on the picture is the crop gesture: the first click selects the image, so
|
|
270
|
+
// by the second the toolbar (and this listener) is already mounted. On the document, since
|
|
271
|
+
// each commit rebuilds the widget and a listener bound to one goes stale with it.
|
|
272
|
+
$effect(() => {
|
|
273
|
+
const onDoubleClick = (e: MouseEvent) => {
|
|
274
|
+
if (cropping) return;
|
|
275
|
+
const widget = getWidgetEl();
|
|
276
|
+
if (!widget || !(e.target instanceof Node) || !widget.contains(e.target)) return;
|
|
277
|
+
e.preventDefault();
|
|
278
|
+
// The double click leaves a native range across the widget; a crop is not a text
|
|
279
|
+
// gesture, so it goes.
|
|
280
|
+
document.getSelection()?.removeAllRanges();
|
|
281
|
+
startCrop();
|
|
282
|
+
};
|
|
283
|
+
document.addEventListener('dblclick', onDoubleClick);
|
|
284
|
+
return () => document.removeEventListener('dblclick', onDoubleClick);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
$effect(() => {
|
|
288
|
+
if (!cropping) return;
|
|
289
|
+
const onKey = (e: KeyboardEvent) => {
|
|
290
|
+
if (e.key === 'Escape') {
|
|
291
|
+
e.preventDefault();
|
|
292
|
+
cancelCrop();
|
|
293
|
+
} else if (e.key === 'Enter') {
|
|
294
|
+
e.preventDefault();
|
|
295
|
+
applyCrop();
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
document.addEventListener('keydown', onKey, true);
|
|
299
|
+
return () => document.removeEventListener('keydown', onKey, true);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// ── Placement ──────────────────────────────────────────────────────────────
|
|
303
|
+
|
|
304
|
+
/** Beside the image, level with its top, when the viewport has room there; otherwise a row
|
|
305
|
+
* inside the image's top-right corner, clamped to the viewport when the image itself runs
|
|
306
|
+
* past it. Re-measured whenever the overlay re-anchors. */
|
|
307
|
+
function keepBesideImage(node: HTMLElement): (() => void) | void {
|
|
308
|
+
const place = () => {
|
|
309
|
+
node.classList.remove('inside');
|
|
310
|
+
node.style.left = '';
|
|
311
|
+
const overlay = node.parentElement?.getBoundingClientRect();
|
|
312
|
+
if (!overlay || node.getBoundingClientRect().right <= window.innerWidth - 8) return;
|
|
313
|
+
node.classList.add('inside');
|
|
314
|
+
const rightLimit = Math.min(overlay.right, window.innerWidth) - 8;
|
|
315
|
+
const width = node.getBoundingClientRect().width;
|
|
316
|
+
node.style.left = `${Math.max(0, rightLimit - width - overlay.left)}px`;
|
|
317
|
+
};
|
|
318
|
+
place();
|
|
319
|
+
const overlay = node.parentElement;
|
|
320
|
+
const observer = overlay ? new MutationObserver(place) : null;
|
|
321
|
+
observer?.observe(overlay!, { attributes: true, attributeFilter: ['style'] });
|
|
322
|
+
window.addEventListener('resize', place);
|
|
323
|
+
return () => {
|
|
324
|
+
observer?.disconnect();
|
|
325
|
+
window.removeEventListener('resize', place);
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/** The field hangs off the toolbar's far side; with no room there it hangs off the near one. */
|
|
330
|
+
function keepFieldOnScreen(node: HTMLElement): void {
|
|
331
|
+
const rect = node.getBoundingClientRect();
|
|
332
|
+
if (rect.right > window.innerWidth - 8) node.classList.add('flipped');
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function onRootKeydown(e: KeyboardEvent) {
|
|
336
|
+
if (e.key === 'Escape' && !fieldOpen && !cropping) {
|
|
89
337
|
e.preventDefault();
|
|
90
|
-
// Esc is cancel: discard local edits so the unmount commit short-circuits on
|
|
91
|
-
// byte-equality rather than persisting in-flight typing.
|
|
92
|
-
url = fields.url;
|
|
93
|
-
alt = fields.alt;
|
|
94
|
-
titleInput = fields.title ?? '';
|
|
95
|
-
titleTouched = false;
|
|
96
338
|
onDismiss();
|
|
97
339
|
}
|
|
98
340
|
}
|
|
99
341
|
</script>
|
|
100
342
|
|
|
343
|
+
{#if cropping}
|
|
344
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
345
|
+
<div
|
|
346
|
+
class="md-image-crop-surface"
|
|
347
|
+
onpointerdown={onCropPointerDown}
|
|
348
|
+
onpointermove={onCropPointerMove}
|
|
349
|
+
onpointerup={onCropPointerUp}
|
|
350
|
+
onpointercancel={onCropPointerUp}
|
|
351
|
+
onwheel={onCropWheel}
|
|
352
|
+
>
|
|
353
|
+
{#each ['tl', 'tr', 'bl', 'br'] as const as corner (corner)}
|
|
354
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
355
|
+
<span
|
|
356
|
+
class="md-image-crop-corner {corner}"
|
|
357
|
+
data-crop-corner={corner}
|
|
358
|
+
onpointerdown={(e) => onCornerPointerDown(e, corner)}
|
|
359
|
+
onpointermove={onCornerPointerMove}
|
|
360
|
+
onpointerup={onCornerPointerUp}
|
|
361
|
+
onpointercancel={onCornerPointerUp}
|
|
362
|
+
></span>
|
|
363
|
+
{/each}
|
|
364
|
+
</div>
|
|
365
|
+
{/if}
|
|
366
|
+
|
|
101
367
|
<div
|
|
102
|
-
bind:this={
|
|
368
|
+
bind:this={rootEl}
|
|
103
369
|
class="md-image-properties"
|
|
104
|
-
role="
|
|
370
|
+
role="toolbar"
|
|
105
371
|
aria-label={IMAGE_PROPERTIES_LABEL}
|
|
106
372
|
tabindex="-1"
|
|
107
|
-
onkeydown={
|
|
373
|
+
onkeydown={onRootKeydown}
|
|
374
|
+
{@attach keepBesideImage}
|
|
108
375
|
>
|
|
109
|
-
|
|
110
|
-
<
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
<
|
|
120
|
-
|
|
376
|
+
{#if cropping}
|
|
377
|
+
<button
|
|
378
|
+
type="button"
|
|
379
|
+
class="md-image-btn"
|
|
380
|
+
aria-label={IMAGE_CROP_APPLY}
|
|
381
|
+
title={IMAGE_CROP_APPLY}
|
|
382
|
+
onclick={applyCrop}
|
|
383
|
+
>
|
|
384
|
+
<MenuIcon name="check" />
|
|
385
|
+
</button>
|
|
386
|
+
<button
|
|
387
|
+
type="button"
|
|
388
|
+
class="md-image-btn"
|
|
389
|
+
aria-label={IMAGE_CROP_CANCEL}
|
|
390
|
+
title={IMAGE_CROP_CANCEL}
|
|
391
|
+
onclick={cancelCrop}
|
|
392
|
+
>
|
|
393
|
+
<MenuIcon name="x" />
|
|
394
|
+
</button>
|
|
395
|
+
{:else}
|
|
396
|
+
<button
|
|
397
|
+
type="button"
|
|
398
|
+
class="md-image-btn"
|
|
399
|
+
class:active={fieldOpen}
|
|
400
|
+
class:set={alt.length > 0}
|
|
401
|
+
aria-label={IMAGE_ALT_FIELD}
|
|
402
|
+
aria-pressed={fieldOpen}
|
|
403
|
+
title={IMAGE_ALT_FIELD}
|
|
404
|
+
onclick={toggleField}
|
|
405
|
+
>
|
|
406
|
+
<MenuIcon name="captions" />
|
|
407
|
+
</button>
|
|
408
|
+
<button
|
|
409
|
+
type="button"
|
|
410
|
+
class="md-image-btn"
|
|
411
|
+
aria-label={IMAGE_CROP}
|
|
412
|
+
title="{IMAGE_CROP} — or double-click it"
|
|
413
|
+
onclick={startCrop}
|
|
414
|
+
>
|
|
415
|
+
<MenuIcon name="crop" />
|
|
416
|
+
</button>
|
|
417
|
+
<button
|
|
418
|
+
type="button"
|
|
419
|
+
class="md-image-btn"
|
|
420
|
+
aria-label={IMAGE_REMOVE}
|
|
421
|
+
title={IMAGE_REMOVE}
|
|
422
|
+
onclick={() => onRemove(target)}
|
|
423
|
+
>
|
|
424
|
+
<MenuIcon name="trash" />
|
|
425
|
+
</button>
|
|
426
|
+
{/if}
|
|
427
|
+
{#if fieldOpen}
|
|
428
|
+
<label class="md-image-field" {@attach keepFieldOnScreen}>
|
|
429
|
+
<span class="md-image-field-label">Alt</span>
|
|
430
|
+
<input
|
|
431
|
+
type="text"
|
|
432
|
+
bind:this={fieldInput}
|
|
433
|
+
bind:value={alt}
|
|
434
|
+
aria-label={IMAGE_ALT_FIELD}
|
|
435
|
+
placeholder={IMAGE_ALT_PLACEHOLDER}
|
|
436
|
+
onkeydown={onFieldKeydown}
|
|
437
|
+
/>
|
|
438
|
+
</label>
|
|
439
|
+
{/if}
|
|
121
440
|
</div>
|
|
122
441
|
|
|
123
442
|
<style>
|
|
443
|
+
/* limestone's cover actions: a stack of small surface buttons beside the image, level with
|
|
444
|
+
its top. `inside` (no room beside) lays them as a row in the image's top-right corner. */
|
|
124
445
|
.md-image-properties {
|
|
125
446
|
position: absolute;
|
|
126
|
-
top:
|
|
127
|
-
left:
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
padding: 8px;
|
|
132
|
-
display: grid;
|
|
133
|
-
gap: 6px;
|
|
134
|
-
min-width: 280px;
|
|
447
|
+
top: 0;
|
|
448
|
+
left: calc(100% + 8px);
|
|
449
|
+
display: flex;
|
|
450
|
+
flex-direction: column;
|
|
451
|
+
gap: 4px;
|
|
135
452
|
z-index: 100;
|
|
136
|
-
|
|
453
|
+
outline: none;
|
|
454
|
+
font-family: var(--font-ui, system-ui, sans-serif);
|
|
137
455
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
456
|
+
/* `inside` is the placement attachment's own class, written outside the template. */
|
|
457
|
+
.md-image-properties:global(.inside) {
|
|
458
|
+
top: 8px;
|
|
459
|
+
flex-direction: row;
|
|
460
|
+
}
|
|
461
|
+
.md-image-btn {
|
|
462
|
+
display: inline-flex;
|
|
141
463
|
align-items: center;
|
|
142
|
-
|
|
464
|
+
justify-content: center;
|
|
465
|
+
width: 26px;
|
|
466
|
+
height: 26px;
|
|
467
|
+
padding: 0;
|
|
468
|
+
border: none;
|
|
469
|
+
border-radius: 6px;
|
|
470
|
+
background: var(--color-bg, #2c2c2a);
|
|
471
|
+
color: var(--color-text-secondary, #cfcfca);
|
|
472
|
+
cursor: pointer;
|
|
473
|
+
box-shadow: var(--menu-shadow, 0 12px 32px rgba(0, 0, 0, 0.5), 0 2px 8px rgba(0, 0, 0, 0.35));
|
|
474
|
+
}
|
|
475
|
+
.md-image-btn:hover,
|
|
476
|
+
.md-image-btn.active,
|
|
477
|
+
.md-image-btn.set {
|
|
478
|
+
color: var(--color-text-primary, #e8e8e5);
|
|
143
479
|
}
|
|
144
|
-
|
|
480
|
+
.md-image-btn.active {
|
|
145
481
|
background: var(--color-surface, #2d3033);
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/* The alt field, hung off the toolbar's far side on the menu surface: a caption over its
|
|
485
|
+
input, the way a properties row reads, rather than a label beside a box. */
|
|
486
|
+
.md-image-field {
|
|
487
|
+
position: absolute;
|
|
488
|
+
top: 0;
|
|
489
|
+
left: calc(100% + 6px);
|
|
490
|
+
width: 240px;
|
|
491
|
+
padding: 8px 10px 10px;
|
|
492
|
+
box-sizing: border-box;
|
|
493
|
+
display: flex;
|
|
494
|
+
flex-direction: column;
|
|
495
|
+
gap: 5px;
|
|
496
|
+
background: var(--color-bg, #2c2c2a);
|
|
497
|
+
border: 1px solid var(--color-border, #3e3e3b);
|
|
498
|
+
border-radius: 8px;
|
|
499
|
+
box-shadow: var(--menu-shadow, 0 12px 32px rgba(0, 0, 0, 0.5), 0 2px 8px rgba(0, 0, 0, 0.35));
|
|
500
|
+
}
|
|
501
|
+
.md-image-field.flipped {
|
|
502
|
+
left: auto;
|
|
503
|
+
right: calc(100% + 6px);
|
|
504
|
+
}
|
|
505
|
+
.md-image-properties:global(.inside) .md-image-field {
|
|
506
|
+
left: auto;
|
|
507
|
+
right: 0;
|
|
508
|
+
top: calc(100% + 6px);
|
|
509
|
+
}
|
|
510
|
+
.md-image-field-label {
|
|
511
|
+
font-size: 10.5px;
|
|
512
|
+
font-weight: 600;
|
|
513
|
+
letter-spacing: 0.06em;
|
|
514
|
+
text-transform: uppercase;
|
|
515
|
+
color: var(--color-text-muted, #aaaaaa);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/* Underlined, not boxed: one rule under the text reads as a field to fill in and keeps the
|
|
519
|
+
surface calm, where a second rounded box inside a rounded card reads as chrome on chrome. */
|
|
520
|
+
.md-image-field input {
|
|
521
|
+
width: 100%;
|
|
522
|
+
box-sizing: border-box;
|
|
523
|
+
background: transparent;
|
|
524
|
+
color: var(--color-text-primary, #e8e8e5);
|
|
525
|
+
border: none;
|
|
526
|
+
border-bottom: 1px solid var(--color-border, #3e3e3b);
|
|
527
|
+
border-radius: 0;
|
|
528
|
+
padding: 3px 1px 5px;
|
|
150
529
|
font-family: inherit;
|
|
151
|
-
font-size:
|
|
530
|
+
font-size: 13px;
|
|
531
|
+
line-height: 1.4;
|
|
532
|
+
outline: none;
|
|
533
|
+
transition: border-color 120ms ease-out;
|
|
534
|
+
}
|
|
535
|
+
.md-image-field input:focus {
|
|
536
|
+
border-bottom-color: var(--color-accent, #567b67);
|
|
537
|
+
}
|
|
538
|
+
.md-image-field input::placeholder {
|
|
539
|
+
color: var(--color-text-muted, #aaaaaa);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/* The crop session: the whole image is a pan surface, bracketed at the corners. */
|
|
543
|
+
.md-image-crop-surface {
|
|
544
|
+
position: absolute;
|
|
545
|
+
inset: 0;
|
|
546
|
+
cursor: grab;
|
|
547
|
+
touch-action: none;
|
|
548
|
+
z-index: 20;
|
|
549
|
+
}
|
|
550
|
+
.md-image-crop-surface:active {
|
|
551
|
+
cursor: grabbing;
|
|
552
|
+
}
|
|
553
|
+
/* The brackets are the frame's own handles: dragging one resizes the frame. */
|
|
554
|
+
.md-image-crop-corner {
|
|
555
|
+
position: absolute;
|
|
556
|
+
width: 18px;
|
|
557
|
+
height: 18px;
|
|
558
|
+
border: 2px solid rgba(255, 255, 255, 0.9);
|
|
559
|
+
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.6));
|
|
560
|
+
touch-action: none;
|
|
561
|
+
}
|
|
562
|
+
.md-image-crop-corner::before {
|
|
563
|
+
content: '';
|
|
564
|
+
position: absolute;
|
|
565
|
+
inset: -8px;
|
|
566
|
+
}
|
|
567
|
+
.md-image-crop-corner.tl {
|
|
568
|
+
top: 8px;
|
|
569
|
+
left: 8px;
|
|
570
|
+
border-right: none;
|
|
571
|
+
border-bottom: none;
|
|
572
|
+
cursor: nwse-resize;
|
|
573
|
+
}
|
|
574
|
+
.md-image-crop-corner.tr {
|
|
575
|
+
top: 8px;
|
|
576
|
+
right: 8px;
|
|
577
|
+
border-left: none;
|
|
578
|
+
border-bottom: none;
|
|
579
|
+
cursor: nesw-resize;
|
|
580
|
+
}
|
|
581
|
+
.md-image-crop-corner.bl {
|
|
582
|
+
bottom: 8px;
|
|
583
|
+
left: 8px;
|
|
584
|
+
border-right: none;
|
|
585
|
+
border-top: none;
|
|
586
|
+
cursor: nesw-resize;
|
|
587
|
+
}
|
|
588
|
+
.md-image-crop-corner.br {
|
|
589
|
+
bottom: 8px;
|
|
590
|
+
right: 8px;
|
|
591
|
+
border-left: none;
|
|
592
|
+
border-top: none;
|
|
593
|
+
cursor: nwse-resize;
|
|
152
594
|
}
|
|
153
595
|
</style>
|
|
@@ -3,12 +3,17 @@ import { type WidgetTarget } from './widget-selection-state.svelte';
|
|
|
3
3
|
type $$ComponentProps = {
|
|
4
4
|
target: WidgetTarget;
|
|
5
5
|
fields: ImageFields;
|
|
6
|
+
getWidgetEl: () => HTMLElement | null;
|
|
6
7
|
/** The popover's dirty check is a byte comparison, and the bytes are the write
|
|
7
8
|
* seam's to decide. */
|
|
8
9
|
buildBytes: (target: WidgetTarget, fields: ImageFields) => string | null;
|
|
9
10
|
onCommit: (target: WidgetTarget, newFields: ImageFields) => void;
|
|
11
|
+
onRemove: (target: WidgetTarget) => void;
|
|
10
12
|
onDismiss: () => void;
|
|
13
|
+
/** The column width: the widest frame a corner drag can open. */
|
|
14
|
+
maxFrameWidth: () => number;
|
|
15
|
+
cropping?: boolean;
|
|
11
16
|
};
|
|
12
|
-
declare const ImageProperties: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
17
|
+
declare const ImageProperties: import("svelte").Component<$$ComponentProps, {}, "cropping">;
|
|
13
18
|
type ImageProperties = ReturnType<typeof ImageProperties>;
|
|
14
19
|
export default ImageProperties;
|