@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
@@ -32,8 +32,6 @@ var __runInitializers = (this && this.__runInitializers) || function (thisArg, i
32
32
  }
33
33
  return useValue ? value : void 0;
34
34
  };
35
- import { panelDragStarted } from '../../affine-shared/styles/index.js';
36
- import { on, once } from '../../affine-shared/utils/index.js';
37
35
  import { WithDisposable } from '../../global/lit/index.js';
38
36
  import { ShadowlessElement } from '../../std/index.js';
39
37
  import { css, html, nothing } from 'lit';
@@ -169,32 +167,19 @@ let FrameCard = (() => {
169
167
  if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
170
168
  }
171
169
  static { this.styles = styles; }
170
+ /**
171
+ * Hand the press to the panel body: the threshold, the selection and the
172
+ * drag itself belong to the side panels' one controller, which the body
173
+ * holds (`createPanelReorderDrag`, ADR 0034).
174
+ */
172
175
  _dispatchDragEvent(e) {
173
176
  e.preventDefault();
174
177
  if (e.button !== 0)
175
178
  return;
176
- const { clientX: startX, clientY: startY } = e;
177
- const disposeDragStart = on(this.ownerDocument, 'mousemove', e => {
178
- if (!panelDragStarted({ x: startX, y: startY }, { x: e.clientX, y: e.clientY })) {
179
- return;
180
- }
181
- if (this.status !== 'selected') {
182
- this._dispatchSelectEvent(e);
183
- }
184
- const event = new CustomEvent('drag', {
185
- detail: {
186
- clientX: e.clientX,
187
- clientY: e.clientY,
188
- pageX: e.pageX,
189
- pageY: e.pageY,
190
- },
191
- });
192
- this.dispatchEvent(event);
193
- disposeDragStart();
194
- });
195
- once(this.ownerDocument, 'mouseup', () => {
196
- disposeDragStart();
179
+ const event = new CustomEvent('drag', {
180
+ detail: { event: e },
197
181
  });
182
+ this.dispatchEvent(event);
198
183
  }
199
184
  _dispatchFitViewEvent(e) {
200
185
  e.stopPropagation();
@@ -248,7 +233,7 @@ let FrameCard = (() => {
248
233
  class="frame-card-body"
249
234
  @click=${this._dispatchSelectEvent}
250
235
  @dblclick=${this._dispatchFitViewEvent}
251
- @mousedown=${this._dispatchDragEvent}
236
+ @pointerdown=${this._dispatchDragEvent}
252
237
  >
253
238
  ${this.status === 'dragging' && stackOrder !== 0
254
239
  ? nothing
@@ -1,2 +1,3 @@
1
+ export * from './card/frame-preview.js';
1
2
  export * from './frame-panel.js';
2
3
  export * from './header/frame-panel-header.js';
@@ -1,2 +1,3 @@
1
+ export * from './card/frame-preview.js';
1
2
  export * from './frame-panel.js';
2
3
  export * from './header/frame-panel-header.js';
@@ -10,13 +10,13 @@ export declare const LinkPreviewDataSchema: z.ZodObject<{
10
10
  }, "strip", z.ZodTypeAny, {
11
11
  description: string | null;
12
12
  title: string | null;
13
- image: string | null;
14
13
  icon: string | null;
14
+ image: string | null;
15
15
  }, {
16
16
  description: string | null;
17
17
  title: string | null;
18
- image: string | null;
19
18
  icon: string | null;
19
+ image: string | null;
20
20
  }>;
21
21
  export type LinkPreviewData = z.infer<typeof LinkPreviewDataSchema>;
22
22
  export type Connectable = Exclude<GfxModel, ConnectorElementModel | BrushElementModel | GroupElementModel>;
@@ -24,12 +24,12 @@ declare const LocalPropsSchema: z.ZodObject<{
24
24
  centerY: z.ZodNumber;
25
25
  zoom: z.ZodNumber;
26
26
  }, "strip", z.ZodTypeAny, {
27
- centerX: number;
28
27
  zoom: number;
28
+ centerX: number;
29
29
  centerY: number;
30
30
  }, {
31
- centerX: number;
32
31
  zoom: number;
32
+ centerX: number;
33
33
  centerY: number;
34
34
  }>, z.ZodObject<{
35
35
  xywh: z.ZodString;
@@ -68,8 +68,8 @@ declare const LocalPropsSchema: z.ZodObject<{
68
68
  localShowGrid: z.ZodBoolean;
69
69
  }, "strip", z.ZodTypeAny, {
70
70
  viewport: {
71
- centerX: number;
72
71
  zoom: number;
72
+ centerX: number;
73
73
  centerY: number;
74
74
  } | {
75
75
  xywh: string;
@@ -86,8 +86,8 @@ declare const LocalPropsSchema: z.ZodObject<{
86
86
  localShowGrid: boolean;
87
87
  }, {
88
88
  viewport: {
89
- centerX: number;
90
89
  zoom: number;
90
+ centerX: number;
91
91
  centerY: number;
92
92
  } | {
93
93
  xywh: string;
@@ -6,13 +6,13 @@ export declare const fontConfigSchema: z.ZodObject<{
6
6
  style: z.ZodString;
7
7
  }, "strip", z.ZodTypeAny, {
8
8
  style: string;
9
- font: string;
10
9
  url: string;
10
+ font: string;
11
11
  weight: string;
12
12
  }, {
13
13
  style: string;
14
- font: string;
15
14
  url: string;
15
+ font: string;
16
16
  weight: string;
17
17
  }>;
18
18
  export type FontConfig = z.infer<typeof fontConfigSchema>;
@@ -36,18 +36,18 @@ export declare class FontLoaderService extends LifeCycleWatcher {
36
36
  }
37
37
  export declare const FontConfigIdentifier: import("../../../global/di/types.js").ServiceIdentifier<{
38
38
  style: string;
39
- font: string;
40
39
  url: string;
40
+ font: string;
41
41
  weight: string;
42
42
  }[]> & (<U extends {
43
43
  style: string;
44
- font: string;
45
44
  url: string;
45
+ font: string;
46
46
  weight: string;
47
47
  }[] = {
48
48
  style: string;
49
- font: string;
50
49
  url: string;
50
+ font: string;
51
51
  weight: string;
52
52
  }[]>(variant: import("../../../global/di/types.js").ServiceVariant) => import("../../../global/di/types.js").ServiceIdentifier<U>);
53
53
  export declare const FontConfigExtension: (fontConfig: FontConfig[]) => ExtensionType;
@@ -25,13 +25,13 @@ export * from './linked-doc-creation-service.js';
25
25
  export * from './native-clipboard-service.js';
26
26
  export * from './notification-service.js';
27
27
  export * from './open-doc-config.js';
28
+ export * from './outline-panel-service.js';
28
29
  export * from './page-viewport-service.js';
29
30
  export * from './parse-url-service.js';
30
31
  export * from './pivot-materiality-publisher.js';
31
32
  export * from './pivot-properties-service.js';
32
33
  export * from './quick-search-service.js';
33
34
  export * from './selection-pane-service.js';
34
- export * from './sidebar-service.js';
35
35
  export * from './telemetry-service/index.js';
36
36
  export * from './theme-service.js';
37
37
  export * from './toolbar-service/index.js';
@@ -25,13 +25,13 @@ export * from './linked-doc-creation-service.js';
25
25
  export * from './native-clipboard-service.js';
26
26
  export * from './notification-service.js';
27
27
  export * from './open-doc-config.js';
28
+ export * from './outline-panel-service.js';
28
29
  export * from './page-viewport-service.js';
29
30
  export * from './parse-url-service.js';
30
31
  export * from './pivot-materiality-publisher.js';
31
32
  export * from './pivot-properties-service.js';
32
33
  export * from './quick-search-service.js';
33
34
  export * from './selection-pane-service.js';
34
- export * from './sidebar-service.js';
35
35
  export * from './telemetry-service/index.js';
36
36
  export * from './theme-service.js';
37
37
  export * from './toolbar-service/index.js';
@@ -8,29 +8,29 @@ declare const _StorageSchema: z.ZodObject<{
8
8
  }, "strip", z.ZodTypeAny, {
9
9
  description?: string | null | undefined;
10
10
  title?: string | null | undefined;
11
- image?: string | null | undefined;
12
11
  icon?: string | null | undefined;
12
+ image?: string | null | undefined;
13
13
  }, {
14
14
  description?: string | null | undefined;
15
15
  title?: string | null | undefined;
16
- image?: string | null | undefined;
17
16
  icon?: string | null | undefined;
17
+ image?: string | null | undefined;
18
18
  }>>;
19
19
  expires: z.ZodOptional<z.ZodNumber>;
20
20
  }, "strip", z.ZodTypeAny, {
21
21
  data: Record<string, {
22
22
  description?: string | null | undefined;
23
23
  title?: string | null | undefined;
24
- image?: string | null | undefined;
25
24
  icon?: string | null | undefined;
25
+ image?: string | null | undefined;
26
26
  }>;
27
27
  expires?: number | undefined;
28
28
  }, {
29
29
  data: Record<string, {
30
30
  description?: string | null | undefined;
31
31
  title?: string | null | undefined;
32
- image?: string | null | undefined;
33
32
  icon?: string | null | undefined;
33
+ image?: string | null | undefined;
34
34
  }>;
35
35
  expires?: number | undefined;
36
36
  }>;
@@ -0,0 +1,31 @@
1
+ import type { ExtensionType } from '../../store/index.js';
2
+ /**
3
+ * The outline panel (ADR 0034 §1): the document's table of contents, drawn by
4
+ * the host beside the editor.
5
+ *
6
+ * The library never mounts an outline itself, so there is no default: a host
7
+ * that draws one registers this seam, a host that does not leaves it out. Its
8
+ * only caller in the library is the "View in TOC" link of the toast the note
9
+ * toolbar shows after a note's display mode changes
10
+ * (`blocks/note/src/configs/toolbar.ts`). Absent or `null`, that toast offers
11
+ * no link, because a link that opens nothing is a lie.
12
+ *
13
+ * It replaces the `open(tabId?)` seam inherited from AFFiNE (removed in 0.46,
14
+ * ADR 0034 §2): one caller, which opened a tab named by a string nobody typed
15
+ * anywhere else. Shaped exactly like `SelectionPaneService`, so every panel
16
+ * the library asks a host to show answers the same two verbs.
17
+ */
18
+ export interface OutlinePanelService {
19
+ /** Show the outline. */
20
+ open(): void;
21
+ /** Put it away. Writes nothing. */
22
+ close(): void;
23
+ }
24
+ export declare const OutlinePanelProvider: import("../../global/di/types.js").ServiceIdentifier<OutlinePanelService> & (<U extends OutlinePanelService = OutlinePanelService>(variant: import("../../global/di/types.js").ServiceVariant) => import("../../global/di/types.js").ServiceIdentifier<U>);
25
+ /**
26
+ * Host seam: the host's outline panel, or `null` to say there is none.
27
+ *
28
+ * `di.override`, like `SelectionPaneExtension`, so a later registration always
29
+ * wins and registering twice never throws at boot.
30
+ */
31
+ export declare function OutlinePanelExtension(service: OutlinePanelService | null): ExtensionType;
@@ -0,0 +1,17 @@
1
+ import { createIdentifier } from '../../global/di/index.js';
2
+ export const OutlinePanelProvider = createIdentifier('AffineOutlinePanel');
3
+ /**
4
+ * Host seam: the host's outline panel, or `null` to say there is none.
5
+ *
6
+ * `di.override`, like `SelectionPaneExtension`, so a later registration always
7
+ * wins and registering twice never throws at boot.
8
+ */
9
+ export function OutlinePanelExtension(service) {
10
+ return {
11
+ setup: di => {
12
+ // `null` rides through the factory on purpose: `getOptional` then
13
+ // reports the panel as absent, which is the whole "no link" story.
14
+ di.override(OutlinePanelProvider, () => service);
15
+ },
16
+ };
17
+ }
@@ -464,6 +464,17 @@ export declare const FILL_COLOR_LABEL: ChromeWording;
464
464
  * `ROOT_TOOLBAR_CREATE_LINKED_DOC` (`blocks/root`).
465
465
  */
466
466
  export declare const TOOLBAR_CREATE_LINKED_DOC_TITLE: ChromeWording;
467
+ /**
468
+ * "Turn into" — the block toolbar's conversions dropdown, said identically by
469
+ * the note's text row and the callout's own row (#468). Was
470
+ * `ROOT_TOOLBAR_TURN_INTO` (`blocks/root`); the key is kept.
471
+ */
472
+ export declare const TOOLBAR_TURN_INTO: ChromeWording;
473
+ /**
474
+ * …and that dropdown's accessible name. Was `ROOT_TOOLBAR_CONVERSIONS_ARIA`
475
+ * (`blocks/root`); the key is kept.
476
+ */
477
+ export declare const TOOLBAR_CONVERSIONS_ARIA: ChromeWording;
467
478
  /**
468
479
  * "Edgeless content" — the surface-ref placeholder's own generic reference
469
480
  * kind, said identically by the drag-handle preview's fallback label. Was
@@ -1034,6 +1034,23 @@ export const TOOLBAR_CREATE_LINKED_DOC_TITLE = [
1034
1034
  'com.labre.root.toolbar.create-linked-doc',
1035
1035
  'Create Linked Doc',
1036
1036
  ];
1037
+ /**
1038
+ * "Turn into" — the block toolbar's conversions dropdown, said identically by
1039
+ * the note's text row and the callout's own row (#468). Was
1040
+ * `ROOT_TOOLBAR_TURN_INTO` (`blocks/root`); the key is kept.
1041
+ */
1042
+ export const TOOLBAR_TURN_INTO = [
1043
+ 'com.labre.root.toolbar.turn-into',
1044
+ 'Turn into',
1045
+ ];
1046
+ /**
1047
+ * …and that dropdown's accessible name. Was `ROOT_TOOLBAR_CONVERSIONS_ARIA`
1048
+ * (`blocks/root`); the key is kept.
1049
+ */
1050
+ export const TOOLBAR_CONVERSIONS_ARIA = [
1051
+ 'com.labre.root.toolbar.conversions',
1052
+ 'Conversions',
1053
+ ];
1037
1054
  /**
1038
1055
  * "Edgeless content" — the surface-ref placeholder's own generic reference
1039
1056
  * kind, said identically by the drag-handle preview's fallback label. Was
@@ -1283,6 +1300,8 @@ export const CHROME_WORDINGS = [
1283
1300
  COLOR_LABEL,
1284
1301
  FILL_COLOR_LABEL,
1285
1302
  TOOLBAR_CREATE_LINKED_DOC_TITLE,
1303
+ TOOLBAR_TURN_INTO,
1304
+ TOOLBAR_CONVERSIONS_ARIA,
1286
1305
  EDGELESS_CONTENT_LABEL,
1287
1306
  TOOLBAR_EDIT,
1288
1307
  TOOLBAR_REMOVE_LINK,
@@ -18,6 +18,7 @@ export * from './label-mode.js';
18
18
  export * from './link-preview-refresh.js';
19
19
  export * from './math.js';
20
20
  export * from './model/index.js';
21
+ export * from './panel-reorder-drag.js';
21
22
  export * from './popper-position.js';
22
23
  export * from './print-to-pdf.js';
23
24
  export * from './reference.js';
@@ -18,6 +18,7 @@ export * from './label-mode.js';
18
18
  export * from './link-preview-refresh.js';
19
19
  export * from './math.js';
20
20
  export * from './model/index.js';
21
+ export * from './panel-reorder-drag.js';
21
22
  export * from './popper-position.js';
22
23
  export * from './print-to-pdf.js';
23
24
  export * from './reference.js';
@@ -0,0 +1,85 @@
1
+ /**
2
+ * The ONE drag-to-reorder gesture of the side panels (ADR 0034):
3
+ * the frame panel's cards and the selection pane's rows.
4
+ *
5
+ * Why it exists: the two panels each wrote their own drag and the two
6
+ * drifted. The frame panel's (`fragments/frame-panel/src/utils/drag.ts`, now
7
+ * deleted) ran on mouse events, never refused a read-only document, drew the
8
+ * drop line at 0 for a drop after the last card, and had no way out but the
9
+ * release. The selection pane's (hand-written in `selection-pane-widget.ts`)
10
+ * ran on pointer events, refused read-only and showed a `not-allowed` cursor,
11
+ * and swallowed Escape. Only the threshold was shared, and a comment held the
12
+ * rest together. Both panels now hand this controller what is theirs — the
13
+ * rows, the drop rule, the ghost, the write — and it owns the gesture:
14
+ *
15
+ * - pointer events, listened to on the document in the CAPTURE phase from the
16
+ * press to the end, so a panel that stops its own events' propagation (the
17
+ * pane does, to keep the canvas out of them) still sees every move;
18
+ * - a read-only document never starts one: the press adds no listener;
19
+ * - a press becomes a drag past {@link panelDragStarted} — called here and
20
+ * nowhere else — and only then is anything picked up;
21
+ * - over the whole viewport, a mask carries the cursor: `grabbing` over a gap
22
+ * that takes the drop, `not-allowed` elsewhere (its geometry stays in each
23
+ * panel's stylesheet, the controller sets the cursor and nothing else);
24
+ * - Escape or `pointercancel` CANCELS: nothing is written, the release that
25
+ * follows writes nothing, and the Escape does not reach the panel (which
26
+ * would close it);
27
+ * - the click that follows a release or a cancel is not a click: the panel
28
+ * asks {@link PanelReorderDrag.consumeSwallowedClick} in its click handler.
29
+ *
30
+ * Multi-selection is the panel's business: `onPickUp` decides what moves,
31
+ * `dropAt` skips the rows that move with it.
32
+ */
33
+ /** A pointer position, in client coordinates. */
34
+ export interface PanelDragPoint {
35
+ clientX: number;
36
+ clientY: number;
37
+ }
38
+ export interface PanelReorderDragOptions<Drop> {
39
+ /** Read at the press: a read-only document never starts a drag. */
40
+ readonly: () => boolean;
41
+ /** The rows, top first, read LIVE at every move. */
42
+ rows: () => readonly HTMLElement[];
43
+ /** The box a drop must land in; outside it, or `null`, refuses. */
44
+ bounds: () => HTMLElement | null;
45
+ /** The press became a drag: select what moves, build the ghost. */
46
+ onPickUp: (press: PanelDragPoint) => void;
47
+ /**
48
+ * What a release at the gap `gap` — before `rows()[gap]`, `rows().length`
49
+ * after the last — would do, or `null` to refuse it.
50
+ */
51
+ dropAt: (gap: number, point: PanelDragPoint) => Drop | null;
52
+ /** Every move of a drag: draw the ghost at `ghost`, the line for `drop`. */
53
+ onMove: (drop: Drop | null, ghost: PanelDragPoint) => void;
54
+ /** The release, over a gap that takes it. Before `onEnd`. */
55
+ onDrop: (drop: Drop) => void;
56
+ /** A drag ended — dropped, refused, cancelled or disposed. Always last. */
57
+ onEnd: () => void;
58
+ /** The cursor mask: appended to `host()` on pick-up, removed at the end. */
59
+ mask: {
60
+ host: () => ParentNode | null;
61
+ className: string;
62
+ testId?: string;
63
+ };
64
+ /** Carries `data-drag="valid" | "invalid"` while a drag is live. */
65
+ stateHost?: () => HTMLElement | null;
66
+ }
67
+ export interface PanelReorderDrag {
68
+ /** Hand it the row's `pointerdown`. */
69
+ press(event: PointerEvent): void;
70
+ /**
71
+ * Whether the click being handled follows a drag's release or cancel —
72
+ * answered `true` once, then `false`.
73
+ */
74
+ consumeSwallowedClick(): boolean;
75
+ /** Whether a press has become a drag that has not ended yet. */
76
+ readonly dragging: boolean;
77
+ /** Ends a live drag (`onEnd`, nothing written) and removes the listeners. */
78
+ dispose(): void;
79
+ }
80
+ /**
81
+ * The gap a pointer at `clientY` is over: how many rows have their middle
82
+ * above it. `0` is before the first row, `rects.length` after the last.
83
+ */
84
+ export declare function panelReorderGap(rects: readonly DOMRect[], clientY: number): number;
85
+ export declare function createPanelReorderDrag<Drop>(options: PanelReorderDragOptions<Drop>): PanelReorderDrag;
@@ -0,0 +1,162 @@
1
+ import { panelDragStarted } from '../styles/panel-header.js';
2
+ /**
3
+ * The gap a pointer at `clientY` is over: how many rows have their middle
4
+ * above it. `0` is before the first row, `rects.length` after the last.
5
+ */
6
+ export function panelReorderGap(rects, clientY) {
7
+ let gap = 0;
8
+ for (const rect of rects) {
9
+ if (clientY > rect.top + rect.height / 2)
10
+ gap++;
11
+ }
12
+ return gap;
13
+ }
14
+ export function createPanelReorderDrag(options) {
15
+ let start = null;
16
+ let dragging = false;
17
+ let listening = null;
18
+ let mask = null;
19
+ let swallowClick = false;
20
+ const point = (event) => ({
21
+ clientX: event.clientX,
22
+ clientY: event.clientY,
23
+ });
24
+ const resolve = (at) => {
25
+ const bounds = options.bounds();
26
+ if (!bounds)
27
+ return null;
28
+ const box = bounds.getBoundingClientRect();
29
+ if (at.clientX < box.left ||
30
+ at.clientX > box.right ||
31
+ at.clientY < box.top ||
32
+ at.clientY > box.bottom) {
33
+ return null;
34
+ }
35
+ const rects = options.rows().map(row => row.getBoundingClientRect());
36
+ return options.dropAt(panelReorderGap(rects, at.clientY), at);
37
+ };
38
+ const show = (drop) => {
39
+ if (mask)
40
+ mask.style.cursor = drop ? 'grabbing' : 'not-allowed';
41
+ const host = options.stateHost?.();
42
+ if (host)
43
+ host.dataset.drag = drop ? 'valid' : 'invalid';
44
+ };
45
+ const unlisten = () => {
46
+ if (!listening)
47
+ return;
48
+ listening.removeEventListener('pointermove', onPointerMove, true);
49
+ listening.removeEventListener('pointerup', onPointerUp, true);
50
+ listening.removeEventListener('pointercancel', onPointerCancel, true);
51
+ listening.removeEventListener('keydown', onKeydown, true);
52
+ listening = null;
53
+ };
54
+ /** Back to idle; `onEnd` only when something was picked up. */
55
+ const finish = () => {
56
+ unlisten();
57
+ start = null;
58
+ if (!dragging)
59
+ return;
60
+ dragging = false;
61
+ mask?.remove();
62
+ mask = null;
63
+ const host = options.stateHost?.();
64
+ if (host)
65
+ delete host.dataset.drag;
66
+ options.onEnd();
67
+ };
68
+ const cancel = () => {
69
+ if (dragging)
70
+ swallowClick = true;
71
+ finish();
72
+ };
73
+ const pickUp = (event) => {
74
+ dragging = true;
75
+ const host = options.mask.host();
76
+ if (host) {
77
+ const doc = event.target?.ownerDocument ?? document;
78
+ mask = doc.createElement('div');
79
+ mask.className = options.mask.className;
80
+ if (options.mask.testId)
81
+ mask.dataset.testid = options.mask.testId;
82
+ mask.style.cursor = 'grabbing';
83
+ host.append(mask);
84
+ }
85
+ options.onPickUp({ clientX: start.x, clientY: start.y });
86
+ };
87
+ function onPointerMove(event) {
88
+ if (!start)
89
+ return;
90
+ if (!dragging) {
91
+ if (!panelDragStarted(start, { x: event.clientX, y: event.clientY })) {
92
+ return;
93
+ }
94
+ pickUp(event);
95
+ }
96
+ const at = point(event);
97
+ const drop = resolve(at);
98
+ show(drop);
99
+ options.onMove(drop, at);
100
+ }
101
+ function onPointerUp(event) {
102
+ if (!dragging) {
103
+ // A press that never became a drag: the click that follows is a click.
104
+ finish();
105
+ return;
106
+ }
107
+ const drop = resolve(point(event));
108
+ swallowClick = true;
109
+ try {
110
+ if (drop !== null)
111
+ options.onDrop(drop);
112
+ }
113
+ finally {
114
+ finish();
115
+ }
116
+ }
117
+ function onPointerCancel() {
118
+ cancel();
119
+ }
120
+ function onKeydown(event) {
121
+ if (event.key !== 'Escape')
122
+ return;
123
+ if (!dragging) {
124
+ // A press still under the threshold: forget it, and let the Escape do
125
+ // what it does in the panel.
126
+ finish();
127
+ return;
128
+ }
129
+ event.stopPropagation();
130
+ event.preventDefault();
131
+ cancel();
132
+ }
133
+ return {
134
+ press(event) {
135
+ // Any new press clears a click left armed by a release or a cancel
136
+ // that no click followed (released over another element).
137
+ swallowClick = false;
138
+ if (event.button !== 0 || start)
139
+ return;
140
+ if (options.readonly())
141
+ return;
142
+ start = { x: event.clientX, y: event.clientY };
143
+ const doc = event.target?.ownerDocument ?? document;
144
+ doc.addEventListener('pointermove', onPointerMove, true);
145
+ doc.addEventListener('pointerup', onPointerUp, true);
146
+ doc.addEventListener('pointercancel', onPointerCancel, true);
147
+ doc.addEventListener('keydown', onKeydown, true);
148
+ listening = doc;
149
+ },
150
+ consumeSwallowedClick() {
151
+ const swallowed = swallowClick;
152
+ swallowClick = false;
153
+ return swallowed;
154
+ },
155
+ get dragging() {
156
+ return dragging;
157
+ },
158
+ dispose() {
159
+ finish();
160
+ },
161
+ };
162
+ }
@@ -44,18 +44,19 @@ export interface PaneSlot {
44
44
  */
45
45
  export declare function paneSlotAtGap(rows: readonly PaneDropRow[], gap: number, parent: string, { lists, parentOf }: PaneLists): PaneSlot | null;
46
46
  /**
47
- * What a drop at `index` of `list` writes for the dragged row `dragId`: the
48
- * model it lands directly ABOVE (`null` = the bottom of its stack), or `null`
49
- * when the slot is refused.
47
+ * What a drop at `index` of `list` writes for the dragged rows `dragged` (one
48
+ * id, or the set a multi-row drag moves): the model they land directly ABOVE
49
+ * (`null` = the bottom of their stack), or `null` when the slot is refused.
50
50
  *
51
- * `stackOf(id)` names the model that stands for a row in the dragged row's
51
+ * `stackOf(id)` names the model that stands for a row in the dragged rows'
52
52
  * own stack — itself, or the frame it belongs to when the dragged row is a
53
53
  * loose element and that row is a frame's member (a frame's members paint
54
54
  * together, right above it) — or `undefined` for a row that is not in that
55
55
  * stack at all. A slot is refused when neither neighbour is in the stack, or
56
56
  * when both stand for the same model: the inside of a frame's block of rows,
57
- * where a loose element cannot be put.
57
+ * where a loose element cannot be put. A row that stands for a dragged model
58
+ * (a dragged frame's member) moves with it, so it is no neighbour either.
58
59
  */
59
- export declare function paneDropAbove(list: readonly SelectionPaneNode[], index: number, dragId: string, stackOf: (id: string) => string | undefined): {
60
+ export declare function paneDropAbove(list: readonly SelectionPaneNode[], index: number, dragged: string | ReadonlySet<string>, stackOf: (id: string) => string | undefined): {
60
61
  above: string | null;
61
62
  } | null;
@@ -59,21 +59,24 @@ export function paneSlotAtGap(rows, gap, parent, { lists, parentOf }) {
59
59
  }
60
60
  }
61
61
  /**
62
- * What a drop at `index` of `list` writes for the dragged row `dragId`: the
63
- * model it lands directly ABOVE (`null` = the bottom of its stack), or `null`
64
- * when the slot is refused.
62
+ * What a drop at `index` of `list` writes for the dragged rows `dragged` (one
63
+ * id, or the set a multi-row drag moves): the model they land directly ABOVE
64
+ * (`null` = the bottom of their stack), or `null` when the slot is refused.
65
65
  *
66
- * `stackOf(id)` names the model that stands for a row in the dragged row's
66
+ * `stackOf(id)` names the model that stands for a row in the dragged rows'
67
67
  * own stack — itself, or the frame it belongs to when the dragged row is a
68
68
  * loose element and that row is a frame's member (a frame's members paint
69
69
  * together, right above it) — or `undefined` for a row that is not in that
70
70
  * stack at all. A slot is refused when neither neighbour is in the stack, or
71
71
  * when both stand for the same model: the inside of a frame's block of rows,
72
- * where a loose element cannot be put.
72
+ * where a loose element cannot be put. A row that stands for a dragged model
73
+ * (a dragged frame's member) moves with it, so it is no neighbour either.
73
74
  */
74
- export function paneDropAbove(list, index, dragId, stackOf) {
75
- const before = list.slice(0, index).filter(node => node.id !== dragId);
76
- const after = list.slice(index).filter(node => node.id !== dragId);
75
+ export function paneDropAbove(list, index, dragged, stackOf) {
76
+ const moving = typeof dragged === 'string' ? new Set([dragged]) : dragged;
77
+ const stays = (node) => !moving.has(node.id) && !moving.has(stackOf(node.id) ?? '');
78
+ const before = list.slice(0, index).filter(stays);
79
+ const after = list.slice(index).filter(stays);
77
80
  const upper = before.length
78
81
  ? stackOf(before[before.length - 1].id)
79
82
  : undefined;