@formicoidea/labre-core 0.45.0 → 0.46.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 (65) hide show
  1. package/dist/_pkgs/affine-block-callout/commands/turn-callout-into.d.ts +43 -0
  2. package/dist/_pkgs/affine-block-callout/commands/turn-callout-into.js +109 -0
  3. package/dist/_pkgs/affine-block-callout/configs/toolbar.d.ts +65 -0
  4. package/dist/_pkgs/affine-block-callout/configs/toolbar.js +178 -0
  5. package/dist/_pkgs/affine-block-callout/view.js +7 -1
  6. package/dist/_pkgs/affine-block-frame/commands.d.ts +27 -0
  7. package/dist/_pkgs/affine-block-frame/commands.js +49 -0
  8. package/dist/_pkgs/affine-block-frame/frame-manager.d.ts +11 -6
  9. package/dist/_pkgs/affine-block-frame/frame-manager.js +10 -5
  10. package/dist/_pkgs/affine-block-frame/frame-order.d.ts +43 -0
  11. package/dist/_pkgs/affine-block-frame/frame-order.js +93 -0
  12. package/dist/_pkgs/affine-block-frame/index.d.ts +2 -0
  13. package/dist/_pkgs/affine-block-frame/index.js +2 -0
  14. package/dist/_pkgs/affine-block-frame/present/frame-order-menu.d.ts +10 -2
  15. package/dist/_pkgs/affine-block-frame/present/frame-order-menu.js +32 -18
  16. package/dist/_pkgs/affine-block-frame/view.js +6 -0
  17. package/dist/_pkgs/affine-block-note/configs/toolbar.d.ts +19 -0
  18. package/dist/_pkgs/affine-block-note/configs/toolbar.js +38 -23
  19. package/dist/_pkgs/affine-block-note/translations.d.ts +2 -0
  20. package/dist/_pkgs/affine-block-note/translations.js +14 -1
  21. package/dist/_pkgs/affine-block-root/configs/toolbar.js +31 -2
  22. package/dist/_pkgs/affine-block-root/translations.d.ts +7 -2
  23. package/dist/_pkgs/affine-block-root/translations.js +10 -13
  24. package/dist/_pkgs/affine-block-surface/extensions/selection-pane/actions.d.ts +20 -9
  25. package/dist/_pkgs/affine-block-surface/extensions/selection-pane/actions.js +57 -20
  26. package/dist/_pkgs/affine-block-surface/extensions/selection-pane/commands.d.ts +6 -8
  27. package/dist/_pkgs/affine-block-surface/extensions/selection-pane/commands.js +21 -8
  28. package/dist/_pkgs/affine-block-surface/extensions/selection-pane/index.d.ts +1 -1
  29. package/dist/_pkgs/affine-block-surface/extensions/selection-pane/index.js +1 -1
  30. package/dist/_pkgs/affine-components/portal/portal.d.ts +1 -1
  31. package/dist/_pkgs/affine-foundation/view.d.ts +8 -8
  32. package/dist/_pkgs/affine-fragment-frame-panel/body/frame-panel-body.d.ts +34 -2
  33. package/dist/_pkgs/affine-fragment-frame-panel/body/frame-panel-body.js +194 -81
  34. package/dist/_pkgs/affine-fragment-frame-panel/card/frame-card.d.ts +7 -4
  35. package/dist/_pkgs/affine-fragment-frame-panel/card/frame-card.js +9 -24
  36. package/dist/_pkgs/affine-fragment-frame-panel/index.d.ts +1 -0
  37. package/dist/_pkgs/affine-fragment-frame-panel/index.js +1 -0
  38. package/dist/_pkgs/affine-model/utils/types.d.ts +2 -2
  39. package/dist/_pkgs/affine-shared/services/edit-props-store.d.ts +4 -4
  40. package/dist/_pkgs/affine-shared/services/font-loader/config.d.ts +2 -2
  41. package/dist/_pkgs/affine-shared/services/font-loader/font-loader-service.d.ts +3 -3
  42. package/dist/_pkgs/affine-shared/services/index.d.ts +1 -1
  43. package/dist/_pkgs/affine-shared/services/index.js +1 -1
  44. package/dist/_pkgs/affine-shared/services/link-preview-service/link-preview-storage.d.ts +4 -4
  45. package/dist/_pkgs/affine-shared/services/outline-panel-service.d.ts +31 -0
  46. package/dist/_pkgs/affine-shared/services/outline-panel-service.js +17 -0
  47. package/dist/_pkgs/affine-shared/services/translation-service/chrome.d.ts +11 -0
  48. package/dist/_pkgs/affine-shared/services/translation-service/chrome.js +19 -0
  49. package/dist/_pkgs/affine-shared/utils/index.d.ts +1 -0
  50. package/dist/_pkgs/affine-shared/utils/index.js +1 -0
  51. package/dist/_pkgs/affine-shared/utils/panel-reorder-drag.d.ts +85 -0
  52. package/dist/_pkgs/affine-shared/utils/panel-reorder-drag.js +162 -0
  53. package/dist/_pkgs/affine-widget-edgeless-toolbar/selection-pane/drop.d.ts +7 -6
  54. package/dist/_pkgs/affine-widget-edgeless-toolbar/selection-pane/drop.js +11 -8
  55. package/dist/_pkgs/affine-widget-edgeless-toolbar/selection-pane/selection-pane-widget.d.ts +31 -15
  56. package/dist/_pkgs/affine-widget-edgeless-toolbar/selection-pane/selection-pane-widget.js +178 -142
  57. package/dist/_pkgs/std/scope/std-scope.d.ts +1 -1
  58. package/dist/commands.js +2 -0
  59. package/dist/host-panels.d.ts +90 -0
  60. package/dist/host-panels.js +111 -0
  61. package/package.json +5 -1
  62. package/dist/_pkgs/affine-fragment-frame-panel/utils/drag.d.ts +0 -27
  63. package/dist/_pkgs/affine-fragment-frame-panel/utils/drag.js +0 -96
  64. package/dist/_pkgs/affine-shared/services/sidebar-service.d.ts +0 -8
  65. package/dist/_pkgs/affine-shared/services/sidebar-service.js +0 -7
@@ -24,8 +24,9 @@ export declare const EDGELESS_SELECTION_PANE_WIDGET = "edgeless-selection-pane-w
24
24
  * (shift / ctrl / cmd adds or removes it, like on the canvas), the padlock
25
25
  * locks that row alone, a double-click renames a group or a frame (a frame is
26
26
  * a row at its place in the stack, ADR 0031 amendments), and a drag moves a row
27
- * among its siblings. Every write goes through `runCommand`, so the read-only
28
- * refusal and the undo step live in one place.
27
+ * — or, pressed on a selected row, the whole selection of one stack — among
28
+ * its siblings (the side panels' one drag, ADR 0034). Every write goes through
29
+ * `runCommand`, so the read-only refusal and the undo step live in one place.
29
30
  */
30
31
  export declare class EdgelessSelectionPaneWidget extends WidgetComponent<RootBlockModel> {
31
32
  static styles: import("lit").CSSResult;
@@ -36,19 +37,22 @@ export declare class EdgelessSelectionPaneWidget extends WidgetComponent<RootBlo
36
37
  private accessor _filter;
37
38
  /** The group row being renamed, or `null`. */
38
39
  private accessor _renaming;
39
- /** Where the dragged row would land. */
40
+ /** Where the dragged rows would land; `null` while refused. */
40
41
  private accessor _drop;
41
42
  /** Where the dragged row's ghost is drawn, in the panel's own box. */
42
43
  private accessor _ghost;
43
44
  /** Bumped when the canvas selection changes, so the rows repaint. */
44
45
  private accessor _selectionRevision;
45
- private _drag;
46
+ /** The drag under way, from its pick-up to its end. */
47
+ private accessor _drag;
48
+ /** The last row pressed: what a pick-up picks up. */
49
+ private _pressed;
50
+ /** The side panels' one reorder gesture, wired in `_wire()`. */
51
+ private _reorder;
46
52
  /** The rows of the last render, top first: what a drag hit-tests. */
47
53
  private _visibleRows;
48
54
  /** The sibling lists before the filter: what a drop is computed in. */
49
55
  private _lists;
50
- /** A drag ends with a click on the row it started on; that click is not one. */
51
- private _swallowNextClick;
52
56
  get paneOpen(): boolean;
53
57
  private get _gfx();
54
58
  private _setOpen;
@@ -109,7 +113,21 @@ export declare class EdgelessSelectionPaneWidget extends WidgetComponent<RootBlo
109
113
  private _onRowContextMenu;
110
114
  private _commitRename;
111
115
  private readonly _onRenameKeydown;
116
+ private _createReorderDrag;
117
+ private _panel;
118
+ private _paneBody;
119
+ /** The rows on screen, top first, with the rows they draw. */
120
+ private _shownRows;
112
121
  private _onRowPointerDown;
122
+ /**
123
+ * The press became a drag. A canvas row picked up is selected, as the frame
124
+ * panel selects a card — unless it already is, and then the whole selection
125
+ * moves with it, as in the frame panel: every selected row the pane lists.
126
+ * Rows that are not all in the pressed row's stack and list have no one
127
+ * place to land, so every drop of theirs is refused, honestly, with a
128
+ * `not-allowed` cursor rather than a line that would write nothing.
129
+ */
130
+ private _pickUp;
113
131
  /**
114
132
  * Which model stands for a row in the dragged row's own stack — the stack
115
133
  * `canvas.element.reorder` restacks it in, the models sharing its raw
@@ -119,20 +137,18 @@ export declare class EdgelessSelectionPaneWidget extends WidgetComponent<RootBlo
119
137
  * mindmap node moves with its mindmap).
120
138
  */
121
139
  private _stackOf;
122
- /** Where a release at `(clientX, clientY)` would land the dragged row. */
140
+ /**
141
+ * Where a release over the gap `gap` (the controller's count of rows whose
142
+ * middle is above the pointer, at `clientY`) would land the dragged rows,
143
+ * or `null` to refuse it.
144
+ */
123
145
  private _dropAt;
124
- private _moveGhost;
125
- private readonly _onDocumentPointerMove;
126
- /** A canvas row picked up is selected, as the frame panel selects a card. */
127
- private _pickUp;
128
- private readonly _onDocumentPointerUp;
129
146
  /**
130
147
  * One write per gesture, through the command: `layer` for a drop into a
131
- * layer, one `index` for a slot. The command writes nothing when the slot
132
- * is where the row already is, and a refused drop never reaches it.
148
+ * layer, one restack for a slot. The command writes nothing when the slot
149
+ * is where the rows already are, and a refused drop never reaches it.
133
150
  */
134
151
  private _commitDrop;
135
- private _endDrag;
136
152
  private _renderGhost;
137
153
  private _renderDropIndicator;
138
154
  private _filterName;
@@ -36,7 +36,8 @@ import { CanvasActiveLayer, EDITOR_ANCHORED_PANEL_Z_INDEX, filterSelectionPaneTr
36
36
  import { menu, popMenu, popupTargetFromElement, } from '../../affine-components/context-menu/index.js';
37
37
  import { FrameBlockModel, MindmapElementModel, } from '../../affine-model/index.js';
38
38
  import { TOOLBAR_LOCK, TOOLBAR_RENAME, translateKey, } from '../../affine-shared/services/index.js';
39
- import { panelDragStarted, panelHeaderStyles, } from '../../affine-shared/styles/index.js';
39
+ import { panelHeaderStyles } from '../../affine-shared/styles/index.js';
40
+ import { createPanelReorderDrag, } from '../../affine-shared/utils/index.js';
40
41
  import { runCommand, WidgetComponent, WidgetViewExtension, } from '../../std/index.js';
41
42
  import { DEFAULT_LAYER_ID, GfxBlockElementModel, GfxControllerIdentifier, } from '../../std/gfx/index.js';
42
43
  import { ArrowDownSmallIcon, ArrowRightSmallIcon, CloseIcon, DeleteIcon, FilterIcon, InvisibleIcon, LayerIcon, LockIcon, MoreHorizontalIcon, PlusIcon, UnlockIcon, ViewIcon, } from '@blocksuite/icons/lit';
@@ -100,8 +101,9 @@ function rawGroupOf(model) {
100
101
  * (shift / ctrl / cmd adds or removes it, like on the canvas), the padlock
101
102
  * locks that row alone, a double-click renames a group or a frame (a frame is
102
103
  * a row at its place in the stack, ADR 0031 amendments), and a drag moves a row
103
- * among its siblings. Every write goes through `runCommand`, so the read-only
104
- * refusal and the undo step live in one place.
104
+ * — or, pressed on a selected row, the whole selection of one stack — among
105
+ * its siblings (the side panels' one drag, ADR 0034). Every write goes through
106
+ * `runCommand`, so the read-only refusal and the undo step live in one place.
105
107
  */
106
108
  let EdgelessSelectionPaneWidget = (() => {
107
109
  let _classSuper = WidgetComponent;
@@ -126,6 +128,9 @@ let EdgelessSelectionPaneWidget = (() => {
126
128
  let __selectionRevision_decorators;
127
129
  let __selectionRevision_initializers = [];
128
130
  let __selectionRevision_extraInitializers = [];
131
+ let __drag_decorators;
132
+ let __drag_initializers = [];
133
+ let __drag_extraInitializers = [];
129
134
  return class EdgelessSelectionPaneWidget extends _classSuper {
130
135
  constructor() {
131
136
  super(...arguments);
@@ -136,13 +141,15 @@ let EdgelessSelectionPaneWidget = (() => {
136
141
  this.#_drop_accessor_storage = (__runInitializers(this, __renaming_extraInitializers), __runInitializers(this, __drop_initializers, null));
137
142
  this.#_ghost_accessor_storage = (__runInitializers(this, __drop_extraInitializers), __runInitializers(this, __ghost_initializers, null));
138
143
  this.#_selectionRevision_accessor_storage = (__runInitializers(this, __ghost_extraInitializers), __runInitializers(this, __selectionRevision_initializers, 0));
139
- this._drag = (__runInitializers(this, __selectionRevision_extraInitializers), null);
144
+ this.#_drag_accessor_storage = (__runInitializers(this, __selectionRevision_extraInitializers), __runInitializers(this, __drag_initializers, null));
145
+ /** The last row pressed: what a pick-up picks up. */
146
+ this._pressed = (__runInitializers(this, __drag_extraInitializers), null);
147
+ /** The side panels' one reorder gesture, wired in `_wire()`. */
148
+ this._reorder = null;
140
149
  /** The rows of the last render, top first: what a drag hit-tests. */
141
150
  this._visibleRows = [];
142
151
  /** The sibling lists before the filter: what a drop is computed in. */
143
152
  this._lists = paneLists([]);
144
- /** A drag ends with a click on the row it started on; that click is not one. */
145
- this._swallowNextClick = false;
146
153
  /** Put the pane away. Writes nothing but its own state. */
147
154
  this.closePanel = () => {
148
155
  this._setOpen(false);
@@ -158,9 +165,8 @@ let EdgelessSelectionPaneWidget = (() => {
158
165
  if (this._renaming)
159
166
  return;
160
167
  event.stopPropagation();
161
- // The frame panel's drag has no cancel: Escape waits for the release.
162
- if (this._drag?.dragging)
163
- return;
168
+ // Escape during a drag never gets here: the drag controller cancels the
169
+ // drag and stops it in the capture phase, so the pane stays open.
164
170
  this.closePanel();
165
171
  };
166
172
  /** Same capture as the catalogue: a wheel over the pane scrolls the pane. */
@@ -186,33 +192,6 @@ let EdgelessSelectionPaneWidget = (() => {
186
192
  this._renaming = null;
187
193
  }
188
194
  };
189
- this._onDocumentPointerMove = (event) => {
190
- const drag = this._drag;
191
- if (!drag)
192
- return;
193
- if (!drag.dragging) {
194
- if (!panelDragStarted(drag, { x: event.clientX, y: event.clientY })) {
195
- return;
196
- }
197
- drag.dragging = true;
198
- this._pickUp(drag);
199
- }
200
- this._moveGhost(event);
201
- this._drop = this._dropAt(event.clientX, event.clientY);
202
- };
203
- this._onDocumentPointerUp = (event) => {
204
- const drag = this._drag;
205
- if (drag?.dragging) {
206
- this._swallowNextClick = true;
207
- // A click only follows when the press and the release land on the same
208
- // row; otherwise nothing would ever clear the flag.
209
- setTimeout(() => (this._swallowNextClick = false), 0);
210
- const drop = this._dropAt(event.clientX, event.clientY);
211
- if (drop)
212
- this._commitDrop(drag, drop);
213
- }
214
- this._endDrag();
215
- };
216
195
  this._openFilterMenu = (event) => {
217
196
  const targets = selectionPaneFilterTargets(this.std);
218
197
  popMenu(popupTargetFromElement(event.currentTarget), {
@@ -258,6 +237,7 @@ let EdgelessSelectionPaneWidget = (() => {
258
237
  __drop_decorators = [state()];
259
238
  __ghost_decorators = [state()];
260
239
  __selectionRevision_decorators = [state()];
240
+ __drag_decorators = [state()];
261
241
  __esDecorate(this, null, __open_decorators, { kind: "accessor", name: "_open", static: false, private: false, access: { has: obj => "_open" in obj, get: obj => obj._open, set: (obj, value) => { obj._open = value; } }, metadata: _metadata }, __open_initializers, __open_extraInitializers);
262
242
  __esDecorate(this, null, __collapsed_decorators, { kind: "accessor", name: "_collapsed", static: false, private: false, access: { has: obj => "_collapsed" in obj, get: obj => obj._collapsed, set: (obj, value) => { obj._collapsed = value; } }, metadata: _metadata }, __collapsed_initializers, __collapsed_extraInitializers);
263
243
  __esDecorate(this, null, __filter_decorators, { kind: "accessor", name: "_filter", static: false, private: false, access: { has: obj => "_filter" in obj, get: obj => obj._filter, set: (obj, value) => { obj._filter = value; } }, metadata: _metadata }, __filter_initializers, __filter_extraInitializers);
@@ -265,6 +245,7 @@ let EdgelessSelectionPaneWidget = (() => {
265
245
  __esDecorate(this, null, __drop_decorators, { kind: "accessor", name: "_drop", static: false, private: false, access: { has: obj => "_drop" in obj, get: obj => obj._drop, set: (obj, value) => { obj._drop = value; } }, metadata: _metadata }, __drop_initializers, __drop_extraInitializers);
266
246
  __esDecorate(this, null, __ghost_decorators, { kind: "accessor", name: "_ghost", static: false, private: false, access: { has: obj => "_ghost" in obj, get: obj => obj._ghost, set: (obj, value) => { obj._ghost = value; } }, metadata: _metadata }, __ghost_initializers, __ghost_extraInitializers);
267
247
  __esDecorate(this, null, __selectionRevision_decorators, { kind: "accessor", name: "_selectionRevision", static: false, private: false, access: { has: obj => "_selectionRevision" in obj, get: obj => obj._selectionRevision, set: (obj, value) => { obj._selectionRevision = value; } }, metadata: _metadata }, __selectionRevision_initializers, __selectionRevision_extraInitializers);
248
+ __esDecorate(this, null, __drag_decorators, { kind: "accessor", name: "_drag", static: false, private: false, access: { has: obj => "_drag" in obj, get: obj => obj._drag, set: (obj, value) => { obj._drag = value; } }, metadata: _metadata }, __drag_initializers, __drag_extraInitializers);
268
249
  if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
269
250
  }
270
251
  static { this.styles = css `
@@ -372,8 +353,8 @@ let EdgelessSelectionPaneWidget = (() => {
372
353
  color: var(--affine-primary-color);
373
354
  }
374
355
 
375
- /* The row being dragged stays in place, dimmed, like the frame panel's
376
- placeholder card; its ghost follows the pointer. */
356
+ /* The rows being dragged stay in place, dimmed, like the frame panel's
357
+ placeholder cards; the pressed row's ghost follows the pointer. */
377
358
  .selection-pane-row[data-dragging] {
378
359
  opacity: 0.5;
379
360
  }
@@ -403,10 +384,10 @@ let EdgelessSelectionPaneWidget = (() => {
403
384
  }
404
385
 
405
386
  /*
406
- The frame panel's drag mask: over the whole viewport for the length of
407
- the drag, so the cursor says "grabbing" or "not-allowed" wherever the
408
- pointer is (the panel's rule above hands it down) and the canvas under
409
- it takes no hover.
387
+ The drag mask, created by the drag controller (createPanelReorderDrag):
388
+ over the whole viewport for the length of the drag, so the cursor —
389
+ set inline by the controller, "grabbing" or "not-allowed" — speaks
390
+ wherever the pointer is, and the canvas under it takes no hover.
410
391
  */
411
392
  .selection-pane-drag-mask {
412
393
  position: fixed;
@@ -439,6 +420,24 @@ let EdgelessSelectionPaneWidget = (() => {
439
420
  flex: initial;
440
421
  }
441
422
 
423
+ /* How many rows move with the ghost: the frame panel's card count. */
424
+ .selection-pane-drag-count {
425
+ flex: none;
426
+ margin-left: auto;
427
+ display: flex;
428
+ align-items: center;
429
+ justify-content: center;
430
+ min-width: 20px;
431
+ height: 20px;
432
+ padding: 0 4px;
433
+ box-sizing: border-box;
434
+ border-radius: 10px;
435
+ background: var(--affine-black);
436
+ color: var(--affine-white);
437
+ font-size: 12px;
438
+ line-height: 20px;
439
+ }
440
+
442
441
  /* A row dragged onto a layer moves into it (ADR 0031 §5). */
443
442
  .selection-pane-row[data-drop='into'] {
444
443
  outline: 2px solid var(--affine-primary-color);
@@ -581,7 +580,7 @@ let EdgelessSelectionPaneWidget = (() => {
581
580
  get _renaming() { return this.#_renaming_accessor_storage; }
582
581
  set _renaming(value) { this.#_renaming_accessor_storage = value; }
583
582
  #_drop_accessor_storage;
584
- /** Where the dragged row would land. */
583
+ /** Where the dragged rows would land; `null` while refused. */
585
584
  get _drop() { return this.#_drop_accessor_storage; }
586
585
  set _drop(value) { this.#_drop_accessor_storage = value; }
587
586
  #_ghost_accessor_storage;
@@ -592,6 +591,10 @@ let EdgelessSelectionPaneWidget = (() => {
592
591
  /** Bumped when the canvas selection changes, so the rows repaint. */
593
592
  get _selectionRevision() { return this.#_selectionRevision_accessor_storage; }
594
593
  set _selectionRevision(value) { this.#_selectionRevision_accessor_storage = value; }
594
+ #_drag_accessor_storage;
595
+ /** The drag under way, from its pick-up to its end. */
596
+ get _drag() { return this.#_drag_accessor_storage; }
597
+ set _drag(value) { this.#_drag_accessor_storage = value; }
595
598
  get paneOpen() {
596
599
  return this._open;
597
600
  }
@@ -619,11 +622,15 @@ let EdgelessSelectionPaneWidget = (() => {
619
622
  const subscription = this._gfx.selection.slots.updated.subscribe(() => {
620
623
  this._selectionRevision++;
621
624
  });
625
+ const reorder = this._createReorderDrag();
626
+ this._reorder = reorder;
622
627
  this._disposables.add(() => {
623
628
  host.removeEventListener('keydown', this._onHostKeydown, true);
624
629
  host.removeEventListener('wheel', this._onHostWheel, true);
625
630
  subscription.unsubscribe();
626
- this._endDrag();
631
+ reorder.dispose();
632
+ if (this._reorder === reorder)
633
+ this._reorder = null;
627
634
  });
628
635
  }
629
636
  connectedCallback() {
@@ -700,10 +707,9 @@ let EdgelessSelectionPaneWidget = (() => {
700
707
  this._collapsed = next;
701
708
  }
702
709
  _onRowClick(event, node) {
703
- if (this._swallowNextClick) {
704
- this._swallowNextClick = false;
710
+ // A drag ends with a click on the row it started on; that click is not one.
711
+ if (this._reorder?.consumeSwallowedClick())
705
712
  return;
706
- }
707
713
  // A layer row is not on the canvas: a click makes it the active layer,
708
714
  // where this viewer's new elements land (session only, never stored).
709
715
  if (node.kind === 'layer') {
@@ -832,31 +838,106 @@ let EdgelessSelectionPaneWidget = (() => {
832
838
  }
833
839
  /* ── Drag to reorder ────────────────────────────────────────────────── */
834
840
  /*
835
- * The frame panel's model (`fragments/frame-panel`): a press becomes a drag
836
- * past its threshold and picks the row up, selecting it; the row stays in
837
- * place, dimmed; a copy of it at its own width follows the pointer; a mask
838
- * over the viewport carries the cursor; a line marks the gap a release would
839
- * land in, and a gap the row cannot go to shows no line and a `not-allowed`
840
- * cursor. Escape does nothing until the release. A read-only document never
841
- * starts one.
841
+ * The gesture is the side panels' one controller (`createPanelReorderDrag`,
842
+ * `@labre/affine-shared/utils`, ADR 0034), shared with the frame panel:
843
+ * threshold, read-only refusal, mask and cursor, Escape and `pointercancel`
844
+ * as cancels, the swallowed click. What is the pane's own is below: which
845
+ * rows move (the pressed row, or the whole selection of one stack), where a
846
+ * gap lands them in their stack, the ghost and the line, and the write.
842
847
  */
848
+ _createReorderDrag() {
849
+ return createPanelReorderDrag({
850
+ readonly: () => this.std.store.readonly,
851
+ rows: () => this._shownRows().map(({ element }) => element),
852
+ bounds: () => this._paneBody(),
853
+ onPickUp: () => this._pickUp(),
854
+ dropAt: (gap, point) => this._dropAt(gap, point.clientY),
855
+ onMove: (drop, point) => {
856
+ this._drop = drop;
857
+ const panel = this._panel();
858
+ if (!panel)
859
+ return;
860
+ const box = panel.getBoundingClientRect();
861
+ this._ghost = {
862
+ x: point.clientX - box.left,
863
+ y: point.clientY - box.top,
864
+ };
865
+ },
866
+ onDrop: drop => this._commitDrop(drop),
867
+ onEnd: () => {
868
+ this._drag = null;
869
+ this._drop = null;
870
+ this._ghost = null;
871
+ },
872
+ mask: {
873
+ host: () => this._panel(),
874
+ className: 'selection-pane-drag-mask',
875
+ testId: 'selection-pane-drag-mask',
876
+ },
877
+ stateHost: () => this._panel(),
878
+ });
879
+ }
880
+ _panel() {
881
+ return (this.shadowRoot?.querySelector('[data-testid="selection-pane-panel"]') ?? null);
882
+ }
883
+ _paneBody() {
884
+ return (this.shadowRoot?.querySelector('[data-testid="selection-pane-body"]') ?? null);
885
+ }
886
+ /** The rows on screen, top first, with the rows they draw. */
887
+ _shownRows() {
888
+ const body = this._paneBody();
889
+ if (!body)
890
+ return [];
891
+ const byId = new Map(this._visibleRows.map(row => [row.node.id, row]));
892
+ return Array.from(body.querySelectorAll('[data-testid="selection-pane-row"], [data-testid="selection-pane-layer"]')).flatMap(element => {
893
+ const row = byId.get(element.dataset.id ?? '');
894
+ return row ? [{ element, row }] : [];
895
+ });
896
+ }
843
897
  _onRowPointerDown(event, row) {
844
- if (event.button !== 0 || this.std.store.readonly)
845
- return;
846
898
  const target = event.target;
847
899
  if (target.closest('button, input'))
848
900
  return;
849
- this._drag = {
850
- id: row.node.id,
851
- kind: row.node.kind,
852
- parent: row.parent,
853
- x: event.clientX,
854
- y: event.clientY,
901
+ this._pressed = {
902
+ row,
855
903
  width: event.currentTarget.getBoundingClientRect().width,
856
- dragging: false,
857
904
  };
858
- document.addEventListener('pointermove', this._onDocumentPointerMove, true);
859
- document.addEventListener('pointerup', this._onDocumentPointerUp, true);
905
+ this._reorder?.press(event);
906
+ }
907
+ /**
908
+ * The press became a drag. A canvas row picked up is selected, as the frame
909
+ * panel selects a card — unless it already is, and then the whole selection
910
+ * moves with it, as in the frame panel: every selected row the pane lists.
911
+ * Rows that are not all in the pressed row's stack and list have no one
912
+ * place to land, so every drop of theirs is refused, honestly, with a
913
+ * `not-allowed` cursor rather than a line that would write nothing.
914
+ */
915
+ _pickUp() {
916
+ const pressed = this._pressed;
917
+ if (!pressed)
918
+ return;
919
+ const { node, parent } = pressed.row;
920
+ const drag = {
921
+ id: node.id,
922
+ kind: node.kind,
923
+ parent,
924
+ width: pressed.width,
925
+ moving: new Set([node.id]),
926
+ refused: false,
927
+ };
928
+ if (node.kind !== 'layer') {
929
+ const { selection } = this._gfx;
930
+ if (selection.selectedIds.includes(node.id)) {
931
+ const listed = selection.selectedIds.filter(id => this._lists.parentOf.has(id));
932
+ drag.moving = new Set([node.id, ...listed]);
933
+ const stackOf = this._stackOf(drag);
934
+ drag.refused = [...drag.moving].some(id => this._lists.parentOf.get(id) !== parent || stackOf?.(id) !== id);
935
+ }
936
+ else {
937
+ selection.set({ elements: [node.id], editing: false });
938
+ }
939
+ }
940
+ this._drag = drag;
860
941
  }
861
942
  /**
862
943
  * Which model stands for a row in the dragged row's own stack — the stack
@@ -887,24 +968,16 @@ let EdgelessSelectionPaneWidget = (() => {
887
968
  return undefined;
888
969
  };
889
970
  }
890
- /** Where a release at `(clientX, clientY)` would land the dragged row. */
891
- _dropAt(clientX, clientY) {
971
+ /**
972
+ * Where a release over the gap `gap` (the controller's count of rows whose
973
+ * middle is above the pointer, at `clientY`) would land the dragged rows,
974
+ * or `null` to refuse it.
975
+ */
976
+ _dropAt(gap, clientY) {
892
977
  const drag = this._drag;
893
- const body = this.shadowRoot?.querySelector('[data-testid="selection-pane-body"]');
894
- if (!drag || !body)
978
+ if (!drag || drag.refused)
895
979
  return null;
896
- const box = body.getBoundingClientRect();
897
- if (clientX < box.left ||
898
- clientX > box.right ||
899
- clientY < box.top ||
900
- clientY > box.bottom) {
901
- return { kind: 'refused' };
902
- }
903
- const byId = new Map(this._visibleRows.map(row => [row.node.id, row]));
904
- const shown = Array.from(body.querySelectorAll('[data-testid="selection-pane-row"], [data-testid="selection-pane-layer"]')).flatMap(element => {
905
- const row = byId.get(element.dataset.id ?? '');
906
- return row ? [{ element, row }] : [];
907
- });
980
+ const shown = this._shownRows();
908
981
  // A canvas row over a layer's header moves INTO that layer (§5).
909
982
  if (drag.kind !== 'layer') {
910
983
  const header = shown.find(({ element, row }) => {
@@ -916,23 +989,16 @@ let EdgelessSelectionPaneWidget = (() => {
916
989
  if (header)
917
990
  return { kind: 'into', id: header.row.node.id };
918
991
  }
919
- // The gap: past the middle of a row is below it.
920
- let gap = 0;
921
- for (const { element } of shown) {
922
- const rect = element.getBoundingClientRect();
923
- if (clientY > rect.top + rect.height / 2)
924
- gap++;
925
- }
926
992
  const stackOf = this._stackOf(drag);
927
993
  const slot = stackOf
928
994
  ? paneSlotAtGap(shown.map(({ row }) => row), gap, drag.parent, this._lists)
929
995
  : null;
930
996
  const list = slot ? this._lists.lists.get(slot.parent) : undefined;
931
997
  const landing = slot && list && stackOf
932
- ? paneDropAbove(list, slot.index, drag.id, stackOf)
998
+ ? paneDropAbove(list, slot.index, drag.moving, stackOf)
933
999
  : null;
934
1000
  if (!slot || !landing)
935
- return { kind: 'refused' };
1001
+ return null;
936
1002
  const under = shown[gap]?.element;
937
1003
  const last = shown[shown.length - 1]?.element;
938
1004
  const top = under
@@ -947,57 +1013,30 @@ let EdgelessSelectionPaneWidget = (() => {
947
1013
  left: 8 + slot.depth * INDENT_PX,
948
1014
  };
949
1015
  }
950
- _moveGhost(event) {
951
- const panel = this.shadowRoot?.querySelector('[data-testid="selection-pane-panel"]');
952
- if (!panel)
953
- return;
954
- const box = panel.getBoundingClientRect();
955
- this._ghost = { x: event.clientX - box.left, y: event.clientY - box.top };
956
- }
957
- /** A canvas row picked up is selected, as the frame panel selects a card. */
958
- _pickUp(drag) {
959
- if (drag.kind === 'layer')
960
- return;
961
- const { selection } = this._gfx;
962
- if (selection.selectedIds.includes(drag.id))
963
- return;
964
- selection.set({ elements: [drag.id], editing: false });
965
- }
966
1016
  /**
967
1017
  * One write per gesture, through the command: `layer` for a drop into a
968
- * layer, one `index` for a slot. The command writes nothing when the slot
969
- * is where the row already is, and a refused drop never reaches it.
1018
+ * layer, one restack for a slot. The command writes nothing when the slot
1019
+ * is where the rows already are, and a refused drop never reaches it.
970
1020
  */
971
- _commitDrop(drag, drop) {
972
- if (drop.kind === 'refused')
1021
+ _commitDrop(drop) {
1022
+ const drag = this._drag;
1023
+ if (!drag)
973
1024
  return;
1025
+ const ids = [...drag.moving];
974
1026
  if (drop.kind === 'into') {
975
- this._run('canvas.layer.moveElements', {
976
- ids: [drag.id],
977
- layerId: drop.id,
978
- });
1027
+ this._run('canvas.layer.moveElements', { ids, layerId: drop.id });
979
1028
  return;
980
1029
  }
981
1030
  if (drag.kind === 'layer') {
982
1031
  this._run('canvas.layer.reorder', { id: drag.id, above: drop.above });
983
1032
  return;
984
1033
  }
985
- this._run('canvas.element.reorder', { id: drag.id, above: drop.above });
986
- }
987
- _endDrag() {
988
- document.removeEventListener('pointermove', this._onDocumentPointerMove, true);
989
- document.removeEventListener('pointerup', this._onDocumentPointerUp, true);
990
- const wasDragging = this._drag?.dragging;
991
- this._drag = null;
992
- this._drop = null;
993
- this._ghost = null;
994
- if (wasDragging)
995
- this.requestUpdate();
1034
+ this._run('canvas.element.reorder', { ids, above: drop.above });
996
1035
  }
997
1036
  _renderGhost() {
998
1037
  const drag = this._drag;
999
1038
  const at = this._ghost;
1000
- if (!drag?.dragging || !at)
1039
+ if (!drag || !at)
1001
1040
  return nothing;
1002
1041
  const style = styleMap({
1003
1042
  transform: `translate(${at.x - 16}px, ${at.y - 8}px)`,
@@ -1021,6 +1060,7 @@ let EdgelessSelectionPaneWidget = (() => {
1021
1060
  const type = model instanceof GfxBlockElementModel
1022
1061
  ? model.flavour
1023
1062
  : model.type;
1063
+ const count = drag.moving.size;
1024
1064
  return html `<div
1025
1065
  class="selection-pane-drag-ghost"
1026
1066
  data-testid="selection-pane-drag-ghost"
@@ -1030,11 +1070,18 @@ let EdgelessSelectionPaneWidget = (() => {
1030
1070
  <span class="selection-pane-label"
1031
1071
  >${selectionPaneRowLabel(this.std, model)}</span
1032
1072
  >
1073
+ ${count > 1
1074
+ ? html `<span
1075
+ class="selection-pane-drag-count"
1076
+ data-testid="selection-pane-drag-count"
1077
+ >${count}</span
1078
+ >`
1079
+ : nothing}
1033
1080
  </div>`;
1034
1081
  }
1035
1082
  _renderDropIndicator() {
1036
1083
  const drop = this._drop;
1037
- if (!this._drag?.dragging || drop?.kind !== 'slot')
1084
+ if (!this._drag || drop?.kind !== 'slot')
1038
1085
  return nothing;
1039
1086
  return html `<div
1040
1087
  class="selection-pane-drop-indicator"
@@ -1070,7 +1117,7 @@ let EdgelessSelectionPaneWidget = (() => {
1070
1117
  const drop = this._drop?.kind === 'into' && this._drop.id === node.id
1071
1118
  ? 'into'
1072
1119
  : undefined;
1073
- const dragging = this._drag?.dragging && this._drag.id === node.id;
1120
+ const dragging = this._drag?.moving.has(node.id) ?? false;
1074
1121
  return html `<div
1075
1122
  class="selection-pane-row selection-pane-layer-row"
1076
1123
  role="treeitem"
@@ -1185,7 +1232,7 @@ let EdgelessSelectionPaneWidget = (() => {
1185
1232
  const drop = this._drop?.kind === 'into' && this._drop.id === node.id
1186
1233
  ? 'into'
1187
1234
  : undefined;
1188
- const dragging = this._drag?.dragging && this._drag.id === node.id;
1235
+ const dragging = this._drag?.moving.has(node.id) ?? false;
1189
1236
  return html `<div
1190
1237
  class="selection-pane-row"
1191
1238
  role="treeitem"
@@ -1303,24 +1350,19 @@ let EdgelessSelectionPaneWidget = (() => {
1303
1350
  this._visibleRows = rows;
1304
1351
  this._lists = paneLists(full);
1305
1352
  const title = translateKey(this.std, ...SELECTION_PANE_TITLE);
1306
- const dragState = this._drag?.dragging
1307
- ? this._drop && this._drop.kind !== 'refused'
1308
- ? 'valid'
1309
- : 'invalid'
1310
- : undefined;
1311
1353
  // `pointermove` too: the editor turns every move over the host into a
1312
1354
  // cursor selection, and a selection with nothing recoverable in it makes
1313
1355
  // the range binding focus the host — which blurred a layer's rename field
1314
1356
  // the moment the pointer moved (a group's survived only because its click
1315
1357
  // had selected it). The pane's own drag listens on the document, in the
1316
- // capture phase, so it still sees every move.
1358
+ // capture phase, so it still sees every move. Its `data-drag` and its
1359
+ // mask are the drag controller's, set outside this template.
1317
1360
  return html `<div
1318
1361
  class="selection-pane-panel"
1319
1362
  role="dialog"
1320
1363
  tabindex="-1"
1321
1364
  aria-label=${title}
1322
1365
  data-testid="selection-pane-panel"
1323
- data-drag=${dragState ?? nothing}
1324
1366
  @pointerdown=${this._swallow}
1325
1367
  @pointerup=${this._swallow}
1326
1368
  @pointermove=${this._swallow}
@@ -1366,12 +1408,6 @@ let EdgelessSelectionPaneWidget = (() => {
1366
1408
  </div>`}
1367
1409
  ${this._renderDropIndicator()}
1368
1410
  </div>
1369
- ${this._drag?.dragging
1370
- ? html `<div
1371
- class="selection-pane-drag-mask"
1372
- data-testid="selection-pane-drag-mask"
1373
- ></div>`
1374
- : nothing}
1375
1411
  ${this._renderGhost()}
1376
1412
  </div>`;
1377
1413
  }
@@ -18,7 +18,7 @@ export interface BlockStdOptions {
18
18
  extensions: ExtensionType[];
19
19
  }
20
20
  export declare class BlockStdScope {
21
- static internalExtensions: (typeof ViewStore | typeof UIEventDispatcher | typeof RangeManager | typeof CommandManager | typeof GridManager | typeof LayerManager | typeof ServiceManager | typeof ElementHighlightManager | typeof GfxSelectionManager | typeof SurfaceMiddlewareExtension | typeof ViewManager)[];
21
+ static internalExtensions: (typeof ViewStore | typeof UIEventDispatcher | typeof RangeManager | typeof CommandManager | typeof ElementHighlightManager | typeof GridManager | typeof GfxSelectionManager | typeof SurfaceMiddlewareExtension | typeof ViewManager | typeof LayerManager | typeof ServiceManager)[];
22
22
  readonly container: Container;
23
23
  readonly store: Store;
24
24
  readonly provider: ServiceProvider;
package/dist/commands.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { frameCommands } from './_pkgs/affine-block-frame/index.js';
1
2
  import { copyLinkCommands, coreCommands, pivotCommands, readingCommands, tagCommands, } from './_pkgs/affine-block-root/index.js';
2
3
  import { auditCommands, canvasGridCommands, exportSvgCommands, mapQualityCommands, selectionPaneCommands, userLayerCommands, } from './_pkgs/affine-block-surface/index.js';
3
4
  import { edgeDirectionCommands } from './_pkgs/affine-gfx-connector/index.js';
@@ -55,6 +56,7 @@ export function getCommands(flags) {
55
56
  ...mapQualityCommands,
56
57
  ...exportSvgCommands,
57
58
  ...selectionPaneCommands,
59
+ ...frameCommands,
58
60
  ...canvasGridCommands,
59
61
  ...userLayerCommands,
60
62
  ...edgeDirectionCommands,