edytor 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/README.md +14 -72
  2. package/dist/block/block.svelte.d.ts +6 -4
  3. package/dist/block/block.svelte.js +10 -4
  4. package/dist/block/inlineBlock.svelte.d.ts +6 -2
  5. package/dist/block/inlineBlock.svelte.js +7 -2
  6. package/dist/cloudflare/room/context.d.ts +6 -0
  7. package/dist/collaboration/awarenessSelection.d.ts +22 -1
  8. package/dist/collaboration/awarenessSelection.js +42 -0
  9. package/dist/components/Block.svelte +3 -1
  10. package/dist/components/Edytor.svelte +36 -10
  11. package/dist/components/Edytor.svelte.d.ts +8 -1
  12. package/dist/components/InlineBlock.svelte +1 -1
  13. package/dist/crdt/data.d.ts +10 -0
  14. package/dist/crdt/data.js +20 -2
  15. package/dist/crdt/doc/reads.d.ts +7 -0
  16. package/dist/crdt/doc/reads.js +11 -1
  17. package/dist/crdt/document.d.ts +8 -0
  18. package/dist/crdt/document.js +12 -0
  19. package/dist/crdt/edytor-doc.d.ts +18 -0
  20. package/dist/crdt/edytor-doc.js +2 -1
  21. package/dist/crdt/index.d.ts +6 -0
  22. package/dist/crdt/operations.d.ts +1 -1
  23. package/dist/crdt/operations.js +1 -0
  24. package/dist/crdt/props.d.ts +32 -0
  25. package/dist/crdt/props.js +172 -0
  26. package/dist/{plugins/blockHandles → dnd}/dropIndicator.d.ts +2 -2
  27. package/dist/{plugins/blockHandles → dnd}/geometry.d.ts +2 -2
  28. package/dist/dnd/placement.d.ts +200 -0
  29. package/dist/dnd/placement.js +426 -0
  30. package/dist/edytor.svelte.d.ts +17 -4
  31. package/dist/edytor.svelte.js +16 -7
  32. package/dist/events/beforeInputCommands.d.ts +2 -2
  33. package/dist/events/beforeInputCommands.js +8 -4
  34. package/dist/events/onDrop.d.ts +6 -0
  35. package/dist/events/onDrop.js +38 -10
  36. package/dist/events/onPaste.d.ts +2 -1
  37. package/dist/events/onPaste.js +8 -3
  38. package/dist/index.d.ts +2 -2
  39. package/dist/index.js +1 -1
  40. package/dist/labels.d.ts +2 -0
  41. package/dist/labels.js +2 -1
  42. package/dist/plugins/arrowMove/arrowMove.d.ts +1 -1
  43. package/dist/plugins/arrowMove/arrowMove.js +1 -1
  44. package/dist/plugins/blockHandles/BlockHandleController.svelte.d.ts +2 -122
  45. package/dist/plugins/blockHandles/BlockHandleController.svelte.js +29 -387
  46. package/dist/plugins/blockHandles/BlockHandles.svelte +14 -32
  47. package/dist/plugins/blockHandles/blockHandlesPlugin.js +2 -1
  48. package/dist/plugins/code/CodeHeader.svelte +5 -3
  49. package/dist/plugins/code/CodePlugin.svelte +3 -3
  50. package/dist/plugins/code/languages.d.ts +0 -3
  51. package/dist/plugins/code/languages.js +0 -2
  52. package/dist/plugins/columns/ColumnResize.svelte +3 -2
  53. package/dist/plugins/columns/ColumnsPlugin.svelte +1 -1
  54. package/dist/plugins/columns/gaps.d.ts +7 -5
  55. package/dist/plugins/columns/gaps.js +11 -6
  56. package/dist/plugins/columns/resize.svelte.d.ts +3 -3
  57. package/dist/plugins/columns/resize.svelte.js +11 -10
  58. package/dist/plugins/equation/EquationPlugin.svelte +1 -8
  59. package/dist/plugins/equation/EquationPlugin.svelte.d.ts +1 -0
  60. package/dist/plugins/equation/equation.css +7 -0
  61. package/dist/plugins/fileDrop.d.ts +28 -0
  62. package/dist/plugins/fileDrop.js +116 -0
  63. package/dist/plugins/image/ImagePlugin.svelte +42 -33
  64. package/dist/plugins/image/ImagePlugin.svelte.d.ts +13 -1
  65. package/dist/plugins/image/image.css +17 -0
  66. package/dist/plugins/index.d.ts +3 -4
  67. package/dist/plugins/index.js +0 -1
  68. package/dist/plugins/markdownShortcuts.js +5 -5
  69. package/dist/plugins/media/AudioPlugin.svelte +13 -1
  70. package/dist/plugins/media/AudioPlugin.svelte.d.ts +11 -1
  71. package/dist/plugins/media/FilePlugin.svelte +11 -1
  72. package/dist/plugins/media/FilePlugin.svelte.d.ts +9 -0
  73. package/dist/plugins/media/SourceBlock.svelte +48 -12
  74. package/dist/plugins/media/SourceBlock.svelte.d.ts +4 -3
  75. package/dist/plugins/media/UploadPreview.svelte +32 -0
  76. package/dist/plugins/media/UploadPreview.svelte.d.ts +11 -0
  77. package/dist/plugins/media/VideoPlugin.svelte +13 -1
  78. package/dist/plugins/media/VideoPlugin.svelte.d.ts +11 -1
  79. package/dist/plugins/media/empty.svelte.d.ts +13 -4
  80. package/dist/plugins/media/empty.svelte.js +30 -19
  81. package/dist/plugins/media/media.d.ts +7 -0
  82. package/dist/plugins/media/upload.css +23 -0
  83. package/dist/plugins/mention/MentionPlugin.svelte +72 -33
  84. package/dist/plugins/mention/MentionPlugin.svelte.d.ts +11 -2
  85. package/dist/plugins/mention/mention.css +6 -0
  86. package/dist/plugins/pageLink/PageLinkPlugin.svelte +122 -17
  87. package/dist/plugins/pageLink/PageLinkPlugin.svelte.d.ts +13 -1
  88. package/dist/plugins/pageLink/pageLink.css +6 -0
  89. package/dist/plugins/richtext/RichTextPlugin.svelte +14 -7
  90. package/dist/plugins/richtext/RichTextPlugin.svelte.d.ts +1 -0
  91. package/dist/plugins/richtext/richtext.css +6 -0
  92. package/dist/plugins/table/chrome.svelte.js +4 -3
  93. package/dist/plugins/uploads.svelte.d.ts +45 -32
  94. package/dist/plugins/uploads.svelte.js +105 -39
  95. package/dist/plugins.d.ts +40 -14
  96. package/dist/session/attempt.d.ts +16 -6
  97. package/dist/session/attempt.js +1 -0
  98. package/dist/session/bindings.js +34 -31
  99. package/dist/session/commands.js +2 -0
  100. package/dist/session/history.js +1 -0
  101. package/dist/session/inputRules.d.ts +6 -3
  102. package/dist/session/inputRules.js +9 -5
  103. package/dist/session/moves.d.ts +33 -0
  104. package/dist/session/moves.js +71 -1
  105. package/dist/session/navigation.d.ts +7 -0
  106. package/dist/session/navigation.js +19 -10
  107. package/dist/session/props.d.ts +28 -0
  108. package/dist/session/props.js +65 -0
  109. package/dist/session/uploads.d.ts +29 -0
  110. package/dist/session/uploads.js +10 -0
  111. package/dist/themes/notion.css +9 -0
  112. package/package.json +1 -1
  113. /package/dist/{plugins/blockHandles → dnd}/dropIndicator.js +0 -0
  114. /package/dist/{plugins/blockHandles → dnd}/geometry.js +0 -0
  115. /package/dist/{plugins/columns → dnd}/stacking.d.ts +0 -0
  116. /package/dist/{plugins/columns → dnd}/stacking.js +0 -0
@@ -135,7 +135,7 @@ export const docFacade = (Y, layers) => {
135
135
  const base = { Y, doc, runsView, roles, roleOf, rendersContentOf };
136
136
  const reads = { ...base, ...docReads(base) };
137
137
  const shape = { ...reads, ...docCapability(reads) };
138
- const { blockJSON, view, blockTypeOf, blockDataOf, docData, dataItemIds, childrenIds, positionOf, pathOf, ancestorsOf, slotOf, isVoid, isIsland, isLines, islandOf, insideIsland, isLayout, isLayoutItem, order, compare, next, previous, displayLength, contentItems, hasBlock, live } = reads;
138
+ const { blockJSON, view, blockTypeOf, blockDataOf, docData, dataItemIds, dataPreview, childrenIds, positionOf, pathOf, ancestorsOf, slotOf, isVoid, isIsland, isLines, islandOf, insideIsland, isLayout, isLayoutItem, order, compare, next, previous, displayLength, contentItems, hasBlock, live } = reads;
139
139
  const { canPlace, fits, nestParent, canMerge, defaultChild } = shape;
140
140
  const funnel = writeFunnel({ ...reads, BA, config });
141
141
  const { write, apply } = funnel;
@@ -262,6 +262,7 @@ export const docFacade = (Y, layers) => {
262
262
  blockDataOf: byRef(blockDataOf),
263
263
  docData,
264
264
  dataItemIds,
265
+ dataPreview,
265
266
  /**
266
267
  * Compact per-block attribution (`{createdBy, contributors,
267
268
  * lastChangedBy}`), or `undefined` for unauthored/system blocks.
@@ -280,6 +280,12 @@ export declare const bindCrdt: (Y: EngineApi) => {
280
280
  blockDataOf: (id: string) => Record<string, unknown> | undefined;
281
281
  docData: () => import("./doc/types.js").JsonObj;
282
282
  dataItemIds: (target: import("./edytor-doc.js").DataTarget, path: readonly string[]) => string[];
283
+ dataPreview: (target: import("./edytor-doc.js").DataTarget, pending: readonly import("./data.js").DataPatch[]) => {
284
+ data: {
285
+ [x: string]: unknown;
286
+ };
287
+ ids: (path: readonly string[]) => string[];
288
+ } | null;
283
289
  blockAttribution: (id: string) => import("./index.js").BlockAttribution | undefined;
284
290
  crdtId: (id: string) => string | null;
285
291
  resolveBlock: (id: string) => import("./engine-api.js").EngineNode | null;
@@ -149,4 +149,4 @@ export interface DocumentOperations extends DocumentWrites, DocumentReads {
149
149
  /** Every write of `DocumentWrites`: what a view dispatches under the same names (`edytor.insertBlocks(…)`). */
150
150
  export declare const DOCUMENT_WRITES: readonly ["insertBlocks", "moveBlocks", "nestBlock", "unNestBlock", "unNestBlocks", "liftOut", "wrapInLayout", "placeBeside", "splitBlock", "mergeBlocks", "mergeBackward", "mergeForward", "deleteBlock", "deleteBlocks", "setBlock", "setBlockType", "patchData", "duplicateBlock", "insertText", "deleteText", "formatRange", "clearMarks", "insertInline", "removeInline", "deleteRange", "replaceRange", "insertFlow"];
151
151
  /** Every name of `DocumentOperations`, for the document's own members (`EdytorDocument`). */
152
- export declare const DOCUMENT_OPERATIONS: readonly ["insertBlocks", "moveBlocks", "nestBlock", "unNestBlock", "unNestBlocks", "liftOut", "wrapInLayout", "placeBeside", "splitBlock", "mergeBlocks", "mergeBackward", "mergeForward", "deleteBlock", "deleteBlocks", "setBlock", "setBlockType", "patchData", "duplicateBlock", "insertText", "deleteText", "formatRange", "clearMarks", "insertInline", "removeInline", "deleteRange", "replaceRange", "insertFlow", "toJSON", "docData", "dataItemIds", "project", "blockJSON", "childrenIds", "parentOf", "ancestorsOf", "positionOf", "pathOf", "blockTypeOf", "blockDataOf", "blockText", "contentItems", "displayLength", "runs", "hasBlock", "isVisibleBlock", "listBlockIds", "blockAttribution", "isVoid", "isIsland", "isLines", "islandOf", "insideIsland", "isLayout", "isLayoutItem", "besideAt", "isTable", "isTableRow", "isTableCell", "tableOf", "tableColumns", "tableGrid", "order", "compare", "next", "previous", "canPlace", "canMerge", "fits", "nestParent", "landingOf", "anchorAt", "resolveAnchor", "prepare", "apply", "compose", "transact", "onChange", "version"];
152
+ export declare const DOCUMENT_OPERATIONS: readonly ["insertBlocks", "moveBlocks", "nestBlock", "unNestBlock", "unNestBlocks", "liftOut", "wrapInLayout", "placeBeside", "splitBlock", "mergeBlocks", "mergeBackward", "mergeForward", "deleteBlock", "deleteBlocks", "setBlock", "setBlockType", "patchData", "duplicateBlock", "insertText", "deleteText", "formatRange", "clearMarks", "insertInline", "removeInline", "deleteRange", "replaceRange", "insertFlow", "toJSON", "docData", "dataItemIds", "dataPreview", "project", "blockJSON", "childrenIds", "parentOf", "ancestorsOf", "positionOf", "pathOf", "blockTypeOf", "blockDataOf", "blockText", "contentItems", "displayLength", "runs", "hasBlock", "isVisibleBlock", "listBlockIds", "blockAttribution", "isVoid", "isIsland", "isLines", "islandOf", "insideIsland", "isLayout", "isLayoutItem", "besideAt", "isTable", "isTableRow", "isTableCell", "tableOf", "tableColumns", "tableGrid", "order", "compare", "next", "previous", "canPlace", "canMerge", "fits", "nestParent", "landingOf", "anchorAt", "resolveAnchor", "prepare", "apply", "compose", "transact", "onChange", "version"];
@@ -34,6 +34,7 @@ export const DOCUMENT_OPERATIONS = [
34
34
  'toJSON',
35
35
  'docData',
36
36
  'dataItemIds',
37
+ 'dataPreview',
37
38
  'project',
38
39
  'blockJSON',
39
40
  'childrenIds',
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Properties: the deep proxy behind `block.data`, `atom.data`,
3
+ * `edytor.data` and `document.data` (SyncedStore's feel, one small module).
4
+ * It is sugar over the one write path: every mutation is a data patch
5
+ * handed to `write`, which is the `patchData` operation (dispatched on a
6
+ * view, raw on a bare document).
7
+ *
8
+ * A read goes to `read()` every time (the live source), so a reactive
9
+ * reader of `block.data.title` re-runs when anyone changes it, and a key a
10
+ * peer deletes is gone. Plain objects read as nested proxies, arrays as
11
+ * array proxies, everything else as the raw value; a proxy is cached per
12
+ * path, so its identity is stable. An object or array held in an array
13
+ * item is that item's (its path names the item by id, from `ids`): it
14
+ * stays on its item wherever peers move it, so `{#each list as item (item)}`
15
+ * keeps a row on its item; once the item is removed, its writes are
16
+ * refused. A key is a path segment as `keySegment` writes it.
17
+ *
18
+ * Writes, each the patch of only what it changes (per-leaf storage, so
19
+ * concurrent edits of other keys and items merge): `obj.key = v`,
20
+ * `arr[i] = v` and `delete obj.key` patch that path; `push`, `pop`,
21
+ * `shift`, `unshift`, `splice` and `length` are one `splice` (the items
22
+ * kept keep their ids), `sort` and `reverse` one `order` (the items move),
23
+ * `fill` and `copyWithin` writes of the items they change. Assigning a
24
+ * value equal to the one there writes nothing. `JSON.stringify` and
25
+ * `$state.snapshot` read plain JSON through it.
26
+ */
27
+ import { type DataPatch } from './data.js';
28
+ type Json = Record<string, unknown>;
29
+ export declare const propsProxy: <T extends object = Json>(read: () => Json, write: (ops: DataPatch[]) => void,
30
+ /** The ids of the items of the array at a path (`facade.dataItemIds`). */
31
+ ids?: (path: readonly string[]) => string[]) => T;
32
+ export {};
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Properties: the deep proxy behind `block.data`, `atom.data`,
3
+ * `edytor.data` and `document.data` (SyncedStore's feel, one small module).
4
+ * It is sugar over the one write path: every mutation is a data patch
5
+ * handed to `write`, which is the `patchData` operation (dispatched on a
6
+ * view, raw on a bare document).
7
+ *
8
+ * A read goes to `read()` every time (the live source), so a reactive
9
+ * reader of `block.data.title` re-runs when anyone changes it, and a key a
10
+ * peer deletes is gone. Plain objects read as nested proxies, arrays as
11
+ * array proxies, everything else as the raw value; a proxy is cached per
12
+ * path, so its identity is stable. An object or array held in an array
13
+ * item is that item's (its path names the item by id, from `ids`): it
14
+ * stays on its item wherever peers move it, so `{#each list as item (item)}`
15
+ * keeps a row on its item; once the item is removed, its writes are
16
+ * refused. A key is a path segment as `keySegment` writes it.
17
+ *
18
+ * Writes, each the patch of only what it changes (per-leaf storage, so
19
+ * concurrent edits of other keys and items merge): `obj.key = v`,
20
+ * `arr[i] = v` and `delete obj.key` patch that path; `push`, `pop`,
21
+ * `shift`, `unshift`, `splice` and `length` are one `splice` (the items
22
+ * kept keep their ids), `sort` and `reverse` one `order` (the items move),
23
+ * `fill` and `copyWithin` writes of the items they change. Assigning a
24
+ * value equal to the one there writes nothing. `JSON.stringify` and
25
+ * `$state.snapshot` read plain JSON through it.
26
+ */
27
+ import { isObject, keySegment, segmentKey } from './data.js';
28
+ import { jsonEquals } from '../utils/json.js';
29
+ /** A plain JSON copy (proxies included), or `undefined`. */
30
+ const plain = (v) => (v === undefined ? v : JSON.parse(JSON.stringify(v)));
31
+ /** The items of `now` that differ from `was`, as writes of those items. */
32
+ const changed = (_, __, was, now) => now.flatMap((value, i) => JSON.stringify(value) === JSON.stringify(was[i]) ? [] : [{ at: `${i}`, value }]);
33
+ /**
34
+ * Each array mutator as the patches of the array (or of its items `at`),
35
+ * from its length `n`, the call's `args`, the array `was` and the result `out`.
36
+ */
37
+ const MUTATORS = {
38
+ push: (n, args) => ({ splice: [n, 0, ...args] }),
39
+ pop: (n) => ({ splice: [Math.max(n - 1, 0), n && 1] }),
40
+ shift: () => ({ splice: [0, 1] }),
41
+ unshift: (_, args) => ({ splice: [0, 0, ...args] }),
42
+ // The start and count `splice` resolved (negative, fractional, past the end).
43
+ splice: (n, [start, , ...items], _, out) => {
44
+ const s = Math.trunc(Number(start)) || 0;
45
+ return { splice: [s < 0 ? Math.max(n + s, 0) : Math.min(s, n), out.length, ...items] };
46
+ },
47
+ sort: (n, [compare], was) => ({ order: sorted(n, was, compare) }),
48
+ reverse: (n) => ({ order: Array.from({ length: n }, (_, i) => n - 1 - i) }),
49
+ fill: changed,
50
+ copyWithin: changed
51
+ };
52
+ /** The indexes `Array.prototype.sort` puts `values` in (stable, `undefined` compare: by string). */
53
+ const sorted = (n, values, compare) => {
54
+ const by = typeof compare === 'function'
55
+ ? compare
56
+ : (a, b) => (`${a}` < `${b}` ? -1 : `${a}` > `${b}` ? 1 : 0);
57
+ return Array.from({ length: n }, (_, i) => i).sort((a, b) => by(values[a], values[b]));
58
+ };
59
+ export const propsProxy = (read, write,
60
+ /** The ids of the items of the array at a path (`facade.dataItemIds`). */
61
+ ids = () => []) => {
62
+ const cache = new Map();
63
+ // Item ids per array path, for the value `read()` last gave.
64
+ let seen;
65
+ const known = new Map();
66
+ const idsOf = (path) => {
67
+ const now = read();
68
+ if (now !== seen) {
69
+ seen = now;
70
+ known.clear();
71
+ }
72
+ const key = JSON.stringify(path);
73
+ return known.get(key) ?? known.set(key, ids(path)).get(key);
74
+ };
75
+ const at = (path) => path.reduce((v, k, n) => /^~[a-z]/.test(k)
76
+ ? Array.isArray(v)
77
+ ? v[idsOf(path.slice(0, n)).indexOf(k)]
78
+ : undefined // its array is gone
79
+ : v !== null && typeof v === 'object'
80
+ ? v[segmentKey(k)]
81
+ : undefined, read());
82
+ const put = (path, value) => {
83
+ const next = plain(value);
84
+ if (jsonEquals(at(path), next))
85
+ return;
86
+ write([{ path, ...(next !== undefined && { value: next }) }]);
87
+ };
88
+ const wrap = (path, v) => Array.isArray(v) || isObject(v) ? proxy(path, Array.isArray(v)) : v;
89
+ const proxy = (path, array) => {
90
+ const key = `${array ? '[' : '{'}${JSON.stringify(path)}`;
91
+ const cached = cache.get(key);
92
+ if (cached)
93
+ return cached;
94
+ const current = () => {
95
+ const v = at(path);
96
+ return array ? (Array.isArray(v) ? v : []) : isObject(v) ? v : {};
97
+ };
98
+ /** The path of child `k`: an item by its id, a key as its segment. */
99
+ const child = (k) => [...path, (array && idsOf(path)[Number(k)]) || keySegment(k)];
100
+ const items = () => current().map((v, i) => wrap(child(`${i}`), v));
101
+ const own = (k) => Object.prototype.hasOwnProperty.call(current(), k);
102
+ const made = new Proxy(array ? [] : {}, {
103
+ get(target, k) {
104
+ if (typeof k === 'symbol')
105
+ return array && k === Symbol.iterator
106
+ ? () => items()[Symbol.iterator]()
107
+ : Reflect.get(target, k);
108
+ if (array && k === 'length')
109
+ return current().length;
110
+ if (own(k))
111
+ return wrap(child(k), current()[k]);
112
+ if (array && Object.hasOwn(MUTATORS, k))
113
+ return (...args) => {
114
+ const was = plain(current());
115
+ const next = [...was];
116
+ const fn = Array.prototype[k];
117
+ args = args.map((a) => (a !== null && typeof a === 'object' ? plain(a) : a));
118
+ const out = fn.apply(next, args);
119
+ const ops = [MUTATORS[k](was.length, args, was, out)].flat();
120
+ if (!jsonEquals(was, next))
121
+ write(ops.map(({ at, ...op }) => ({ ...op, path: at ? [...path, at] : path })));
122
+ return out === next ? made : out;
123
+ };
124
+ const method = array ? Array.prototype[k] : undefined;
125
+ if (typeof method === 'function')
126
+ return (...args) => method.apply(items(), args);
127
+ return Reflect.get(target, k);
128
+ },
129
+ set(_, k, value) {
130
+ if (typeof k === 'symbol')
131
+ return false;
132
+ if (array && k === 'length') {
133
+ const n = current().length;
134
+ const nulls = Array.from({ length: Math.max(value - n, 0) }, () => null);
135
+ write([{ path, splice: [Math.min(value, n), Math.max(n - value, 0), ...nulls] }]);
136
+ }
137
+ else
138
+ put([...path, keySegment(k)], value);
139
+ return true;
140
+ },
141
+ deleteProperty(_, k) {
142
+ if (typeof k === 'symbol')
143
+ return false;
144
+ if (own(k))
145
+ put([...path, keySegment(k)], undefined);
146
+ return true;
147
+ },
148
+ has: (target, k) => (typeof k === 'string' && own(k)) || Reflect.has(target, k),
149
+ ownKeys: () => [...Object.keys(current()), ...(array ? ['length'] : [])],
150
+ getOwnPropertyDescriptor(target, k) {
151
+ if (array && k === 'length')
152
+ return (Reflect.getOwnPropertyDescriptor(target, k) && {
153
+ value: current().length,
154
+ writable: true,
155
+ enumerable: false,
156
+ configurable: false
157
+ });
158
+ if (typeof k !== 'string' || !own(k))
159
+ return undefined;
160
+ return {
161
+ value: wrap(child(k), current()[k]),
162
+ writable: true,
163
+ enumerable: true,
164
+ configurable: true
165
+ };
166
+ }
167
+ });
168
+ cache.set(key, made);
169
+ return made;
170
+ };
171
+ return proxy([], false);
172
+ };
@@ -5,8 +5,8 @@
5
5
  * future parent's own row. Both live in the overlay layer, measured each
6
6
  * frame. The controller decides the placement; this draws it.
7
7
  */
8
- import type { Block } from '../../block/block.svelte.js';
9
- import type { Edytor } from '../../edytor.svelte.js';
8
+ import type { Block } from '../block/block.svelte.js';
9
+ import type { Edytor } from '../edytor.svelte.js';
10
10
  import { type DropPlacement } from './geometry.js';
11
11
  /** What the indicator reads of the controller: the dragged blocks and the beside resolution. */
12
12
  type IndicatorContext = {
@@ -4,8 +4,8 @@
4
4
  * placement's shape, a block's own row, its first text line and one
5
5
  * nesting step.
6
6
  */
7
- import type { Block } from '../../block/block.svelte.js';
8
- import type { BlockMovePosition } from '../../session/moves.js';
7
+ import type { Block } from '../block/block.svelte.js';
8
+ import type { BlockMovePosition } from '../session/moves.js';
9
9
  export type DropPlacement = {
10
10
  target: Block;
11
11
  node: HTMLElement;
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Where a drop lands in a view (Notion's rules): the one resolution a block
3
+ * handle's drag and a file dragged from outside both read, and the drop
4
+ * indicator draws (`dropIndicator.ts`). A drag is what it carries
5
+ * (`Drag`: the blocks it moves, none for files) and the placements the
6
+ * document takes for it (`fits`); the rows are the view's drop targets
7
+ * (`targetOf`). The block handles wire it to the drag library; a file drag
8
+ * reads it from the host's own `dragover` (the media plugins' `plugins/fileDrop.ts`).
9
+ */
10
+ import type { Block } from '../block/block.svelte.js';
11
+ import type { Edytor } from '../edytor.svelte.js';
12
+ import type { DropIndicator } from './dropIndicator.js';
13
+ import { type DropPlacement } from './geometry.js';
14
+ export type Input = {
15
+ clientX: number;
16
+ clientY: number;
17
+ };
18
+ /** What a drag carries: the blocks it moves (none for files), and whether a placement takes it. */
19
+ export type Drag = {
20
+ group: readonly Block[];
21
+ fits: (placement: DropPlacement) => boolean;
22
+ };
23
+ /** How far beside a target's box it stays the drop target (`getIsSticky`), across and along. */
24
+ export declare const SLOP_X = 20;
25
+ export declare const SLOP_Y = 24;
26
+ /**
27
+ * A block a drop may land at (a drop target): a movable block, not a
28
+ * table's row (its row menus move it) nor a cell.
29
+ */
30
+ export declare const isDropTarget: (edytor: Edytor, block: Block) => boolean;
31
+ type ResolverContext = {
32
+ /** The drop target block of a block element, if it is one. */
33
+ targetOf: (node: HTMLElement) => Block | undefined;
34
+ /** The drag's handle (its width is the handle column's), if any. */
35
+ handle: () => HTMLElement | null;
36
+ /** The indicator showing the drag's placement (a margin's reorder holds while it shows). */
37
+ indicator: DropIndicator;
38
+ };
39
+ export declare class DropResolver {
40
+ private readonly edytor;
41
+ private readonly ctx;
42
+ constructor(edytor: Edytor, ctx: ResolverContext);
43
+ targetOf: (node: HTMLElement) => Block | undefined;
44
+ isTarget: (node: HTMLElement) => boolean;
45
+ /**
46
+ * The placement over `row` at the pointer: a beside band of the row first
47
+ * (`beside`; `false` when the document refuses it: nothing shows), else
48
+ * the row's halves, the top one first when `before` (the pointer's half,
49
+ * or the one a sticky target holds).
50
+ */
51
+ at(drag: Drag, row: Block, input: Input, before: boolean): DropPlacement | null | false;
52
+ /**
53
+ * The drop target under the pointer for a drag the host's own events see
54
+ * (a file's): the innermost target element at the point, `null` outside
55
+ * the blocks and in the handle column (the margins answer there,
56
+ * `margin`).
57
+ */
58
+ hitAt(input: Input): HTMLElement | null;
59
+ /** The placement over the row of drop target `node` at the pointer (`rowAt`, its own row's half: `at`). */
60
+ over(drag: Drag, node: HTMLElement, input: Input): DropPlacement | null | false;
61
+ /**
62
+ * The editor's content column (`start`…`end`, the root's content box:
63
+ * where its blocks' boxes start and end), the width of the handle column
64
+ * left of it (the drag's handle, measured: `+`, grip and the gap to the
65
+ * block) and how far the drag reaches past them (`dropReach`, the
66
+ * `--edytor-drop-reach` the root inherits).
67
+ */
68
+ gutter(root: HTMLElement): {
69
+ start: number;
70
+ end: number;
71
+ handles: number;
72
+ reach: number;
73
+ };
74
+ /** Whether `x` is in the handle column left of the editor's blocks (`gutter`): the page target's. */
75
+ inHandleColumn(x: number): boolean;
76
+ /**
77
+ * A placement in the editor's margins at the pointer (`dnd.reach`), within
78
+ * the editor's height, for the row the content's near edge shows at the
79
+ * pointer's height (`rowAt`: a nested block's own row; over a layout, the
80
+ * row of its first column on the left, of its last on the right, never
81
+ * another), by the zone the pointer is in (`marginAt`):
82
+ * - the handle column left of the blocks (`gutter`): that row's reorder
83
+ * (`reorder`): a drag straight down the handles reorders, row after
84
+ * row, gaps included (never a beside band);
85
+ * - the first `BESIDE_MARGIN` px past it on the left, and past the
86
+ * content's right edge: the beside band of that side (`beside`, as if
87
+ * the pointer were at that edge) when the document has a layout kind
88
+ * and the row offers one (a refused band shows nothing);
89
+ * - elsewhere within the reach (and there without a band): the reorder,
90
+ * on the left the handle column's, on the right the one the row's
91
+ * right edge offers (its halves as just inside the content, so nothing
92
+ * jumps as the pointer leaves it).
93
+ * `undefined` past the reach, above or below the editor, and inside the
94
+ * content (the blocks' own targets answer there).
95
+ */
96
+ margin(drag: Drag, { clientX: x, clientY: y }: Input): DropPlacement | undefined;
97
+ /**
98
+ * The halves of `row` (`zones`) at the pointer, the one it is in first:
99
+ * the top half of `rect` (its own row by default) before, the bottom one
100
+ * after; with `plain`, only before or after a block — never inside one.
101
+ */
102
+ halves(drag: Drag, row: Block, input: Input, rect: DOMRect, plain?: boolean): DropPlacement[][];
103
+ /**
104
+ * The first placement of `halves` the document takes (a refused one gives
105
+ * way to the next, then to the other half's), `blocked` when the first
106
+ * half took none; `null` when none fits.
107
+ */
108
+ pick(drag: Drag, halves: DropPlacement[][]): DropPlacement | null;
109
+ /**
110
+ * The handle column's reorder over `row` at the pointer: its plain halves
111
+ * (`halves`, the pointer's x picking the level); `null` when neither half
112
+ * fits. Over an item of a container the dragged blocks do not fit in (a
113
+ * paragraph over a list's item: the document refuses every placement at
114
+ * the item's level), the container is the row: its whole box, the upper
115
+ * half before it, the lower half after it (round 4), so a drag down the
116
+ * handles meets the list as one row it can pass.
117
+ */
118
+ reorder(drag: Drag, row: Block, input: Input): DropPlacement | null;
119
+ /**
120
+ * The block whose row the pointer is on over `node`: the last shown drop
121
+ * target in it whose row starts at or above the pointer (beside a nested
122
+ * block, over its ancestors' indent, the pointer is on that block's
123
+ * row), else `target`, over its own row (`emptied`). A layout's columns
124
+ * sit side by side: only the one under the pointer is measured (between
125
+ * two of them, the layout is the row: its gap).
126
+ */
127
+ rowAt(drag: Drag, target: Block, node: HTMLElement, { clientX, clientY }: Input): Block;
128
+ /** Whether the document has a layout kind: only then are there beside placements. */
129
+ get layouts(): boolean;
130
+ /**
131
+ * The block a beside placement over `block` stands beside: the document's
132
+ * own resolution (`facade.besideAt`, `placeBeside`'s): a list item's list,
133
+ * a code line's code block, a toggle's child itself, an item itself.
134
+ */
135
+ besideOf(block: Block): Block;
136
+ /** The item (and its layout) a beside placement at `outer` (`besideOf`) adds a column beside. */
137
+ layoutOf(outer: Block): {
138
+ item: Block;
139
+ layout: Block | null;
140
+ } | null;
141
+ /**
142
+ * Whether `x` is over a column other than one holding `block`: a sticky
143
+ * target never holds the pointer across a column boundary.
144
+ */
145
+ overOtherColumn(block: Block, x: number): boolean;
146
+ /**
147
+ * Notion's beside bands over `row` (`docs/columns-plan.md`, "View layer"), only while the
148
+ * pointer is within its own row's height, the document has a layout kind
149
+ * and the layout the drop makes or grows does not stack (`stacks`):
150
+ * - right: the row's last 15% (at least 32px), and the slop past it;
151
+ * - left: the slop left of the row (its sticky margin, left of its text
152
+ * column), only for a row whose parent is the root or a column (a
153
+ * list's item: its list's) — a nested row keeps the pointer's x for its
154
+ * levels (`zones`) — and only
155
+ * inside the editor (a column's gap): left of the editor's blocks is the
156
+ * handle column, which reorders (`margin`);
157
+ * - over a layout's gap (the row is the layout): right of the column
158
+ * before it, a new column between.
159
+ * `undefined` outside the bands (the hitbox's halves stand), else the
160
+ * placement, or `null` when the document refuses it: a refused band shows
161
+ * nothing.
162
+ */
163
+ beside(drag: Drag, row: Block, { clientX: x, clientY: y }: Input, band?: 'left' | 'right'): DropPlacement | null | undefined;
164
+ /** Where the editor's blocks start (`gutter`), or `fallback` without a host. */
165
+ inside(fallback: number): number;
166
+ /** The children a block shows: none for a closed toggle's, or a container's items. */
167
+ shown(block: Block): Block[];
168
+ /**
169
+ * Over a dragged block, the parent it leaves when that parent shows no
170
+ * other child: the gap under the parent's own row, which reparents as
171
+ * the parent's lower half does (`zones`). Else `row`.
172
+ */
173
+ emptied(drag: Drag, row: Block): Block;
174
+ /**
175
+ * The placements over `row`, each half's in preference order (a refused
176
+ * one gives way to the next, then to the other half's):
177
+ * - the top half: before it;
178
+ * - the bottom half of a block whose children show (not a closed toggle):
179
+ * its first child's slot. Pragmatic drag and drop's tree rule: an
180
+ * expanded item offers no "after" on its own row;
181
+ * - else, after it at the level whose text column is nearest the pointer:
182
+ * the last block of a nested group reparents to any ancestor it ends,
183
+ * down to the root. Past one nesting step right of its text start,
184
+ * inside it (its last child) first. A block every shown child of which
185
+ * is dragged is not expanded: its levels, never inside it when the drag
186
+ * moves only its children (they would stay where they are).
187
+ * None over a dragged block's own row or its subtree: released there, the
188
+ * drag changes nothing (its levels would outdent it) — unless it empties
189
+ * its parent's shown children (`emptied`): that parent's row. Over the
190
+ * hint of the row's empty body (`body.hint`: an open toggle's, a
191
+ * callout's), inside it first, either half.
192
+ */
193
+ zones(drag: Drag, row: Block, { clientX, clientY }: Input): {
194
+ before: DropPlacement[];
195
+ after: DropPlacement[];
196
+ };
197
+ /** The dragged block that is `row` or holds it: its row offers nothing (it moves with the drag). */
198
+ carrier(drag: Drag, row: Block): Block | undefined;
199
+ }
200
+ export {};