@formicoidea/labre-core 0.59.0 → 0.61.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 (72) hide show
  1. package/architecture/code.c4 +111 -104
  2. package/architecture/domain.c4 +4 -4
  3. package/dist/_pkgs/affine-block-attachment/asset-picker.d.ts +8 -0
  4. package/dist/_pkgs/affine-block-attachment/asset-picker.js +25 -1
  5. package/dist/_pkgs/affine-block-attachment/attachment-block.js +9 -7
  6. package/dist/_pkgs/affine-block-database/database-block.js +4 -5
  7. package/dist/_pkgs/affine-block-database/service/index.d.ts +1 -0
  8. package/dist/_pkgs/affine-block-database/service/index.js +1 -0
  9. package/dist/_pkgs/affine-block-database/service/initial-view.d.ts +33 -0
  10. package/dist/_pkgs/affine-block-database/service/initial-view.js +20 -0
  11. package/dist/_pkgs/affine-block-database/utils/current-view.d.ts +7 -0
  12. package/dist/_pkgs/affine-block-database/utils/current-view.js +14 -0
  13. package/dist/_pkgs/affine-block-embed/common/render-linked-doc.js +2 -0
  14. package/dist/_pkgs/affine-block-embed-doc/common/child-card.d.ts +25 -0
  15. package/dist/_pkgs/affine-block-embed-doc/common/child-card.js +27 -0
  16. package/dist/_pkgs/affine-block-embed-doc/embed-linked-doc-block/commands/insert-embed-linked-doc.js +6 -1
  17. package/dist/_pkgs/affine-block-embed-doc/embed-linked-doc-block/configs/new-doc.js +2 -2
  18. package/dist/_pkgs/affine-block-embed-doc/embed-linked-doc-block/embed-linked-doc-block.js +2 -6
  19. package/dist/_pkgs/affine-block-embed-doc/embed-synced-doc-block/embed-synced-doc-block.js +2 -3
  20. package/dist/_pkgs/affine-block-embed-doc/page-drop/page-drop.js +3 -3
  21. package/dist/_pkgs/affine-block-surface/extensions/index.d.ts +1 -0
  22. package/dist/_pkgs/affine-block-surface/extensions/index.js +1 -0
  23. package/dist/_pkgs/affine-block-surface/extensions/legend-label.d.ts +11 -0
  24. package/dist/_pkgs/affine-block-surface/extensions/legend-label.js +16 -0
  25. package/dist/_pkgs/affine-block-surface/extensions/legend.d.ts +1 -11
  26. package/dist/_pkgs/affine-block-surface/extensions/legend.js +2 -11
  27. package/dist/_pkgs/affine-block-surface/extensions/toolbox.d.ts +62 -0
  28. package/dist/_pkgs/affine-block-surface/extensions/toolbox.js +1 -0
  29. package/dist/_pkgs/affine-block-surface/interchange-headless.d.ts +2 -0
  30. package/dist/_pkgs/affine-block-surface/interchange-headless.js +2 -0
  31. package/dist/_pkgs/affine-components/icons/loading.d.ts +6 -0
  32. package/dist/_pkgs/affine-components/icons/loading.js +16 -12
  33. package/dist/_pkgs/affine-fragment-doc-title/title-band/show-here.js +2 -2
  34. package/dist/_pkgs/affine-fragment-doc-title/title-band/title-band.d.ts +8 -0
  35. package/dist/_pkgs/affine-fragment-doc-title/title-band/title-band.js +35 -13
  36. package/dist/_pkgs/affine-fragment-doc-title/translations.d.ts +1 -1
  37. package/dist/_pkgs/affine-fragment-doc-title/translations.js +1 -1
  38. package/dist/_pkgs/affine-gfx-mindmap/toolbar/basket-elements.d.ts +12 -0
  39. package/dist/_pkgs/affine-gfx-mindmap/toolbar/basket-elements.js +44 -5
  40. package/dist/_pkgs/affine-gfx-mindmap/toolbar/senior-tool.d.ts +3 -2
  41. package/dist/_pkgs/affine-gfx-mindmap/toolbar/senior-tool.js +3 -2
  42. package/dist/_pkgs/affine-gfx-template/builtin/slides/build.js +10 -10
  43. package/dist/_pkgs/affine-gfx-template/builtin/slides/consts.d.ts +48 -64
  44. package/dist/_pkgs/affine-gfx-template/builtin/slides/consts.js +24 -21
  45. package/dist/_pkgs/affine-gfx-template/builtin/slides/masks.d.ts +1 -2
  46. package/dist/_pkgs/affine-gfx-template/builtin/slides/masks.js +0 -34
  47. package/dist/_pkgs/affine-inline-reference/reference-node/configs/toolbar.js +2 -1
  48. package/dist/_pkgs/affine-model/elements/c4/board.d.ts +0 -22
  49. package/dist/_pkgs/affine-model/elements/c4/board.js +1 -22
  50. package/dist/_pkgs/affine-model/elements/c4/consts.d.ts +29 -0
  51. package/dist/_pkgs/affine-model/elements/c4/consts.js +29 -0
  52. package/dist/_pkgs/affine-model/elements/c4/index.d.ts +1 -0
  53. package/dist/_pkgs/affine-model/elements/c4/index.js +1 -0
  54. package/dist/_pkgs/affine-model/headless.d.ts +1 -0
  55. package/dist/_pkgs/affine-model/headless.js +1 -0
  56. package/dist/_pkgs/affine-shared/consts/index.d.ts +9 -0
  57. package/dist/_pkgs/affine-shared/consts/index.js +9 -0
  58. package/dist/_pkgs/affine-shared/services/asset-picker-service.d.ts +7 -6
  59. package/dist/_pkgs/affine-shared/services/asset-picker-service.js +5 -5
  60. package/dist/_pkgs/affine-shared/services/title-band-service.d.ts +19 -0
  61. package/dist/_pkgs/data-view/core/component/cell-status/cell-status-css.d.ts +1 -1
  62. package/dist/_pkgs/data-view/core/component/cell-status/cell-status-css.js +10 -10
  63. package/dist/_pkgs/data-view/core/component/cell-status/cell-status.d.ts +1 -1
  64. package/dist/_pkgs/data-view/core/component/cell-status/cell-status.js +7 -5
  65. package/dist/_pkgs/data-view/property-presets/number/cell-renderer.d.ts +18 -0
  66. package/dist/_pkgs/data-view/property-presets/number/cell-renderer.js +51 -10
  67. package/dist/_pkgs/data-view/property-presets/text/cell-renderer.d.ts +23 -1
  68. package/dist/_pkgs/data-view/property-presets/text/cell-renderer.js +56 -15
  69. package/dist/_pkgs/data-view/property-presets/typing-commit.d.ts +16 -0
  70. package/dist/_pkgs/data-view/property-presets/typing-commit.js +27 -0
  71. package/dist/_pkgs/data-view/view-presets/list/list-view.js +1 -1
  72. package/package.json +1 -1
@@ -1,5 +1,11 @@
1
1
  import { cssVarV2 } from '@toeverything/theme/v2';
2
2
  import { html } from 'lit';
3
+ /**
4
+ * The library's spinner. Rotated by SMIL, not by a `<style>` inside the
5
+ * SVG: that sheet was global in light DOM (any `.spinner` of the page
6
+ * spun), and a binding after it fails to render under happy-dom, so no
7
+ * unit spec could mount a view that draws it.
8
+ */
3
9
  export const LoadingIcon = ({ size = '1em', progress = 0.2, ringColor = cssVarV2('loading/background'), strokeColor = cssVarV2('loading/foreground'), } = {}) => html `<svg
4
10
  width="${size}"
5
11
  height="${size}"
@@ -7,17 +13,6 @@ export const LoadingIcon = ({ size = '1em', progress = 0.2, ringColor = cssVarV2
7
13
  xmlns="http://www.w3.org/2000/svg"
8
14
  fill="none"
9
15
  >
10
- <style>
11
- .spinner {
12
- transform-origin: center;
13
- animation: spinner_animate 0.75s infinite linear;
14
- }
15
- @keyframes spinner_animate {
16
- 100% {
17
- transform: rotate(360deg);
18
- }
19
- }
20
- </style>
21
16
  <circle cx="12" cy="12" r="8" stroke="${ringColor}" stroke-width="4" />
22
17
  <circle
23
18
  class="spinner"
@@ -31,5 +26,14 @@ export const LoadingIcon = ({ size = '1em', progress = 0.2, ringColor = cssVarV2
31
26
  Math.PI *
32
27
  8 *
33
28
  (1 - progress)}"
34
- />
29
+ >
30
+ <animateTransform
31
+ attributeName="transform"
32
+ type="rotate"
33
+ from="0 12 12"
34
+ to="360 12 12"
35
+ dur="0.75s"
36
+ repeatCount="indefinite"
37
+ />
38
+ </circle>
35
39
  </svg>`;
@@ -1,5 +1,5 @@
1
1
  import { EdgelessCRUDIdentifier } from '../../affine-block-surface/index.js';
2
- import { EMBED_CARD_HEIGHT, EMBED_CARD_WIDTH, } from '../../affine-shared/consts/index.js';
2
+ import { EMBED_CARD_HEIGHT, EMBED_CARD_WIDTH, LINKED_DOC_NOTE_STYLE, } from '../../affine-shared/consts/index.js';
3
3
  import { TelemetryProvider } from '../../affine-shared/services/index.js';
4
4
  import { getLastNoteBlock } from '../../affine-shared/utils/index.js';
5
5
  import { Bound } from '../../global/gfx/index.js';
@@ -45,7 +45,7 @@ function insertInPage(std, pageId) {
45
45
  if (!root)
46
46
  return null;
47
47
  const noteId = getLastNoteBlock(store)?.id ?? store.addBlock('affine:note', {}, root.id);
48
- return store.addBlock(LINKED_DOC, { pageId }, noteId);
48
+ return store.addBlock(LINKED_DOC, { pageId, style: LINKED_DOC_NOTE_STYLE }, noteId);
49
49
  }
50
50
  function insertOnCanvas(std, pageId) {
51
51
  const gfx = std.get(GfxControllerIdentifier);
@@ -62,8 +62,16 @@ export declare class TitleBand extends TitleBand_base {
62
62
  private _cellFor;
63
63
  private readonly _openPanel;
64
64
  private readonly _onChip;
65
+ /** The host's menu for the property, under its name (`configure`). */
66
+ private _onNameContextMenu;
65
67
  private _renderFallback;
66
68
  private _renderChip;
69
+ /**
70
+ * `withAdd: false` on the collapsed row: a "+" there added a property the
71
+ * reader could not see (recette 3, P3-x), so it only shows once the chips
72
+ * it adds to are drawn. A page with no property has nothing to collapse
73
+ * and keeps its "+".
74
+ */
67
75
  private _renderTools;
68
76
  private _renderSheet;
69
77
  private _renderNotShown;
@@ -32,6 +32,7 @@ var __runInitializers = (this && this.__runInitializers) || function (thisArg, i
32
32
  }
33
33
  return useValue ? value : void 0;
34
34
  };
35
+ import { LoadingIcon } from '../../affine-components/icons/index.js';
35
36
  import { RefNodeSlotsProvider } from '../../affine-inline-reference/index.js';
36
37
  import { DOC_UNTITLED, FeatureFlagService, formatLocale, PropertiesPanelProvider, TitleBandProvider, translateKey, } from '../../affine-shared/services/index.js';
37
38
  import { stopPropagation } from '../../affine-shared/utils/index.js';
@@ -224,12 +225,11 @@ let TitleBand = (() => {
224
225
  color: var(--affine-placeholder-color);
225
226
  }
226
227
 
227
- .labre-title-band-skeleton {
228
- display: inline-block;
229
- width: 96px;
230
- height: 20px;
231
- border-radius: 6px;
232
- background-color: var(--affine-hover-color);
228
+ /* The host's rule: a spinner centred in what loads, never a skeleton
229
+ (recette 3; ADR-0057 on the Labre side). */
230
+ .labre-title-band-row.labre-title-band-loading {
231
+ justify-content: center;
232
+ font-size: 16px;
233
233
  }
234
234
 
235
235
  .labre-title-band-not-shown-label {
@@ -423,6 +423,18 @@ let TitleBand = (() => {
423
423
  sources.cells.set(id, { type, optionsWritable, cell });
424
424
  return cell;
425
425
  }
426
+ /** The host's menu for the property, under its name (`configure`). */
427
+ _onNameContextMenu(sources, prop, event) {
428
+ const configure = sources.service.configure;
429
+ if (!configure || this._readonly)
430
+ return;
431
+ // A touch long-press is the browser's (text selection), as before.
432
+ if (event.pointerType === 'touch')
433
+ return;
434
+ event.preventDefault();
435
+ event.stopPropagation();
436
+ configure.call(sources.service, sources.docId, prop.id, event.currentTarget);
437
+ }
426
438
  _renderFallback(prop) {
427
439
  const text = formatPropertyValue(prop, this._locale);
428
440
  return html `<span
@@ -460,7 +472,12 @@ let TitleBand = (() => {
460
472
  this._onChip(event, false);
461
473
  }}
462
474
  >
463
- <span class="labre-title-band-chip-name">${prop.name}</span>
475
+ <span
476
+ class="labre-title-band-chip-name"
477
+ data-testid="title-band-chip-name"
478
+ @contextmenu=${(event) => this._onNameContextMenu(sources, prop, event)}
479
+ >${prop.name}</span
480
+ >
464
481
  ${cell
465
482
  ? html `<dv-standalone-cell
466
483
  class="labre-title-band-chip-value"
@@ -474,8 +491,14 @@ let TitleBand = (() => {
474
491
  : this._renderFallback(prop)}
475
492
  </div>`;
476
493
  }
477
- _renderTools(sources, hasPanel) {
478
- const canAdd = !!sources.service.add && !this._readonly;
494
+ /**
495
+ * `withAdd: false` on the collapsed row: a "+" there added a property the
496
+ * reader could not see (recette 3, P3-x), so it only shows once the chips
497
+ * it adds to are drawn. A page with no property has nothing to collapse
498
+ * and keeps its "+".
499
+ */
500
+ _renderTools(sources, hasPanel, withAdd = true) {
501
+ const canAdd = withAdd && !!sources.service.add && !this._readonly;
479
502
  return html `${canAdd
480
503
  ? html `<button
481
504
  class="labre-title-band-icon-button"
@@ -509,14 +532,13 @@ let TitleBand = (() => {
509
532
  const state = sources.sheet$.value;
510
533
  if (state.status === 'loading') {
511
534
  return html `<div
512
- class="labre-title-band-row"
535
+ class="labre-title-band-row labre-title-band-loading"
513
536
  role="status"
514
537
  aria-busy="true"
515
538
  aria-label=${this._t(TITLE_BAND_LOADING)}
516
539
  data-testid="title-band-loading"
517
540
  >
518
- <span class="labre-title-band-skeleton"></span>
519
- <span class="labre-title-band-skeleton"></span>
541
+ ${LoadingIcon()}
520
542
  </div>`;
521
543
  }
522
544
  if (state.status !== 'ready')
@@ -545,7 +567,7 @@ let TitleBand = (() => {
545
567
  >${this._t(TITLE_BAND_EXPAND, properties.length)}</span
546
568
  >
547
569
  </button>
548
- ${this._renderTools(sources, hasPanel)}
570
+ ${this._renderTools(sources, hasPanel, false)}
549
571
  </div>`;
550
572
  }
551
573
  return html `<div
@@ -21,7 +21,7 @@ export declare const TITLE_BAND_OPEN_PANEL: ChromeWording;
21
21
  export declare const TITLE_BAND_COLLAPSE: ChromeWording;
22
22
  /** The folded band's only button; `count` is the number of chips. */
23
23
  export declare const TITLE_BAND_EXPAND: ChromeWording;
24
- /** The accessible name of the skeleton drawn while the sheet loads. */
24
+ /** The accessible name of the spinner drawn while the sheet loads. */
25
25
  export declare const TITLE_BAND_LOADING: ChromeWording;
26
26
  /**
27
27
  * The row of child pages the document holds but does not display, under the
@@ -42,7 +42,7 @@ export const TITLE_BAND_EXPAND = [
42
42
  'com.labre.title-band.expand',
43
43
  'Properties ({{count}})',
44
44
  ];
45
- /** The accessible name of the skeleton drawn while the sheet loads. */
45
+ /** The accessible name of the spinner drawn while the sheet loads. */
46
46
  export const TITLE_BAND_LOADING = [
47
47
  'com.labre.title-band.loading',
48
48
  'Loading properties',
@@ -18,5 +18,17 @@ export declare const mindmapConfig: ToolConfig;
18
18
  export declare const mediaConfig: ToolConfig;
19
19
  export declare const getMindmapRender: (mindmapStyle: MindmapStyle) => DraggableTool["render"];
20
20
  export declare const textRender: DraggableTool['render'];
21
+ /**
22
+ * The canvas's "Add file" (the promoted senior tool, and the media item of the
23
+ * mind map basket): the host's asset dialog when there is one
24
+ * (`AssetPickerProvider`, `kind: 'file'`, one ref), the system file chooser
25
+ * otherwise. Either way an image lands as an image block and anything else as
26
+ * an attachment card, at `bound`'s corner.
27
+ *
28
+ * It used to call the file chooser unconditionally, so with the seam present
29
+ * the canvas's image and file buttons of the note menu opened the dialog
30
+ * while this one, the most visible, still opened the system chooser (labre
31
+ * recette of 2026-10-28).
32
+ */
21
33
  export declare const mediaRender: DraggableTool['render'];
22
34
  export declare const toolConfig2StyleObj: (config: ToolConfig) => Record<string, string>;
@@ -1,9 +1,9 @@
1
- import { addAttachments } from '../../affine-block-attachment/index.js';
2
- import { addImages } from '../../affine-block-image/index.js';
1
+ import { addAttachments, addAttachmentsFromAssets, } from '../../affine-block-attachment/index.js';
2
+ import { addImages, addImagesFromAssets } from '../../affine-block-image/index.js';
3
3
  import { CanvasElementType, EdgelessCRUDIdentifier, } from '../../affine-block-surface/index.js';
4
4
  import { insertEdgelessTextCommand, mountTextElementEditor, } from '../../affine-gfx-text/index.js';
5
5
  import { MAX_IMAGE_WIDTH, TextElementModel, } from '../../affine-model/index.js';
6
- import { FeatureFlagService, TelemetryProvider, translateKey, } from '../../affine-shared/services/index.js';
6
+ import { AssetPickerProvider, FeatureFlagService, TelemetryProvider, translateKey, } from '../../affine-shared/services/index.js';
7
7
  import { openSingleFileWith } from '../../affine-shared/utils/index.js';
8
8
  import { Bound } from '../../global/gfx/index.js';
9
9
  import * as Y from 'yjs';
@@ -105,7 +105,48 @@ export const textRender = async (bound, edgeless) => {
105
105
  });
106
106
  return id;
107
107
  };
108
+ /**
109
+ * The canvas's "Add file" (the promoted senior tool, and the media item of the
110
+ * mind map basket): the host's asset dialog when there is one
111
+ * (`AssetPickerProvider`, `kind: 'file'`, one ref), the system file chooser
112
+ * otherwise. Either way an image lands as an image block and anything else as
113
+ * an attachment card, at `bound`'s corner.
114
+ *
115
+ * It used to call the file chooser unconditionally, so with the seam present
116
+ * the canvas's image and file buttons of the note menu opened the dialog
117
+ * while this one, the most visible, still opened the system chooser (labre
118
+ * recette of 2026-10-28).
119
+ */
108
120
  export const mediaRender = async (bound, edgeless) => {
121
+ const std = edgeless.std;
122
+ const point = [bound.x, bound.y];
123
+ const picker = std.getOptional(AssetPickerProvider);
124
+ if (picker) {
125
+ let asset;
126
+ try {
127
+ [asset] =
128
+ (await picker.pick({
129
+ kind: 'file',
130
+ mode: 'insert',
131
+ multiple: false,
132
+ docId: std.store.id,
133
+ })) ?? [];
134
+ }
135
+ catch (e) {
136
+ console.error(e);
137
+ return null;
138
+ }
139
+ if (!asset)
140
+ return null;
141
+ const [id] = asset.type.startsWith('image/')
142
+ ? await addImagesFromAssets(std, [asset], {
143
+ point,
144
+ maxWidth: MAX_IMAGE_WIDTH,
145
+ shouldTransformPoint: false,
146
+ })
147
+ : addAttachmentsFromAssets(std, [asset], point, false);
148
+ return id ?? null;
149
+ }
109
150
  let file = null;
110
151
  try {
111
152
  file = await openSingleFileWith();
@@ -117,8 +158,6 @@ export const mediaRender = async (bound, edgeless) => {
117
158
  if (!file)
118
159
  return null;
119
160
  const files = [file];
120
- const std = edgeless.std;
121
- const point = [bound.x, bound.y];
122
161
  // image
123
162
  if (file.type.startsWith('image/')) {
124
163
  const [id] = await addImages(std, files, {
@@ -7,7 +7,8 @@ export declare const mindMapSeniorTool: import("../../store/index.js").Extension
7
7
  export declare const textSeniorTool: import("../../store/index.js").ExtensionType;
8
8
  /**
9
9
  * Standalone "Add file" tool. Promoted out of the former "Others" submenu: a
10
- * single tap opens the file picker and inserts the image/attachment at the
11
- * viewport center. Reuses the submenu's `mediaRender` action.
10
+ * single tap opens the host's asset dialog (or the file picker without one)
11
+ * and inserts the image/attachment at the viewport center. Reuses the
12
+ * submenu's `mediaRender` action.
12
13
  */
13
14
  export declare const mediaSeniorTool: import("../../store/index.js").ExtensionType;
@@ -66,8 +66,9 @@ export const textSeniorTool = SeniorToolExtension('edgeless-text', ({ block, gfx
66
66
  }));
67
67
  /**
68
68
  * Standalone "Add file" tool. Promoted out of the former "Others" submenu: a
69
- * single tap opens the file picker and inserts the image/attachment at the
70
- * viewport center. Reuses the submenu's `mediaRender` action.
69
+ * single tap opens the host's asset dialog (or the file picker without one)
70
+ * and inserts the image/attachment at the viewport center. Reuses the
71
+ * submenu's `mediaRender` action.
71
72
  */
72
73
  export const mediaSeniorTool = SeniorToolExtension('edgeless-media', ({ block, gfx }) => ({
73
74
  name: 'Add file',
@@ -4,7 +4,7 @@ import * as Y from 'yjs';
4
4
  import { makeTemplateSnapshot, surfaceText, } from '../../make-snapshot.js';
5
5
  import { SLIDE_SEED_FOOTER, SLIDE_SEED_IMAGE } from '../../translations.js';
6
6
  import { tr } from '../other.js';
7
- import { SLIDE_FOOTER_Y, SLIDE_HAIRLINE_WIDTH, SLIDE_HEIGHT, SLIDE_LINE_HEIGHT, SLIDE_MARGIN, SLIDE_PALETTES, SLIDE_RADIUS, SLIDE_TYPE, SLIDE_WIDTH, SLIDE_ZONE_STROKE_WIDTH, } from './consts.js';
7
+ import { SLIDE_FOOTER_Y, SLIDE_HAIRLINE_WIDTH, SLIDE_HEIGHT, SLIDE_LINE_HEIGHT, SLIDE_MARGIN, SLIDE_PALETTE, SLIDE_RADIUS, SLIDE_TYPE, SLIDE_WIDTH, SLIDE_ZONE_STROKE_WIDTH, } from './consts.js';
8
8
  /**
9
9
  * The one builder of the slide masks: a mask declaration (`masks.ts`) in, a
10
10
  * template snapshot (one 1600 x 900 frame and the elements it holds) and a
@@ -43,7 +43,7 @@ function shapeElement(x, y, w, h, opts) {
43
43
  type: 'shape',
44
44
  shapeType: opts.shapeType,
45
45
  filled: opts.fill !== null,
46
- fillColor: opts.fill ?? SLIDE_PALETTES.paper.paper,
46
+ fillColor: opts.fill ?? SLIDE_PALETTE.paper,
47
47
  strokeColor: opts.stroke,
48
48
  strokeWidth: opts.dash ? SLIDE_ZONE_STROKE_WIDTH : SLIDE_HAIRLINE_WIDTH,
49
49
  strokeStyle: opts.dash ? StrokeStyle.Dash : StrokeStyle.Solid,
@@ -89,7 +89,7 @@ function zoneElement(zone, palette, std) {
89
89
  return shapeElement(zone.x, zone.y, zone.w, zone.h, {
90
90
  shapeType: zone.shape ?? 'rect',
91
91
  fill: zone.filled ? palette.hairline : null,
92
- stroke: zone.stroke === 'muted' ? palette.muted : palette.hairline,
92
+ stroke: zone.stroke === 'muted' ? palette.rule : palette.hairline,
93
93
  dash: false,
94
94
  radius: SLIDE_RADIUS,
95
95
  label: zone.label ? tr(std, zone.label, zone.params) : undefined,
@@ -108,7 +108,7 @@ function zoneElement(zone, palette, std) {
108
108
  return {
109
109
  type: 'connector',
110
110
  mode: ConnectorMode.Straight,
111
- stroke: palette.muted,
111
+ stroke: palette.rule,
112
112
  strokeWidth: SLIDE_HAIRLINE_WIDTH,
113
113
  strokeStyle: StrokeStyle.Solid,
114
114
  frontEndpointStyle: PointStyle.None,
@@ -120,7 +120,7 @@ function zoneElement(zone, palette, std) {
120
120
  }
121
121
  /** The elements of one mask, footer and slide number last, in paint order. */
122
122
  export function slideElements(mask, std) {
123
- const palette = SLIDE_PALETTES[mask.variant];
123
+ const palette = SLIDE_PALETTE;
124
124
  const elements = {};
125
125
  mask.zones.forEach((zone, i) => {
126
126
  elements[`z${i}`] = zoneElement(zone, palette, std);
@@ -164,7 +164,7 @@ function slideFrame(mask, elements, std) {
164
164
  '$blocksuite:internal:text$': true,
165
165
  delta: [{ insert: name }],
166
166
  },
167
- background: SLIDE_PALETTES[mask.variant].paper,
167
+ background: SLIDE_PALETTE.paper,
168
168
  xywh: xywh(0, 0, SLIDE_WIDTH, SLIDE_HEIGHT),
169
169
  index: 'a0',
170
170
  childElementIds: Object.fromEntries(Object.keys(elements).map(id => [id, true])),
@@ -246,7 +246,7 @@ function previewZone(zone, palette) {
246
246
  const filled = zone.kind === 'image' || (zone.kind === 'card' && zone.filled);
247
247
  const fill = filled ? light(palette.hairline) : 'none';
248
248
  const stroke = light(zone.kind === 'card' && zone.stroke === 'muted'
249
- ? palette.muted
249
+ ? palette.rule
250
250
  : zone.kind === 'image'
251
251
  ? palette.zoneStroke
252
252
  : palette.hairline);
@@ -259,7 +259,7 @@ function previewZone(zone, palette) {
259
259
  case 'dot':
260
260
  return `<circle cx="${zone.x + zone.size / 2}" cy="${zone.y + zone.size / 2}" r="${zone.size / 2}" fill="${light(palette.ink)}"/>`;
261
261
  case 'line':
262
- return `<path d="M${zone.from[0]} ${zone.from[1]} L${zone.to[0]} ${zone.to[1]}" stroke="${light(palette.muted)}" stroke-width="6"/>`;
262
+ return `<path d="M${zone.from[0]} ${zone.from[1]} L${zone.to[0]} ${zone.to[1]}" stroke="${light(palette.rule)}" stroke-width="6"/>`;
263
263
  }
264
264
  }
265
265
  /**
@@ -270,7 +270,7 @@ function previewZone(zone, palette) {
270
270
  * panel.
271
271
  */
272
272
  export function slidePreview(mask) {
273
- const palette = SLIDE_PALETTES[mask.variant];
273
+ const palette = SLIDE_PALETTE;
274
274
  const body = mask.zones.map(zone => previewZone(zone, palette)).join('');
275
- return `<svg width="100%" height="100%" viewBox="-20 -20 ${SLIDE_WIDTH + 40} ${SLIDE_HEIGHT + 40}" xmlns="http://www.w3.org/2000/svg"><rect x="0" y="0" width="${SLIDE_WIDTH}" height="${SLIDE_HEIGHT}" rx="16" fill="${light(palette.paper)}" stroke="${light(SLIDE_PALETTES.paper.hairline)}" stroke-width="8"/>${body}</svg>`;
275
+ return `<svg width="100%" height="100%" viewBox="-20 -20 ${SLIDE_WIDTH + 40} ${SLIDE_HEIGHT + 40}" xmlns="http://www.w3.org/2000/svg"><rect x="0" y="0" width="${SLIDE_WIDTH}" height="${SLIDE_HEIGHT}" rx="16" fill="${light(palette.paper)}" stroke="${light(palette.hairline)}" stroke-width="8"/>${body}</svg>`;
276
276
  }
@@ -1,8 +1,8 @@
1
1
  import { type Color, FontWeight } from '../../../affine-model/index.js';
2
2
  /**
3
- * The slide masks' one system: geometry, typography and the two paper
4
- * variants. Every mask in `masks.ts` reads these and restates none of them,
5
- * so a size or a colour changes here, once, for the whole set.
3
+ * The slide masks' one system: geometry, typography and the one paper.
4
+ * Every mask in `masks.ts` reads these and restates none of them, so a size
5
+ * or a colour changes here, once, for the whole set.
6
6
  */
7
7
  /** A 16:9 slide, the frame menu's own `16:9` preset (1600 × 900). */
8
8
  export declare const SLIDE_WIDTH = 1600;
@@ -47,78 +47,62 @@ export type SlidePalette = {
47
47
  paper: Color;
48
48
  /** Titles, headings, body text, key figures. */
49
49
  ink: Color;
50
- /** Eyebrows, captions, footer, slide number, axis labels. */
50
+ /** Eyebrows, captions, footer, slide number, axis labels, details. */
51
51
  muted: Color;
52
+ /** Rules, arrows and the outlines meant to be seen (a Venn circle). */
53
+ rule: Color;
52
54
  /** Hairlines, card outlines, the fill of a placeholder. */
53
55
  hairline: Color;
54
56
  /** The dashed outline of a fill zone. */
55
57
  zoneStroke: Color;
56
58
  };
57
59
  /**
58
- * Two variants, so the set reads as one deck: `paper`, a light slide that
59
- * turns dark grey on a dark canvas, and `inverse`, the ink slide of a section
60
- * divider, a statement or a quote, which swaps paper and ink.
60
+ * One paper for every mask: white, turning dark grey on a dark canvas. The
61
+ * set once had an `inverse` black slide (section, statement, quote); the PO
62
+ * wants every slide of a deck on white (recette of 0.60), so a deck reads as
63
+ * one sheet and prints as one.
64
+ *
65
+ * Words reach WCAG AA (4.5:1) on both sides of the paper, which the base
66
+ * palette's medium grey does not (`#929292` on white is 3.1:1): secondary
67
+ * words are set in ink, and their tone is carried by their role's size.
68
+ * The medium grey stays for rules and outlines, where 3:1 is the bar.
69
+ *
70
+ * ponytail: `muted` is the ink pair, so a muted line looks like an ink one
71
+ * at the same size. The ceiling is a deck with no tonal step between a
72
+ * heading and its detail; the upgrade is a darker grey `{ light, dark }`
73
+ * pair in the base palette (one AA on white and on `#252525`), set here.
61
74
  */
62
- export declare const SLIDE_PALETTES: {
63
- readonly paper: {
64
- readonly paper: Color;
65
- readonly ink: string | {
66
- normal: string;
67
- } | {
68
- dark: string;
69
- light: string;
70
- };
71
- readonly muted: string | {
72
- normal: string;
73
- } | {
74
- dark: string;
75
- light: string;
76
- };
77
- readonly hairline: {
78
- dark: string;
79
- light: string;
80
- };
81
- readonly zoneStroke: string | {
82
- normal: string;
83
- } | {
84
- dark: string;
85
- light: string;
86
- };
75
+ export declare const SLIDE_PALETTE: {
76
+ readonly paper: Color;
77
+ readonly ink: string | {
78
+ normal: string;
79
+ } | {
80
+ dark: string;
81
+ light: string;
82
+ };
83
+ readonly muted: string | {
84
+ normal: string;
85
+ } | {
86
+ dark: string;
87
+ light: string;
88
+ };
89
+ readonly rule: string | {
90
+ normal: string;
91
+ } | {
92
+ dark: string;
93
+ light: string;
94
+ };
95
+ readonly hairline: {
96
+ dark: string;
97
+ light: string;
87
98
  };
88
- readonly inverse: {
89
- readonly paper: string | {
90
- normal: string;
91
- } | {
92
- dark: string;
93
- light: string;
94
- };
95
- readonly ink: string | {
96
- normal: string;
97
- } | {
98
- dark: string;
99
- light: string;
100
- };
101
- readonly muted: string | {
102
- normal: string;
103
- } | {
104
- dark: string;
105
- light: string;
106
- };
107
- readonly hairline: string | {
108
- normal: string;
109
- } | {
110
- dark: string;
111
- light: string;
112
- };
113
- readonly zoneStroke: string | {
114
- normal: string;
115
- } | {
116
- dark: string;
117
- light: string;
118
- };
99
+ readonly zoneStroke: string | {
100
+ normal: string;
101
+ } | {
102
+ dark: string;
103
+ light: string;
119
104
  };
120
105
  };
121
- export type SlideVariant = keyof typeof SLIDE_PALETTES;
122
106
  /** Stroke widths, model units. */
123
107
  export declare const SLIDE_HAIRLINE_WIDTH = 2;
124
108
  export declare const SLIDE_ZONE_STROKE_WIDTH = 2;
@@ -2,9 +2,9 @@ import { DefaultTheme, FontWeight } from '../../../affine-model/index.js';
2
2
  import { HEADING_SCALE } from '../../../affine-shared/consts/index.js';
3
3
  import { darkThemeV2, lightThemeV2 } from '@toeverything/theme/v2';
4
4
  /**
5
- * The slide masks' one system: geometry, typography and the two paper
6
- * variants. Every mask in `masks.ts` reads these and restates none of them,
7
- * so a size or a colour changes here, once, for the whole set.
5
+ * The slide masks' one system: geometry, typography and the one paper.
6
+ * Every mask in `masks.ts` reads these and restates none of them, so a size
7
+ * or a colour changes here, once, for the whole set.
8
8
  */
9
9
  /** A 16:9 slide, the frame menu's own `16:9` preset (1600 × 900). */
10
10
  export const SLIDE_WIDTH = 1600;
@@ -74,25 +74,28 @@ const NOTE_GREY = {
74
74
  light: lightThemeV2['edgeless/note/grey'],
75
75
  };
76
76
  /**
77
- * Two variants, so the set reads as one deck: `paper`, a light slide that
78
- * turns dark grey on a dark canvas, and `inverse`, the ink slide of a section
79
- * divider, a statement or a quote, which swaps paper and ink.
77
+ * One paper for every mask: white, turning dark grey on a dark canvas. The
78
+ * set once had an `inverse` black slide (section, statement, quote); the PO
79
+ * wants every slide of a deck on white (recette of 0.60), so a deck reads as
80
+ * one sheet and prints as one.
81
+ *
82
+ * Words reach WCAG AA (4.5:1) on both sides of the paper, which the base
83
+ * palette's medium grey does not (`#929292` on white is 3.1:1): secondary
84
+ * words are set in ink, and their tone is carried by their role's size.
85
+ * The medium grey stays for rules and outlines, where 3:1 is the bar.
86
+ *
87
+ * ponytail: `muted` is the ink pair, so a muted line looks like an ink one
88
+ * at the same size. The ceiling is a deck with no tonal step between a
89
+ * heading and its detail; the upgrade is a darker grey `{ light, dark }`
90
+ * pair in the base palette (one AA on white and on `#252525`), set here.
80
91
  */
81
- export const SLIDE_PALETTES = {
82
- paper: {
83
- paper: DefaultTheme.NoteBackgroundColorMap['White'],
84
- ink: DefaultTheme.black,
85
- muted: MEDIUM_GREY,
86
- hairline: NOTE_GREY,
87
- zoneStroke: MEDIUM_GREY,
88
- },
89
- inverse: {
90
- paper: DefaultTheme.black,
91
- ink: DefaultTheme.white,
92
- muted: MEDIUM_GREY,
93
- hairline: MEDIUM_GREY,
94
- zoneStroke: MEDIUM_GREY,
95
- },
92
+ export const SLIDE_PALETTE = {
93
+ paper: DefaultTheme.NoteBackgroundColorMap['White'],
94
+ ink: DefaultTheme.black,
95
+ muted: DefaultTheme.black,
96
+ rule: MEDIUM_GREY,
97
+ hairline: NOTE_GREY,
98
+ zoneStroke: MEDIUM_GREY,
96
99
  };
97
100
  /** Stroke widths, model units. */
98
101
  export const SLIDE_HAIRLINE_WIDTH = 2;
@@ -1,5 +1,5 @@
1
1
  import type { ChromeWording, TranslationParams } from '../../../affine-shared/services/index.js';
2
- import { type SlideTextRole, type SlideVariant } from './consts.js';
2
+ import { type SlideTextRole } from './consts.js';
3
3
  /**
4
4
  * The slide masks, as DATA: a mask is the list of its zones, and one builder
5
5
  * (`build.ts`) projects every mask into a frame, its elements and its preview.
@@ -76,7 +76,6 @@ export type SlideMask = {
76
76
  name: ChromeWording;
77
77
  /** Plain English search words besides the name (`Template.keywords`). */
78
78
  keywords: string[];
79
- variant: SlideVariant;
80
79
  zones: SlideZone[];
81
80
  };
82
81
  /** Every mask, in the panel's order: a deck reads top to bottom. */