@formicoidea/labre-core 0.37.0 → 0.38.2

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 (54) hide show
  1. package/dist/_pkgs/affine-block-frame/frame-manager.d.ts +31 -0
  2. package/dist/_pkgs/affine-block-frame/frame-manager.js +62 -2
  3. package/dist/_pkgs/affine-block-frame/frame-tool.js +6 -2
  4. package/dist/_pkgs/affine-block-surface/extensions/audit.js +12 -34
  5. package/dist/_pkgs/affine-block-surface/extensions/reading-toolbar.js +12 -12
  6. package/dist/_pkgs/affine-block-surface/extensions/reading.js +10 -28
  7. package/dist/_pkgs/affine-block-surface/extensions/validation.d.ts +321 -16
  8. package/dist/_pkgs/affine-block-surface/extensions/validation.js +945 -62
  9. package/dist/_pkgs/affine-block-surface/framework-background/facts.d.ts +24 -0
  10. package/dist/_pkgs/affine-block-surface/framework-background/facts.js +31 -0
  11. package/dist/_pkgs/affine-block-surface/framework-background/index.d.ts +1 -0
  12. package/dist/_pkgs/affine-block-surface/framework-background/index.js +1 -0
  13. package/dist/_pkgs/affine-block-surface/framework-background/stacking.d.ts +131 -0
  14. package/dist/_pkgs/affine-block-surface/framework-background/stacking.js +184 -0
  15. package/dist/_pkgs/affine-block-surface/utils/sort.d.ts +15 -1
  16. package/dist/_pkgs/affine-block-surface/utils/sort.js +30 -31
  17. package/dist/_pkgs/affine-block-surface/view.js +5 -0
  18. package/dist/_pkgs/affine-components/highlight-dropdown-menu/highlight-duotone-icon.js +8 -8
  19. package/dist/_pkgs/affine-components/highlight-dropdown-menu/text-duotone-icon.js +11 -11
  20. package/dist/_pkgs/affine-components/icons/text.js +426 -426
  21. package/dist/_pkgs/affine-gfx-group/element-renderer/utils.js +3 -1
  22. package/dist/_pkgs/affine-gfx-template/index.d.ts +1 -0
  23. package/dist/_pkgs/affine-gfx-template/index.js +1 -0
  24. package/dist/_pkgs/affine-gfx-template/make-snapshot.d.ts +16 -2
  25. package/dist/_pkgs/affine-gfx-template/make-snapshot.js +43 -4
  26. package/dist/_pkgs/affine-gfx-template/services/template-middlewares.js +1 -0
  27. package/dist/_pkgs/affine-gfx-template/services/template.d.ts +6 -0
  28. package/dist/_pkgs/affine-gfx-template/services/template.js +6 -0
  29. package/dist/_pkgs/affine-gfx-template/snapshot-from-action.d.ts +20 -0
  30. package/dist/_pkgs/affine-gfx-template/snapshot-from-action.js +205 -0
  31. package/dist/_pkgs/affine-gfx-template/toolbar/template-panel.js +4 -0
  32. package/dist/_pkgs/affine-gfx-template/toolbar/template-type.d.ts +13 -0
  33. package/dist/_pkgs/affine-inline-link/link-node/configs/toolbar.d.ts +11 -17
  34. package/dist/_pkgs/affine-inline-link/link-node/configs/toolbar.js +55 -14
  35. package/dist/_pkgs/affine-model/elements/cynefin/cynefin.d.ts +9 -20
  36. package/dist/_pkgs/affine-model/elements/cynefin/cynefin.js +7 -28
  37. package/dist/_pkgs/affine-model/elements/edgy/board.d.ts +9 -21
  38. package/dist/_pkgs/affine-model/elements/edgy/board.js +8 -29
  39. package/dist/_pkgs/affine-model/elements/edgy/facets.d.ts +14 -28
  40. package/dist/_pkgs/affine-model/elements/edgy/facets.js +12 -36
  41. package/dist/_pkgs/affine-model/elements/estuarine/estuarine.d.ts +9 -19
  42. package/dist/_pkgs/affine-model/elements/estuarine/estuarine.js +7 -27
  43. package/dist/_pkgs/affine-shared/services/link-preview-service/link-preview-service.d.ts +28 -0
  44. package/dist/_pkgs/affine-shared/services/link-preview-service/link-preview-service.js +20 -3
  45. package/dist/_pkgs/affine-widget-edgeless-auto-connect/index.js +124 -124
  46. package/dist/_pkgs/affine-widget-edgeless-zoom-toolbar/index.js +26 -26
  47. package/dist/_pkgs/affine-widget-keyboard-toolbar/icons.js +98 -98
  48. package/dist/_pkgs/affine-widget-page-dragging-area/index.js +10 -10
  49. package/dist/_pkgs/affine-widget-viewport-overlay/index.js +13 -13
  50. package/dist/_pkgs/data-view/core/group-by/renderer/boolean-group.js +11 -11
  51. package/dist/_pkgs/data-view/core/utils/menu-title.js +17 -17
  52. package/dist/_pkgs/data-view/view-presets/table/pc-virtual/group/top/group-title.js +18 -18
  53. package/dist/_pkgs/std/gfx/model/surface/surface-model.js +12 -2
  54. package/package.json +1 -1
@@ -35,6 +35,37 @@ export declare class EdgelessFrameManager extends GfxExtension {
35
35
  }>(a: T, b: T): 0 | 1 | -1;
36
36
  private _addChildrenToLegacyFrame;
37
37
  private _addFrameBlock;
38
+ /**
39
+ * The index a frame gets when it is placed at `bound`.
40
+ *
41
+ * A frame's index is deliberately at the BACK of the stack, so it renders
42
+ * behind everything it owns. On a bare canvas that is right. On a framework
43
+ * board it is not: a Wardley map — a C4 board, a BPMN pool, any
44
+ * `FrameworkBackgroundElementModel` — is an OPAQUE canvas element, so a
45
+ * frame sent behind the whole surface goes behind the paint and only the
46
+ * part overhanging the board stays visible. The back of the SURFACE and the
47
+ * back of the BOARD are two different depths.
48
+ *
49
+ * So the frame lands just above the topmost background it covers, which is
50
+ * still behind every artefact drawn on that board — the depth a Wardley zone
51
+ * already needed, for the same reason, in #213. See
52
+ * {@link indexOverBackgrounds}.
53
+ */
54
+ frameIndexAt(bound: Bound): string;
55
+ /**
56
+ * A frame DROPPED onto a framework background is raised above it, exactly as
57
+ * a frame drawn there is ({@link frameIndexAt}) — same gesture, arriving a
58
+ * moment later.
59
+ *
60
+ * Only ever upwards, and only while the frame is actually BELOW a background
61
+ * it overlaps. So a frame already above one is left alone (a drag writes an
62
+ * index at most once, and only on the move that buries it), and undo is safe
63
+ * for the same reason: the restored position is the one the frame came from,
64
+ * where nothing overlapping was above it. A frame the author sent to the
65
+ * back on purpose is only raised again if they move it — on a board, moving
66
+ * a frame means wanting to see it.
67
+ */
68
+ private _watchFrameMoved;
38
69
  private _watchElementAdded;
39
70
  /**
40
71
  * Whether `element` fully encloses `frame`.
@@ -1,4 +1,4 @@
1
- import { Overlay } from '../affine-block-surface/index.js';
1
+ import { indexOverBackgrounds, Overlay, stackedElementsOf, } from '../affine-block-surface/index.js';
2
2
  import { FrameworkBackgroundElementModel } from '../affine-model/index.js';
3
3
  import { EditPropsStore } from '../affine-shared/services/index.js';
4
4
  import { DisposableGroup } from '../global/disposable/index.js';
@@ -100,6 +100,7 @@ export class EdgelessFrameManager extends GfxExtension {
100
100
  super(gfx);
101
101
  this._disposable = new DisposableGroup();
102
102
  this._watchElementAdded();
103
+ this._watchFrameMoved();
103
104
  }
104
105
  static framePresentationComparator(a, b) {
105
106
  function stringCompare(a, b) {
@@ -145,7 +146,7 @@ export class EdgelessFrameManager extends GfxExtension {
145
146
  .applyLastProps('affine:frame', {
146
147
  title: new Text(new Y.Text(`Frame ${this.frames.length + 1}`)),
147
148
  xywh: bound.serialize(),
148
- index: this.gfx.layer.generateIndex(true),
149
+ index: this.frameIndexAt(bound),
149
150
  presentationIndex: this.generatePresentationIndex(),
150
151
  });
151
152
  const id = this.gfx.doc.addBlock('affine:frame', props, surfaceModel);
@@ -155,6 +156,65 @@ export class EdgelessFrameManager extends GfxExtension {
155
156
  }
156
157
  return frameModel;
157
158
  }
159
+ /**
160
+ * The index a frame gets when it is placed at `bound`.
161
+ *
162
+ * A frame's index is deliberately at the BACK of the stack, so it renders
163
+ * behind everything it owns. On a bare canvas that is right. On a framework
164
+ * board it is not: a Wardley map — a C4 board, a BPMN pool, any
165
+ * `FrameworkBackgroundElementModel` — is an OPAQUE canvas element, so a
166
+ * frame sent behind the whole surface goes behind the paint and only the
167
+ * part overhanging the board stays visible. The back of the SURFACE and the
168
+ * back of the BOARD are two different depths.
169
+ *
170
+ * So the frame lands just above the topmost background it covers, which is
171
+ * still behind every artefact drawn on that board — the depth a Wardley zone
172
+ * already needed, for the same reason, in #213. See
173
+ * {@link indexOverBackgrounds}.
174
+ */
175
+ frameIndexAt(bound) {
176
+ return (indexOverBackgrounds(stackedElementsOf(this.gfx), bound) ??
177
+ this.gfx.layer.generateIndex(true));
178
+ }
179
+ /**
180
+ * A frame DROPPED onto a framework background is raised above it, exactly as
181
+ * a frame drawn there is ({@link frameIndexAt}) — same gesture, arriving a
182
+ * moment later.
183
+ *
184
+ * Only ever upwards, and only while the frame is actually BELOW a background
185
+ * it overlaps. So a frame already above one is left alone (a drag writes an
186
+ * index at most once, and only on the move that buries it), and undo is safe
187
+ * for the same reason: the restored position is the one the frame came from,
188
+ * where nothing overlapping was above it. A frame the author sent to the
189
+ * back on purpose is only raised again if they move it — on a board, moving
190
+ * a frame means wanting to see it.
191
+ */
192
+ _watchFrameMoved() {
193
+ this._disposable.add(this.gfx.doc.slots.blockUpdated.subscribe(payload => {
194
+ if (payload.type !== 'update' ||
195
+ !payload.isLocal ||
196
+ payload.flavour !== 'affine:frame' ||
197
+ payload.props.key !== 'xywh') {
198
+ return;
199
+ }
200
+ const frame = this.gfx.getElementById(payload.id);
201
+ // A nested frame keeps its parent's ordering: `compare` reads the
202
+ // ancestor's index, so raising the child would change nothing.
203
+ if (!frame || !isFrameBlock(frame) || frame.group !== null)
204
+ return;
205
+ const siblings = stackedElementsOf(this.gfx, frame);
206
+ const box = frame.elementBound;
207
+ const buried = siblings.some(element => element.isBackground &&
208
+ element.index > frame.index &&
209
+ Bound.deserialize(element.xywh).isOverlapWithBound(box));
210
+ if (!buried)
211
+ return;
212
+ const over = indexOverBackgrounds(siblings, box);
213
+ if (over !== null) {
214
+ this.gfx.updateElement(frame, { index: over });
215
+ }
216
+ }));
217
+ }
158
218
  _watchElementAdded() {
159
219
  if (!this.gfx.surface) {
160
220
  return;
@@ -44,12 +44,16 @@ export class FrameTool extends BaseTool {
44
44
  return;
45
45
  if (!this._frame) {
46
46
  const frames = this.gfx.layer.blocks.filter(block => block.flavour === 'affine:frame');
47
+ const bound = Bound.fromPoints([this._startPoint, currentPoint]);
47
48
  const props = this.std
48
49
  .get(EditPropsStore)
49
50
  .applyLastProps('affine:frame', {
50
51
  title: new Text(new Y.Text(`Frame ${frames.length + 1}`)),
51
- xywh: Bound.fromPoints([this._startPoint, currentPoint]).serialize(),
52
- index: this.gfx.layer.generateIndex(true),
52
+ xywh: bound.serialize(),
53
+ // Not the raw back of the stack: a frame drawn on a framework
54
+ // background must sit just above it, or the author draws blind
55
+ // behind an opaque map. See `EdgelessFrameManager.frameIndexAt`.
56
+ index: this.frameManager.frameIndexAt(bound),
53
57
  presentationIndex: this.frameManager.generatePresentationIndex(),
54
58
  });
55
59
  const id = this.doc.addBlock('affine:frame', props, this.gfx.surface);
@@ -31,7 +31,7 @@ import { getAuditCriteria, requestAudit, TelemetryProvider, } from '../../affine
31
31
  import { GfxControllerIdentifier, GfxPrimitiveElementModel, roleIsA, } from '../../std/gfx/index.js';
32
32
  import { z } from 'zod';
33
33
  import { backgroundInstanceZones, backgroundPlot, } from '../framework-background/def.js';
34
- import { backgroundAxisFacts } from '../framework-background/facts.js';
34
+ import { backgroundAxisFacts, containingFrame, } from '../framework-background/facts.js';
35
35
  import { ValidationManager, ValidationRuleIdentifier, } from './validation.js';
36
36
  /**
37
37
  * `map.audit` takes no required parameter: the assistant audits what is
@@ -105,6 +105,7 @@ function collectFrames(rules, elements) {
105
105
  const profileId = el.validationProfile;
106
106
  frames.push({
107
107
  def,
108
+ id: el.id,
108
109
  bound: el.elementBound,
109
110
  fact: {
110
111
  elementId: el.id,
@@ -135,41 +136,12 @@ function collectFrames(rules, elements) {
135
136
  }
136
137
  return frames;
137
138
  }
138
- /** Squared edge-to-edge gap — the same attribution heuristic the engine uses. */
139
- function gapSquared(frame, bound) {
140
- const dx = Math.max(frame.x - bound.maxX, bound.x - frame.maxX, 0);
141
- const dy = Math.max(frame.y - bound.maxY, bound.y - frame.maxY, 0);
142
- return dx * dx + dy * dy;
143
- }
144
- /**
145
- * The frame an element belongs to: the one that CONTAINS it, failing that the
146
- * nearest by edge-to-edge gap, ties broken by the smaller id.
147
- *
148
- * Deliberately the same answer `attributeBackground` gives inside the engine —
149
- * an audit that attributed an element to a different map than the rule that
150
- * indicted it would produce two findings about one component that disagree
151
- * about which map it is on.
152
- */
153
- function attribute(bound, frames) {
154
- let nearest = null;
155
- let nearestDistance = Infinity;
156
- for (const frame of frames) {
157
- if (frame.bound.contains(bound))
158
- return frame;
159
- const distance = gapSquared(frame.bound, bound);
160
- if (distance < nearestDistance ||
161
- (distance === nearestDistance &&
162
- nearest !== null &&
163
- frame.fact.elementId < nearest.fact.elementId)) {
164
- nearestDistance = distance;
165
- nearest = frame;
166
- }
167
- }
168
- return nearest;
169
- }
170
139
  /**
171
140
  * Where an element's centre sits inside a frame's PLOT, as ratios.
172
141
  *
142
+ * A POSITION, not a membership test: it is asked only about an element some
143
+ * frame already contains, and answers where in that frame's plot it lies.
144
+ *
173
145
  * Ratios of the plot and not of the element box: a Wardley transition drawn at
174
146
  * `0.4` is 40 % of the PLOT, not of the map element, and the margin between the
175
147
  * two is exactly where the axis titles live. `backgroundPlot` is the one place
@@ -262,7 +234,13 @@ export function collectAuditFacts(std) {
262
234
  if (role === undefined || frameIds.has(el.id))
263
235
  continue;
264
236
  const bound = el.elementBound;
265
- const frame = attribute(bound, frames);
237
+ // Membership is WHOLE containment (PF2.4), and there is no nearest-frame
238
+ // fallback: an element straddling the edge of a map is not on it, so the
239
+ // audit reports it exactly as it reports one on bare canvas — id and role,
240
+ // no frame, no position, no zone. Anything else would tell an assistant a
241
+ // component is on a map the engine is simultaneously indicting it for
242
+ // hanging off.
243
+ const frame = containingFrame(bound, frames, candidate => candidate.bound);
266
244
  if (frame === null) {
267
245
  elementFacts.push({ id: el.id, role });
268
246
  continue;
@@ -58,18 +58,18 @@ const READ_LABEL_FALLBACK = 'Read this component';
58
58
  * Drawn inline like its neighbours: this package does not depend on
59
59
  * `@blocksuite/icons` and one glyph is not worth a new dependency.
60
60
  */
61
- const ReadIcon = html `<svg
62
- width="20"
63
- height="20"
64
- viewBox="0 0 24 24"
65
- fill="none"
66
- stroke="currentColor"
67
- stroke-width="2"
68
- stroke-linecap="round"
69
- stroke-linejoin="round"
70
- >
71
- <path d="M2 12s3.5-6 10-6 10 6 10 6-3.5 6-10 6-10-6-10-6z" />
72
- <circle cx="12" cy="12" r="3" />
61
+ const ReadIcon = html `<svg
62
+ width="20"
63
+ height="20"
64
+ viewBox="0 0 24 24"
65
+ fill="none"
66
+ stroke="currentColor"
67
+ stroke-width="2"
68
+ stroke-linecap="round"
69
+ stroke-linejoin="round"
70
+ >
71
+ <path d="M2 12s3.5-6 10-6 10 6 10 6-3.5 6-10 6-10-6-10-6z" />
72
+ <circle cx="12" cy="12" r="3" />
73
73
  </svg>`;
74
74
  /** The command this module drives. Spelled once. */
75
75
  const ELEMENT_READ_ID = 'element.read';
@@ -8,7 +8,7 @@ import { effect, signal } from '@preact/signals-core';
8
8
  // reading is a one-way read of pure declaration data that must never pull a
9
9
  // canvas in.
10
10
  import { backgroundPlot, } from '../framework-background/def.js';
11
- import { backgroundTransitionBands } from '../framework-background/facts.js';
11
+ import { backgroundTransitionBands, containingFrame, } from '../framework-background/facts.js';
12
12
  /** Deepest role chain reported by {@link ReadingNodeType.specialises}. */
13
13
  const MAX_ROLE_DEPTH = 32;
14
14
  /** Numeric slack on the two geometry comparisons. Model units. */
@@ -127,7 +127,7 @@ const centreOf = (bound) => [
127
127
  bound.y + bound.h / 2,
128
128
  ];
129
129
  /**
130
- * The frame the subject sits ON — containment of its CENTRE, and nothing else.
130
+ * The frame the subject sits ON — the one that WHOLLY CONTAINS it (PF2.4).
131
131
  *
132
132
  * Deliberately not the "nearest map" attribution the validation engine uses: a
133
133
  * finding has to be filed against some map even when the subject is off in the
@@ -135,40 +135,22 @@ const centreOf = (bound) => [
135
135
  * invented fact. A component beside the map has no phase, and the panel says
136
136
  * nothing rather than something plausible.
137
137
  *
138
- * The centre, not the whole bound: a node overhanging the edge of the map is
139
- * still on it to every reader.
138
+ * The whole bound and not the centre: a node overhanging the edge of the map is
139
+ * not on it yet, which is what the engine already says about it.
140
140
  *
141
141
  * ## Two frames under one component
142
142
  *
143
- * The first match in DOCUMENT order wins — not the smallest, not the topmost.
144
- * Deterministic, and deliberately not clever: z-order is a paint concern and
145
- * "the smallest frame containing it" is a rule nobody declared. Two overlapping
146
- * maps of different sizes are a board that has not decided what it is, and a
147
- * reading that quietly picked one of them by area would be harder to argue with
148
- * than one that picked the first. The day a framework needs nesting, this is the
149
- * one function to change.
143
+ * Ties go to the SMALLER id, which is the tie-break `containingFrame` and the
144
+ * engine share — one function for one question, and an answer that cannot
145
+ * depend on the order a `Y.Map` was rebuilt in.
150
146
  */
151
147
  function frameOf(element, elements, profile) {
152
148
  const frame = profile.frame;
153
149
  if (!frame)
154
150
  return null;
155
- const [cx, cy] = centreOf(element.elementBound);
156
- for (const candidate of elements) {
157
- if (candidate.id === element.id)
158
- continue;
159
- if (candidate.role === undefined)
160
- continue;
161
- if (!roleIsA(candidate.role, frame.backgroundRole, profile.roles))
162
- continue;
163
- const bound = candidate.elementBound;
164
- if (cx >= bound.x &&
165
- cx <= bound.x + bound.w &&
166
- cy >= bound.y &&
167
- cy <= bound.y + bound.h) {
168
- return candidate;
169
- }
170
- }
171
- return null;
151
+ return containingFrame(element.elementBound, elements.filter(candidate => candidate.id !== element.id &&
152
+ candidate.role !== undefined &&
153
+ roleIsA(candidate.role, frame.backgroundRole, profile.roles)), candidate => candidate.elementBound);
172
154
  }
173
155
  /**
174
156
  * The phase, read from the position and the frame's DECLARED zones.