@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
@@ -1,19 +1,17 @@
1
1
  import type { AnyCommandDescriptor } from '../../../std/index.js';
2
2
  import { z } from 'zod';
3
3
  export declare const reorderElementParams: z.ZodObject<{
4
- /** The element or gfx block to move. */
5
- id: z.ZodString;
6
- /**
7
- * The model it lands directly ABOVE, stacked with it; `null` puts it at the
8
- * bottom of its stack.
9
- */
4
+ id: z.ZodOptional<z.ZodString>;
5
+ ids: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
10
6
  above: z.ZodNullable<z.ZodString>;
11
7
  }, "strip", z.ZodTypeAny, {
12
- id: string;
13
8
  above: string | null;
9
+ id?: string | undefined;
10
+ ids?: string[] | undefined;
14
11
  }, {
15
- id: string;
16
12
  above: string | null;
13
+ id?: string | undefined;
14
+ ids?: string[] | undefined;
17
15
  }>;
18
16
  export type ReorderElementParams = z.infer<typeof reorderElementParams>;
19
17
  export declare const lockElementsParams: z.ZodObject<{
@@ -1,7 +1,7 @@
1
1
  import { CanvasLocalVisibility, SelectionPaneProvider, TelemetryProvider, } from '../../../affine-shared/services/index.js';
2
2
  import { GfxControllerIdentifier } from '../../../std/gfx/index.js';
3
3
  import { z } from 'zod';
4
- import { renamePaneFrame, renamePaneGroup, reorderPaneElement, setPaneElementsHiddenForEveryone, setPaneElementsLocked, } from './actions.js';
4
+ import { renamePaneFrame, renamePaneGroup, reorderPaneElements, setPaneElementsHiddenForEveryone, setPaneElementsLocked, } from './actions.js';
5
5
  import { setUserLayersHiddenForEveryone } from '../user-layers/actions.js';
6
6
  import { SelectionPaneModel } from './tree.js';
7
7
  /**
@@ -68,13 +68,20 @@ const toggleSelectionPane = {
68
68
  });
69
69
  },
70
70
  };
71
+ /*
72
+ * "An `id` or a non-empty `ids`" is checked in `run`, not with a zod `refine`:
73
+ * a refined schema is a `ZodEffects`, which `describeCommandParams` cannot
74
+ * project, so the agent manifest would lose the whole parameter contract.
75
+ */
71
76
  export const reorderElementParams = z.object({
72
- /** The element or gfx block to move. */
73
- id: z.string().min(1),
74
- /**
75
- * The model it lands directly ABOVE, stacked with it; `null` puts it at the
76
- * bottom of its stack.
77
- */
77
+ // The element or gfx block to move.
78
+ id: z.string().min(1).optional(),
79
+ // Several models of ONE stack, moved as a block in their current relative
80
+ // order (the selection pane's multi-row drag, ADR 0034). Added beside `id`,
81
+ // which stays: a caller passing `id` alone is unchanged.
82
+ ids: z.array(z.string().min(1)).optional(),
83
+ // The model they land directly ABOVE, stacked with them; `null` puts them
84
+ // at the bottom of their stack.
78
85
  above: z.string().min(1).nullable(),
79
86
  });
80
87
  const reorderElement = {
@@ -97,7 +104,13 @@ const reorderElement = {
97
104
  console.error('canvas.element.reorder: invalid params', parsed.error);
98
105
  return;
99
106
  }
100
- reorderPaneElement(std, parsed.data.id, parsed.data.above);
107
+ const { id, ids = [], above } = parsed.data;
108
+ const moving = id === undefined ? ids : [id, ...ids];
109
+ if (!moving.length) {
110
+ console.error('canvas.element.reorder: needs an id or ids');
111
+ return;
112
+ }
113
+ reorderPaneElements(std, moving, above);
101
114
  },
102
115
  };
103
116
  export const lockElementsParams = z.object({
@@ -1,3 +1,3 @@
1
- export { filterSelectionPaneTree, renamePaneFrame, renamePaneGroup, reorderPaneElement, type SelectionPaneFilterTarget, selectionPaneFilterMembers, selectionPaneFilterTargets, setPaneElementsHiddenForEveryone, setPaneElementsLocked, } from './actions.js';
1
+ export { filterSelectionPaneTree, renamePaneFrame, renamePaneGroup, reorderPaneElement, reorderPaneElements, type SelectionPaneFilterTarget, selectionPaneFilterMembers, selectionPaneFilterTargets, setPaneElementsHiddenForEveryone, setPaneElementsLocked, } from './actions.js';
2
2
  export { type HideForEveryoneParams, hideForEveryoneParams, type HideLocalParams, hideLocalParams, type LockElementsParams, lockElementsParams, type RenameGroupParams, renameGroupParams, type ReorderElementParams, reorderElementParams, selectionPaneCommands, } from './commands.js';
3
3
  export { buildSelectionPaneTree, DEFAULT_LAYER_ID, paneContainerOf, paneLayersOf, type SelectionPaneLayers, type SelectionPaneNode, SelectionPaneModel, selectionPaneTree, } from './tree.js';
@@ -1,3 +1,3 @@
1
- export { filterSelectionPaneTree, renamePaneFrame, renamePaneGroup, reorderPaneElement, selectionPaneFilterMembers, selectionPaneFilterTargets, setPaneElementsHiddenForEveryone, setPaneElementsLocked, } from './actions.js';
1
+ export { filterSelectionPaneTree, renamePaneFrame, renamePaneGroup, reorderPaneElement, reorderPaneElements, selectionPaneFilterMembers, selectionPaneFilterTargets, setPaneElementsHiddenForEveryone, setPaneElementsLocked, } from './actions.js';
2
2
  export { hideForEveryoneParams, hideLocalParams, lockElementsParams, renameGroupParams, reorderElementParams, selectionPaneCommands, } from './commands.js';
3
3
  export { buildSelectionPaneTree, DEFAULT_LAYER_ID, paneContainerOf, paneLayersOf, SelectionPaneModel, selectionPaneTree, } from './tree.js';
@@ -17,7 +17,7 @@ import { LitElement, type TemplateResult } from 'lit';
17
17
  */
18
18
  export declare class Portal extends LitElement {
19
19
  private _portalRoot;
20
- createRenderRoot(): HTMLDivElement | ShadowRoot;
20
+ createRenderRoot(): ShadowRoot | HTMLDivElement;
21
21
  disconnectedCallback(): void;
22
22
  render(): TemplateResult | undefined;
23
23
  accessor container: HTMLElement;
@@ -23,13 +23,13 @@ declare const optionsSchema: z.ZodObject<{
23
23
  style: z.ZodString;
24
24
  }, "strip", z.ZodTypeAny, {
25
25
  style: string;
26
- font: string;
27
26
  url: string;
27
+ font: string;
28
28
  weight: string;
29
29
  }, {
30
30
  style: string;
31
- font: string;
32
31
  url: string;
32
+ font: string;
33
33
  weight: string;
34
34
  }>, "many">>;
35
35
  telemetry: z.ZodOptional<z.ZodType<TelemetryService, z.ZodTypeDef, TelemetryService>>;
@@ -42,8 +42,8 @@ declare const optionsSchema: z.ZodObject<{
42
42
  } | undefined;
43
43
  fontConfig?: {
44
44
  style: string;
45
- font: string;
46
45
  url: string;
46
+ font: string;
47
47
  weight: string;
48
48
  }[] | undefined;
49
49
  telemetry?: TelemetryService | undefined;
@@ -56,8 +56,8 @@ declare const optionsSchema: z.ZodObject<{
56
56
  } | undefined;
57
57
  fontConfig?: {
58
58
  style: string;
59
- font: string;
60
59
  url: string;
60
+ font: string;
61
61
  weight: string;
62
62
  }[] | undefined;
63
63
  telemetry?: TelemetryService | undefined;
@@ -87,13 +87,13 @@ export declare class FoundationViewExtension extends ViewExtensionProvider<Found
87
87
  style: z.ZodString;
88
88
  }, "strip", z.ZodTypeAny, {
89
89
  style: string;
90
- font: string;
91
90
  url: string;
91
+ font: string;
92
92
  weight: string;
93
93
  }, {
94
94
  style: string;
95
- font: string;
96
95
  url: string;
96
+ font: string;
97
97
  weight: string;
98
98
  }>, "many">>;
99
99
  telemetry: z.ZodOptional<z.ZodType<TelemetryService, z.ZodTypeDef, TelemetryService>>;
@@ -106,8 +106,8 @@ export declare class FoundationViewExtension extends ViewExtensionProvider<Found
106
106
  } | undefined;
107
107
  fontConfig?: {
108
108
  style: string;
109
- font: string;
110
109
  url: string;
110
+ font: string;
111
111
  weight: string;
112
112
  }[] | undefined;
113
113
  telemetry?: TelemetryService | undefined;
@@ -120,8 +120,8 @@ export declare class FoundationViewExtension extends ViewExtensionProvider<Found
120
120
  } | undefined;
121
121
  fontConfig?: {
122
122
  style: string;
123
- font: string;
124
123
  url: string;
124
+ font: string;
125
125
  weight: string;
126
126
  }[] | undefined;
127
127
  telemetry?: TelemetryService | undefined;
@@ -11,8 +11,15 @@ export declare class FramePanelBody extends FramePanelBody_base {
11
11
  */
12
12
  private readonly _clickBlank;
13
13
  private _docDisposables;
14
- private _frameElementHeight;
15
14
  private _frameItems;
15
+ /** The side panels' one reorder gesture (ADR 0034), made on connect. */
16
+ private _reorder;
17
+ /** The card a press landed on: what a pick-up picks up. */
18
+ private _pressed;
19
+ /** The dragged cards' copies following the pointer, top one last. */
20
+ private _ghosts;
21
+ /** The list's `gap`, read once at the pick-up: the drop line sits in it. */
22
+ private _listGap;
16
23
  private _indicatorTranslateY;
17
24
  private _lastEdgelessRootId;
18
25
  private get _gfx();
@@ -20,10 +27,36 @@ export declare class FramePanelBody extends FramePanelBody_base {
20
27
  private readonly _updateFrameItems;
21
28
  get frames(): FrameBlockModel[];
22
29
  get viewportPadding(): [number, number, number, number];
30
+ private _createReorderDrag;
31
+ /** The cards on screen, in presentation order. */
32
+ private _cards;
33
+ /** A press on a card: the controller decides whether it becomes a drag. */
23
34
  private _drag;
35
+ /**
36
+ * The press became a drag. The pressed card is selected if it was not
37
+ * (shift adds it to the selection), and every selected card moves: their
38
+ * copies — the last two, the top one counting them — follow the pointer,
39
+ * and the cards themselves stay in place as placeholders.
40
+ */
41
+ private _pickUp;
42
+ /**
43
+ * A release at the gap `gap` puts the selected cards right before the first
44
+ * card at or after it that does not move, or at the end. The line sits in
45
+ * the middle of the list's gap: above the card under the gap, or under the
46
+ * last card — the frame panel's own drag used to draw that last one at 0.
47
+ */
48
+ private _dropAt;
24
49
  private _fitToElement;
25
50
  private _renderEmptyContent;
26
51
  private _renderFrameList;
52
+ /**
53
+ * Move the selected cards, as one block, right before the frame `before`
54
+ * (`null` = at the end; `_dropAt` picks it). The write is
55
+ * `canvas.frame.reorder` (ADR 0034): the read-only refusal, the no-op check
56
+ * and the one undo step live in its action, run through the imported
57
+ * descriptor so the panel reorders in page mode too, where the command is
58
+ * not registered.
59
+ */
27
60
  private _reorderFrames;
28
61
  private _setDocDisposables;
29
62
  private _updateFrames;
@@ -34,7 +67,6 @@ export declare class FramePanelBody extends FramePanelBody_base {
34
67
  updated(_changedProperties: PropertyValues): void;
35
68
  private accessor _dragging;
36
69
  private accessor _selected;
37
- accessor domHost: Document | HTMLElement;
38
70
  accessor editorHost: EditorHost;
39
71
  accessor fitPadding: number[];
40
72
  accessor frameListContainer: HTMLElement;
@@ -32,20 +32,35 @@ var __runInitializers = (this && this.__runInitializers) || function (thisArg, i
32
32
  }
33
33
  return useValue ? value : void 0;
34
34
  };
35
- import { EdgelessFrameManager } from '../../affine-block-frame/index.js';
35
+ import { EdgelessFrameManager, frameCommands } from '../../affine-block-frame/index.js';
36
36
  import { DocModeProvider, EditPropsStore, translateKey, } from '../../affine-shared/services/index.js';
37
+ import { createPanelReorderDrag, } from '../../affine-shared/utils/index.js';
37
38
  import { DisposableGroup } from '../../global/disposable/index.js';
38
39
  import { Bound } from '../../global/gfx/index.js';
39
40
  import { SignalWatcher, WithDisposable } from '../../global/lit/index.js';
40
- import { ShadowlessElement } from '../../std/index.js';
41
- import { generateKeyBetweenV2, GfxControllerIdentifier } from '../../std/gfx/index.js';
41
+ import { runCommand, ShadowlessElement, } from '../../std/index.js';
42
+ import { GfxControllerIdentifier } from '../../std/gfx/index.js';
42
43
  import { css, html, nothing } from 'lit';
43
44
  import { property, query, state } from 'lit/decorators.js';
44
45
  import { keyed } from 'lit/directives/keyed.js';
45
46
  import { repeat } from 'lit/directives/repeat.js';
47
+ import { FrameCard, } from '../card/frame-card.js';
46
48
  import { FRAME_PANEL_EMPTY_PLACEHOLDER } from '../translations.js';
47
- import { startDragging } from '../utils/drag.js';
48
49
  const compare = EdgelessFrameManager.framePresentationComparator;
50
+ /**
51
+ * How the panel invokes the registry: from the editor's chrome, on the cards
52
+ * it names, like the selection pane's rows.
53
+ */
54
+ const PANEL_INVOCATION = {
55
+ surface: 'contextual-toolbar',
56
+ source: 'toolbar:general',
57
+ };
58
+ function reorderCommand() {
59
+ const command = frameCommands.find(c => c.id === 'canvas.frame.reorder');
60
+ if (!command)
61
+ throw new Error('frame panel: no canvas.frame.reorder');
62
+ return command;
63
+ }
49
64
  const styles = css `
50
65
  .frame-list-container {
51
66
  display: flex;
@@ -89,6 +104,19 @@ const styles = css `
89
104
  width: 284px;
90
105
  left: 0;
91
106
  }
107
+
108
+ /*
109
+ The drag mask, created by the drag controller (createPanelReorderDrag):
110
+ over the whole viewport for the length of the drag, so its cursor — set
111
+ inline by the controller, "grabbing" or "not-allowed" — speaks wherever
112
+ the pointer is. The dragged cards are appended after it, so they paint
113
+ over it.
114
+ */
115
+ .frame-panel-drag-mask {
116
+ position: fixed;
117
+ inset: 0;
118
+ z-index: calc(var(--affine-z-index-popover, 0) + 3);
119
+ }
92
120
  `;
93
121
  export const AFFINE_FRAME_PANEL_BODY = 'affine-frame-panel-body';
94
122
  let FramePanelBody = (() => {
@@ -99,9 +127,6 @@ let FramePanelBody = (() => {
99
127
  let __selected_decorators;
100
128
  let __selected_initializers = [];
101
129
  let __selected_extraInitializers = [];
102
- let _domHost_decorators;
103
- let _domHost_initializers = [];
104
- let _domHost_extraInitializers = [];
105
130
  let _editorHost_decorators;
106
131
  let _editorHost_initializers = [];
107
132
  let _editorHost_extraInitializers = [];
@@ -119,14 +144,12 @@ let FramePanelBody = (() => {
119
144
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
120
145
  __dragging_decorators = [state()];
121
146
  __selected_decorators = [state()];
122
- _domHost_decorators = [property({ attribute: false })];
123
147
  _editorHost_decorators = [property({ attribute: false })];
124
148
  _fitPadding_decorators = [property({ attribute: false })];
125
149
  _frameListContainer_decorators = [query('.frame-list-container')];
126
150
  _insertIndex_decorators = [property({ attribute: false })];
127
151
  __esDecorate(this, null, __dragging_decorators, { kind: "accessor", name: "_dragging", static: false, private: false, access: { has: obj => "_dragging" in obj, get: obj => obj._dragging, set: (obj, value) => { obj._dragging = value; } }, metadata: _metadata }, __dragging_initializers, __dragging_extraInitializers);
128
152
  __esDecorate(this, null, __selected_decorators, { kind: "accessor", name: "_selected", static: false, private: false, access: { has: obj => "_selected" in obj, get: obj => obj._selected, set: (obj, value) => { obj._selected = value; } }, metadata: _metadata }, __selected_initializers, __selected_extraInitializers);
129
- __esDecorate(this, null, _domHost_decorators, { kind: "accessor", name: "domHost", static: false, private: false, access: { has: obj => "domHost" in obj, get: obj => obj.domHost, set: (obj, value) => { obj.domHost = value; } }, metadata: _metadata }, _domHost_initializers, _domHost_extraInitializers);
130
153
  __esDecorate(this, null, _editorHost_decorators, { kind: "accessor", name: "editorHost", static: false, private: false, access: { has: obj => "editorHost" in obj, get: obj => obj.editorHost, set: (obj, value) => { obj.editorHost = value; } }, metadata: _metadata }, _editorHost_initializers, _editorHost_extraInitializers);
131
154
  __esDecorate(this, null, _fitPadding_decorators, { kind: "accessor", name: "fitPadding", static: false, private: false, access: { has: obj => "fitPadding" in obj, get: obj => obj.fitPadding, set: (obj, value) => { obj.fitPadding = value; } }, metadata: _metadata }, _fitPadding_initializers, _fitPadding_extraInitializers);
132
155
  __esDecorate(this, null, _frameListContainer_decorators, { kind: "accessor", name: "frameListContainer", static: false, private: false, access: { has: obj => "frameListContainer" in obj, get: obj => obj.frameListContainer, set: (obj, value) => { obj.frameListContainer = value; } }, metadata: _metadata }, _frameListContainer_initializers, _frameListContainer_extraInitializers);
@@ -148,53 +171,129 @@ let FramePanelBody = (() => {
148
171
  ? [0, 0, 0, 0].map((val, idx) => Number.isFinite(this.fitPadding[idx]) ? this.fitPadding[idx] : val)
149
172
  : [0, 0, 0, 0];
150
173
  }
151
- _drag(e) {
152
- if (!this._selected.length)
153
- return;
154
- this._dragging = true;
155
- const framesMap = this._frameItems.reduce((map, frame) => {
156
- map.set(frame.frame.id, {
157
- ...frame,
158
- });
159
- return map;
160
- }, new Map());
161
- const selected = this._selected.slice();
162
- const draggedFramesInfo = selected.map(id => {
163
- const frame = framesMap.get(id);
164
- return {
165
- frame: frame.frame,
166
- element: this.renderRoot.querySelector(`[data-frame-id="${frame.frame.id}"]`),
167
- cardIndex: frame.cardIndex,
168
- frameIndex: frame.frameIndex,
169
- };
170
- });
171
- const width = draggedFramesInfo[0].element.clientWidth;
172
- this._frameElementHeight = draggedFramesInfo[0].element.offsetHeight;
173
- startDragging(draggedFramesInfo, {
174
- width,
175
- container: this,
176
- document: this.ownerDocument,
177
- domHost: this.domHost ?? this.ownerDocument,
178
- start: {
179
- x: e.detail.clientX,
180
- y: e.detail.clientY,
174
+ /* ── Drag to reorder ────────────────────────────────────────────────── */
175
+ /*
176
+ * The gesture is the side panels' one controller (`createPanelReorderDrag`,
177
+ * `@labre/affine-shared/utils`, ADR 0034), shared with the selection pane:
178
+ * pointer events, the 5px threshold, the read-only refusal, the mask and
179
+ * its cursor, Escape and `pointercancel` as cancels, the swallowed click.
180
+ * What is the panel's own is below: the selected cards move together, their
181
+ * copies follow the pointer, a line marks the gap, and the write is
182
+ * `canvas.frame.reorder`.
183
+ */
184
+ _createReorderDrag() {
185
+ return createPanelReorderDrag({
186
+ readonly: () => this.editorHost.store.readonly,
187
+ rows: () => this._cards(),
188
+ bounds: () => this,
189
+ onPickUp: press => this._pickUp(press),
190
+ dropAt: gap => this._dropAt(gap),
191
+ onMove: (drop, point) => {
192
+ for (const ghost of this._ghosts) {
193
+ ghost.pos = { x: point.clientX, y: point.clientY };
194
+ }
195
+ this._indicatorTranslateY = drop?.lineY ?? 0;
196
+ this.insertIndex = drop?.gap;
197
+ this.requestUpdate();
181
198
  },
182
- framePanelBody: this,
183
- frameListContainer: this.frameListContainer,
184
- frameElementHeight: this._frameElementHeight,
185
- onDragEnd: insertIdx => {
199
+ onDrop: drop => this._reorderFrames(this._selected.slice(), drop.before),
200
+ onEnd: () => {
201
+ this._ghosts.forEach(ghost => ghost.remove());
202
+ this._ghosts = [];
203
+ this._pressed = null;
186
204
  this._dragging = false;
187
205
  this.insertIndex = undefined;
188
- if (insertIdx === undefined || this._frameItems.length <= 1)
189
- return;
190
- this._reorderFrames(selected, framesMap, insertIdx);
206
+ this._updateFrames();
191
207
  },
192
- onDragMove: (idx, indicatorTranslateY) => {
193
- this.insertIndex = idx;
194
- this._indicatorTranslateY = indicatorTranslateY ?? 0;
208
+ mask: {
209
+ host: () => this,
210
+ className: 'frame-panel-drag-mask',
211
+ testId: 'frame-panel-drag-mask',
195
212
  },
196
213
  });
197
214
  }
215
+ /** The cards on screen, in presentation order. */
216
+ _cards() {
217
+ const list = this.frameListContainer;
218
+ if (!list)
219
+ return [];
220
+ return Array.from(list.querySelectorAll(':scope > affine-frame-card'));
221
+ }
222
+ /** A press on a card: the controller decides whether it becomes a drag. */
223
+ _drag(e) {
224
+ const { event } = e.detail;
225
+ this._pressed = {
226
+ card: e.currentTarget,
227
+ shiftKey: event.shiftKey,
228
+ };
229
+ this._reorder?.press(event);
230
+ }
231
+ /**
232
+ * The press became a drag. The pressed card is selected if it was not
233
+ * (shift adds it to the selection), and every selected card moves: their
234
+ * copies — the last two, the top one counting them — follow the pointer,
235
+ * and the cards themselves stay in place as placeholders.
236
+ */
237
+ _pickUp(press) {
238
+ const card = this._pressed?.card;
239
+ if (!card?.frame)
240
+ return;
241
+ const id = card.frame.id;
242
+ if (!this._selected.includes(id)) {
243
+ this._selected = this._pressed?.shiftKey ? [...this._selected, id] : [id];
244
+ this._gfx.selection.set({ elements: this._selected, editing: false });
245
+ }
246
+ const list = this.frameListContainer;
247
+ this._listGap = list ? parseFloat(getComputedStyle(list).gap) || 0 : 0;
248
+ const items = new Map(this._frameItems.map(item => [item.frame.id, item]));
249
+ const dragged = this._selected.flatMap(selected => {
250
+ const item = items.get(selected);
251
+ return item ? [item] : [];
252
+ });
253
+ const width = this.renderRoot.querySelector(`[data-frame-id="${dragged[0]?.frame.id}"]`)?.clientWidth ?? card.clientWidth;
254
+ this._ghosts = dragged.slice(-2).map((item, idx, arr) => {
255
+ const ghost = new FrameCard();
256
+ ghost.frame = item.frame;
257
+ ghost.cardIndex = item.cardIndex;
258
+ ghost.frameIndex = item.frameIndex;
259
+ ghost.status = 'dragging';
260
+ ghost.stackOrder = arr.length - 1 - idx;
261
+ ghost.pos = { x: press.clientX, y: press.clientY };
262
+ ghost.width = width;
263
+ ghost.std = this.editorHost.std;
264
+ if (ghost.stackOrder === 0) {
265
+ ghost.dataset.testid = 'frame-panel-drag-ghost';
266
+ if (dragged.length > 1)
267
+ ghost.draggingCardNumber = dragged.length;
268
+ }
269
+ return ghost;
270
+ });
271
+ this.renderRoot.append(...this._ghosts);
272
+ this._dragging = true;
273
+ }
274
+ /**
275
+ * A release at the gap `gap` puts the selected cards right before the first
276
+ * card at or after it that does not move, or at the end. The line sits in
277
+ * the middle of the list's gap: above the card under the gap, or under the
278
+ * last card — the frame panel's own drag used to draw that last one at 0.
279
+ */
280
+ _dropAt(gap) {
281
+ const cards = this._cards();
282
+ const last = cards[cards.length - 1];
283
+ if (!last)
284
+ return null;
285
+ const moving = new Set(this._selected);
286
+ const before = cards
287
+ .slice(gap)
288
+ .map(card => card.frame?.id)
289
+ .find(id => id !== undefined && !moving.has(id)) ?? null;
290
+ const half = this._listGap / 2;
291
+ const under = cards[gap];
292
+ const lineY = under
293
+ ? under.offsetTop - half
294
+ : last.offsetTop + last.offsetHeight + half;
295
+ return { gap, before, lineY };
296
+ }
198
297
  _fitToElement(e) {
199
298
  const { block } = e.detail;
200
299
  const bound = Bound.deserialize(block.xywh);
@@ -246,6 +345,7 @@ let FramePanelBody = (() => {
246
345
  ${this.insertIndex !== undefined
247
346
  ? html `<div
248
347
  class="insert-indicator"
348
+ data-testid="frame-panel-drop-indicator"
249
349
  style=${`transform: translateY(${this._indicatorTranslateY}px)`}
250
350
  ></div>`
251
351
  : nothing}
@@ -253,37 +353,41 @@ let FramePanelBody = (() => {
253
353
  </div>`;
254
354
  return frameList;
255
355
  }
256
- _reorderFrames(selected, framesMap, insertIndex) {
257
- if (insertIndex >= 0 && insertIndex <= this._frameItems.length) {
258
- const frames = Array.from(framesMap.values()).map(frameItem => frameItem.frame);
259
- const selectedFrames = selected
260
- .map(id => framesMap.get(id))
261
- .map(frameItem => frameItem.frame)
262
- .sort(compare);
263
- // update selected frames index
264
- // make the indexes larger than the frame before and smaller than the frame after
265
- let before = frames[insertIndex - 1]?.props.presentationIndex || null;
266
- const after = frames[insertIndex]?.props.presentationIndex || null;
267
- selectedFrames.forEach(frame => {
268
- const newIndex = generateKeyBetweenV2(before, after);
269
- frame.store.updateBlock(frame, {
270
- presentationIndex: newIndex,
271
- });
272
- before = newIndex;
273
- });
274
- this.editorHost.store.captureSync();
275
- this._updateFrames();
276
- }
356
+ /**
357
+ * Move the selected cards, as one block, right before the frame `before`
358
+ * (`null` = at the end; `_dropAt` picks it). The write is
359
+ * `canvas.frame.reorder` (ADR 0034): the read-only refusal, the no-op check
360
+ * and the one undo step live in its action, run through the imported
361
+ * descriptor so the panel reorders in page mode too, where the command is
362
+ * not registered.
363
+ */
364
+ _reorderFrames(selected, before) {
365
+ if (!selected.length)
366
+ return;
367
+ runCommand(this.editorHost.std, reorderCommand(), PANEL_INVOCATION, {
368
+ ids: selected,
369
+ before,
370
+ });
371
+ this._updateFrames();
277
372
  }
278
373
  _setDocDisposables(doc) {
279
374
  this._clearDocDisposables();
280
375
  this._docDisposables = new DisposableGroup();
281
- this._docDisposables.add(doc.slots.blockUpdated.subscribe(({ type, flavour }) => {
282
- if (flavour === 'affine:frame' && type !== 'update') {
283
- requestAnimationFrame(() => {
284
- this._updateFrames();
285
- });
376
+ this._docDisposables.add(doc.slots.blockUpdated.subscribe(payload => {
377
+ if (payload.flavour !== 'affine:frame')
378
+ return;
379
+ // A frame added or deleted changes the cards; of the updates, only a
380
+ // new presentation order does. The panel is not the order's only
381
+ // writer — an undo, a host's slide panel running
382
+ // `canvas.frame.reorder`, a remote peer — and it went stale on all
383
+ // three until reopened.
384
+ if (payload.type === 'update' &&
385
+ payload.props.key !== 'presentationIndex') {
386
+ return;
286
387
  }
388
+ requestAnimationFrame(() => {
389
+ this._updateFrames();
390
+ });
287
391
  }));
288
392
  }
289
393
  _updateFrames() {
@@ -316,10 +420,13 @@ let FramePanelBody = (() => {
316
420
  connectedCallback() {
317
421
  super.connectedCallback();
318
422
  this._updateFrameItems();
423
+ this._reorder = this._createReorderDrag();
319
424
  }
320
425
  disconnectedCallback() {
321
426
  super.disconnectedCallback();
322
427
  this._clearDocDisposables();
428
+ this._reorder?.dispose();
429
+ this._reorder = null;
323
430
  }
324
431
  firstUpdated() {
325
432
  const disposables = this.disposables;
@@ -354,9 +461,6 @@ let FramePanelBody = (() => {
354
461
  // Store the ids of the selected frames
355
462
  get _selected() { return this.#_selected_accessor_storage; }
356
463
  set _selected(value) { this.#_selected_accessor_storage = value; }
357
- #domHost_accessor_storage;
358
- get domHost() { return this.#domHost_accessor_storage; }
359
- set domHost(value) { this.#domHost_accessor_storage = value; }
360
464
  #editorHost_accessor_storage;
361
465
  get editorHost() { return this.#editorHost_accessor_storage; }
362
466
  set editorHost(value) { this.#editorHost_accessor_storage = value; }
@@ -392,11 +496,21 @@ let FramePanelBody = (() => {
392
496
  });
393
497
  };
394
498
  this._docDisposables = null;
395
- this._frameElementHeight = 0;
396
499
  this._frameItems = [];
500
+ /** The side panels' one reorder gesture (ADR 0034), made on connect. */
501
+ this._reorder = null;
502
+ /** The card a press landed on: what a pick-up picks up. */
503
+ this._pressed = null;
504
+ /** The dragged cards' copies following the pointer, top one last. */
505
+ this._ghosts = [];
506
+ /** The list's `gap`, read once at the pick-up: the drop line sits in it. */
507
+ this._listGap = 0;
397
508
  this._indicatorTranslateY = 0;
398
509
  this._lastEdgelessRootId = '';
399
510
  this._selectFrame = (e) => {
511
+ // A drag ends with a click on the card it started on; that click is not one.
512
+ if (this._reorder?.consumeSwallowedClick())
513
+ return;
400
514
  const { selected, id, multiselect } = e.detail;
401
515
  if (!selected) {
402
516
  // de-select frame
@@ -422,8 +536,7 @@ let FramePanelBody = (() => {
422
536
  };
423
537
  this.#_dragging_accessor_storage = __runInitializers(this, __dragging_initializers, false);
424
538
  this.#_selected_accessor_storage = (__runInitializers(this, __dragging_extraInitializers), __runInitializers(this, __selected_initializers, []));
425
- this.#domHost_accessor_storage = (__runInitializers(this, __selected_extraInitializers), __runInitializers(this, _domHost_initializers, void 0));
426
- this.#editorHost_accessor_storage = (__runInitializers(this, _domHost_extraInitializers), __runInitializers(this, _editorHost_initializers, void 0));
539
+ this.#editorHost_accessor_storage = (__runInitializers(this, __selected_extraInitializers), __runInitializers(this, _editorHost_initializers, void 0));
427
540
  this.#fitPadding_accessor_storage = (__runInitializers(this, _editorHost_extraInitializers), __runInitializers(this, _fitPadding_initializers, void 0));
428
541
  this.#frameListContainer_accessor_storage = (__runInitializers(this, _fitPadding_extraInitializers), __runInitializers(this, _frameListContainer_initializers, void 0));
429
542
  this.#insertIndex_accessor_storage = (__runInitializers(this, _frameListContainer_extraInitializers), __runInitializers(this, _insertIndex_initializers, undefined));
@@ -11,11 +11,9 @@ export type SelectEvent = CustomEvent<{
11
11
  index: number;
12
12
  multiselect: boolean;
13
13
  }>;
14
+ /** A press on a card's body, handed to the panel's drag controller. */
14
15
  export type DragEvent = CustomEvent<{
15
- clientX: number;
16
- clientY: number;
17
- pageX: number;
18
- pageY: number;
16
+ event: PointerEvent;
19
17
  }>;
20
18
  export type FitViewEvent = CustomEvent<{
21
19
  block: FrameBlockModel;
@@ -24,6 +22,11 @@ export declare const AFFINE_FRAME_CARD = "affine-frame-card";
24
22
  declare const FrameCard_base: typeof ShadowlessElement & import("../../global/utils/types.js").Constructor<import("../../global/lit/with-disposable.js").DisposableClass>;
25
23
  export declare class FrameCard extends FrameCard_base {
26
24
  static styles: import("lit").CSSResult;
25
+ /**
26
+ * Hand the press to the panel body: the threshold, the selection and the
27
+ * drag itself belong to the side panels' one controller, which the body
28
+ * holds (`createPanelReorderDrag`, ADR 0034).
29
+ */
27
30
  private _dispatchDragEvent;
28
31
  private _dispatchFitViewEvent;
29
32
  private _dispatchSelectEvent;