@formicoidea/labre-core 0.53.0 → 0.55.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 (104) hide show
  1. package/dist/_pkgs/affine-block-data-view/data-view-block.js +1 -1
  2. package/dist/_pkgs/affine-block-database/components/title/index.js +7 -2
  3. package/dist/_pkgs/affine-block-database/database-block-styles.js +9 -1
  4. package/dist/_pkgs/affine-block-embed-doc/page-drop/page-drop.d.ts +70 -0
  5. package/dist/_pkgs/affine-block-embed-doc/page-drop/page-drop.js +196 -0
  6. package/dist/_pkgs/affine-block-embed-doc/view.js +3 -0
  7. package/dist/_pkgs/affine-block-frame/frame-template.d.ts +11 -0
  8. package/dist/_pkgs/affine-block-frame/frame-template.js +24 -4
  9. package/dist/_pkgs/affine-block-frame/translations.d.ts +3 -0
  10. package/dist/_pkgs/affine-block-frame/translations.js +7 -0
  11. package/dist/_pkgs/affine-block-surface/extensions/export-svg/render.js +9 -3
  12. package/dist/_pkgs/affine-block-surface/framework-background/renderer.js +9 -5
  13. package/dist/_pkgs/affine-block-surface/index.d.ts +1 -0
  14. package/dist/_pkgs/affine-block-surface/index.js +1 -0
  15. package/dist/_pkgs/affine-block-surface/renderer/canvas-renderer.d.ts +55 -3
  16. package/dist/_pkgs/affine-block-surface/renderer/canvas-renderer.js +139 -6
  17. package/dist/_pkgs/affine-block-surface/renderer/notation-tint.d.ts +92 -0
  18. package/dist/_pkgs/affine-block-surface/renderer/notation-tint.js +113 -0
  19. package/dist/_pkgs/affine-block-surface/surface-block.js +2 -2
  20. package/dist/_pkgs/affine-components/color-picker/styles.js +1 -1
  21. package/dist/_pkgs/affine-components/context-menu/input.js +2 -1
  22. package/dist/_pkgs/affine-components/context-menu/menu-renderer.d.ts +7 -0
  23. package/dist/_pkgs/affine-components/context-menu/menu-renderer.js +53 -13
  24. package/dist/_pkgs/affine-components/drop-indicator/file-drop-manager.js +15 -0
  25. package/dist/_pkgs/affine-gfx-shape/element-renderer/shape/index.js +4 -2
  26. package/dist/_pkgs/affine-inline-link/link-node/link-popup/styles.js +1 -1
  27. package/dist/_pkgs/affine-inline-reference/reference-node/reference-popup/reference-popup.js +9 -9
  28. package/dist/_pkgs/affine-shared/consts/notation.d.ts +1 -0
  29. package/dist/_pkgs/affine-shared/consts/notation.js +29 -0
  30. package/dist/_pkgs/affine-shared/services/frame-template-service.d.ts +5 -0
  31. package/dist/_pkgs/affine-shared/services/index.d.ts +1 -0
  32. package/dist/_pkgs/affine-shared/services/index.js +1 -0
  33. package/dist/_pkgs/affine-shared/services/page-drop-service.d.ts +61 -0
  34. package/dist/_pkgs/affine-shared/services/page-drop-service.js +30 -0
  35. package/dist/_pkgs/affine-shared/styles/scrollbar-style.js +1 -1
  36. package/dist/_pkgs/affine-widget-edgeless-auto-connect/index.js +5 -5
  37. package/dist/_pkgs/affine-widget-edgeless-selected-rect/edgeless-auto-complete.js +2 -2
  38. package/dist/_pkgs/affine-widget-keyboard-toolbar/config.js +6 -6
  39. package/dist/_pkgs/affine-widget-keyboard-toolbar/styles.js +1 -1
  40. package/dist/_pkgs/affine-widget-linked-doc/config.d.ts +8 -0
  41. package/dist/_pkgs/affine-widget-linked-doc/config.js +8 -1
  42. package/dist/_pkgs/affine-widget-linked-doc/linked-doc-popover.js +40 -32
  43. package/dist/_pkgs/affine-widget-linked-doc/styles.js +1 -1
  44. package/dist/_pkgs/affine-widget-linked-doc/translations.d.ts +2 -0
  45. package/dist/_pkgs/affine-widget-linked-doc/translations.js +6 -0
  46. package/dist/_pkgs/affine-widget-thread-marks/widget.d.ts +3 -1
  47. package/dist/_pkgs/affine-widget-thread-marks/widget.js +6 -1
  48. package/dist/_pkgs/affine-widget-toolbar/utils.d.ts +2 -1
  49. package/dist/_pkgs/affine-widget-toolbar/utils.js +5 -2
  50. package/dist/_pkgs/data-view/core/common/properties.js +2 -2
  51. package/dist/_pkgs/data-view/core/common/property-menu.d.ts +3 -3
  52. package/dist/_pkgs/data-view/core/common/property-menu.js +7 -7
  53. package/dist/_pkgs/data-view/core/component/overflow/overflow.js +4 -1
  54. package/dist/_pkgs/data-view/core/group-by/renderer/base.d.ts +4 -1
  55. package/dist/_pkgs/data-view/core/group-by/renderer/base.js +17 -2
  56. package/dist/_pkgs/data-view/core/group-by/renderer/number-group.js +1 -1
  57. package/dist/_pkgs/data-view/core/group-by/renderer/select-group.d.ts +0 -3
  58. package/dist/_pkgs/data-view/core/group-by/renderer/select-group.js +63 -118
  59. package/dist/_pkgs/data-view/core/group-by/renderer/string-group.js +1 -1
  60. package/dist/_pkgs/data-view/core/group-by/setting.js +2 -2
  61. package/dist/_pkgs/data-view/core/utils/wording.d.ts +6 -2
  62. package/dist/_pkgs/data-view/core/utils/wording.js +5 -3
  63. package/dist/_pkgs/data-view/core/view/default-name.d.ts +6 -0
  64. package/dist/_pkgs/data-view/core/view/default-name.js +8 -0
  65. package/dist/_pkgs/data-view/translations.d.ts +45 -0
  66. package/dist/_pkgs/data-view/translations.js +118 -0
  67. package/dist/_pkgs/data-view/view-presets/flat/flat-view-ui.d.ts +3 -1
  68. package/dist/_pkgs/data-view/view-presets/flat/flat-view-ui.js +5 -1
  69. package/dist/_pkgs/data-view/view-presets/kanban/mobile/card.d.ts +6 -0
  70. package/dist/_pkgs/data-view/view-presets/kanban/mobile/card.js +11 -0
  71. package/dist/_pkgs/data-view/view-presets/kanban/mobile/cell.js +9 -10
  72. package/dist/_pkgs/data-view/view-presets/kanban/mobile/kanban-view-ui-logic.js +3 -0
  73. package/dist/_pkgs/data-view/view-presets/table/mobile/cell.js +7 -4
  74. package/dist/_pkgs/data-view/view-presets/table/mobile/column-header.d.ts +3 -1
  75. package/dist/_pkgs/data-view/view-presets/table/mobile/column-header.js +102 -124
  76. package/dist/_pkgs/data-view/view-presets/table/mobile/group.js +12 -7
  77. package/dist/_pkgs/data-view/view-presets/table/mobile/header.d.ts +8 -2
  78. package/dist/_pkgs/data-view/view-presets/table/mobile/header.js +64 -24
  79. package/dist/_pkgs/data-view/view-presets/table/mobile/menu.d.ts +3 -1
  80. package/dist/_pkgs/data-view/view-presets/table/mobile/menu.js +7 -3
  81. package/dist/_pkgs/data-view/view-presets/table/mobile/row.d.ts +23 -1
  82. package/dist/_pkgs/data-view/view-presets/table/mobile/row.js +138 -55
  83. package/dist/_pkgs/data-view/view-presets/table/mobile/table-view-style.d.ts +17 -0
  84. package/dist/_pkgs/data-view/view-presets/table/mobile/table-view-style.js +31 -6
  85. package/dist/_pkgs/data-view/view-presets/table/mobile/table-view-ui-logic.d.ts +5 -1
  86. package/dist/_pkgs/data-view/view-presets/table/mobile/table-view-ui-logic.js +11 -4
  87. package/dist/_pkgs/data-view/view-presets/table/pc/group.js +4 -2
  88. package/dist/_pkgs/data-view/view-presets/table/pc/row/row.js +5 -1
  89. package/dist/_pkgs/data-view/widget-presets/quick-setting-bar/filter/index.js +1 -2
  90. package/dist/_pkgs/data-view/widget-presets/quick-setting-bar/filter/list-view.d.ts +3 -1
  91. package/dist/_pkgs/data-view/widget-presets/quick-setting-bar/filter/list-view.js +23 -3
  92. package/dist/_pkgs/data-view/widget-presets/quick-setting-bar/index.js +8 -8
  93. package/dist/_pkgs/data-view/widget-presets/tools/presets/filter/filter.d.ts +3 -0
  94. package/dist/_pkgs/data-view/widget-presets/tools/presets/filter/filter.js +125 -60
  95. package/dist/_pkgs/data-view/widget-presets/tools/presets/search/search.d.ts +3 -0
  96. package/dist/_pkgs/data-view/widget-presets/tools/presets/search/search.js +37 -5
  97. package/dist/_pkgs/data-view/widget-presets/tools/presets/sort/sort.d.ts +3 -0
  98. package/dist/_pkgs/data-view/widget-presets/tools/presets/sort/sort.js +131 -66
  99. package/dist/_pkgs/data-view/widget-presets/tools/presets/table-add-row/add-row.d.ts +3 -0
  100. package/dist/_pkgs/data-view/widget-presets/tools/presets/table-add-row/add-row.js +143 -40
  101. package/dist/_pkgs/data-view/widget-presets/tools/presets/view-options/view-options.js +30 -22
  102. package/dist/_pkgs/data-view/widget-presets/tools/presets/view-options/view-settings.js +8 -6
  103. package/dist/_pkgs/data-view/widget-presets/views-bar/views-view.js +15 -0
  104. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { ColorScheme } from '../../affine-model/index.js';
1
+ import { ColorScheme, ConnectorElementModel, FrameworkBackgroundElementModel, } from '../../affine-model/index.js';
2
2
  import { FeatureFlagService } from '../../affine-shared/services/index.js';
3
3
  import { requestConnectedFrame } from '../../affine-shared/utils/index.js';
4
4
  import { DisposableGroup } from '../../global/disposable/index.js';
@@ -9,9 +9,26 @@ import last from 'lodash-es/last.js';
9
9
  import { Subject } from 'rxjs';
10
10
  import { ElementRendererIdentifier } from '../extensions/element-renderer.js';
11
11
  import { RoughCanvas } from '../utils/rough/canvas.js';
12
+ import { NotationDarkPaletteIdentifier, NotationTint, } from './notation-tint.js';
12
13
  import { isPainted } from './paint-predicate.js';
14
+ /** The stored fill an element paints as its body, if it paints one. */
15
+ function paperOf(element) {
16
+ const { filled, fillColor } = element;
17
+ return filled && typeof fillColor === 'string' && fillColor
18
+ ? fillColor
19
+ : null;
20
+ }
13
21
  const STACKING_CANVAS_PADDING = 32;
14
22
  export class CanvasRenderer {
23
+ /**
24
+ * The dark notation table while a NOTATION element is being painted on a
25
+ * dark canvas, `null` otherwise (light scheme, a neutral element, outside a
26
+ * paint). Painters read it through `notationColor` and
27
+ * {@link getColorValue}; see `notation-tint.ts` and ADR 0039.
28
+ */
29
+ get notationTint() {
30
+ return this._notationTint;
31
+ }
15
32
  get stackingCanvas() {
16
33
  return this._stackingCanvas;
17
34
  }
@@ -44,6 +61,11 @@ export class CanvasRenderer {
44
61
  };
45
62
  this.stackingCanvasUpdated = new Subject();
46
63
  this.usePlaceholder = false;
64
+ /** Set by {@link withColorScheme} for the length of one paint. */
65
+ this._schemeOverride = null;
66
+ /** Built on the first dark paint, from the registered framework entries. */
67
+ this._darkTint = null;
68
+ this._notationTint = null;
47
69
  this._lastDebugSnapshot = {
48
70
  canvasMemoryBytes: 0,
49
71
  canvasMemorySnapshots: [],
@@ -72,6 +94,7 @@ export class CanvasRenderer {
72
94
  if (options.enableStackingCanvas) {
73
95
  this._initStackingCanvas(options.onStackingCanvasCreated);
74
96
  }
97
+ this._surface = options.surfaceModel;
75
98
  this._watchSurface(options.surfaceModel);
76
99
  }
77
100
  /**
@@ -440,6 +463,9 @@ export class CanvasRenderer {
440
463
  this.grid.search(bound, {
441
464
  filter: ['canvas', 'local'],
442
465
  });
466
+ const tint = this._tintForPass();
467
+ let boards = null;
468
+ const papers = [];
443
469
  for (const element of elements) {
444
470
  const display = isPainted(element, this._gfx.localVisibility);
445
471
  if (display && intersects(getBoundWithRotation(element), bound)) {
@@ -463,7 +489,24 @@ export class CanvasRenderer {
463
489
  ctx.globalAlpha = element.opacity ?? 1;
464
490
  const dx = element.x - bound.x;
465
491
  const dy = element.y - bound.y;
466
- renderFn(element, ctx, matrix.translate(dx, dy), this, rc, bound);
492
+ if (tint) {
493
+ // Every board of the surface, not of this pass: a stacking canvas
494
+ // paints one layer, and the board may sit in another.
495
+ boards ??= this._surface.elementModels.filter((e) => e instanceof FrameworkBackgroundElementModel);
496
+ this._notationTint =
497
+ this._isNotation(element, boards) &&
498
+ !this._sitsOnKeptPaper(element, papers, tint)
499
+ ? tint
500
+ : null;
501
+ if (paperOf(element))
502
+ papers.push(element);
503
+ }
504
+ try {
505
+ renderFn(element, ctx, matrix.translate(dx, dy), this, rc, bound);
506
+ }
507
+ finally {
508
+ this._notationTint = null;
509
+ }
467
510
  ctx.restore();
468
511
  }
469
512
  }
@@ -557,13 +600,103 @@ export class CanvasRenderer {
557
600
  this._renderByBound(ctx, matrix, rc, bound, surfaceElements);
558
601
  }
559
602
  getColorScheme() {
560
- return this.provider.getColorScheme?.() ?? ColorScheme.Light;
603
+ return (this._schemeOverride ??
604
+ this.provider.getColorScheme?.() ??
605
+ ColorScheme.Light);
606
+ }
607
+ /**
608
+ * A stored colour, resolved for the scheme in force.
609
+ *
610
+ * While a notation element is painted on a dark canvas, a PLAIN stored value
611
+ * (a string: what every framework writes) goes through the dark notation
612
+ * table as `use`; a `{ light, dark }` pair already knows its dark side and
613
+ * is never tinted, nor is a fallback (ADR 0039).
614
+ */
615
+ getColorValue(color, fallback, real, use = 'ink') {
616
+ const value = this.provider.getColorValue?.(color, fallback, real, this._schemeOverride ?? undefined) ?? 'transparent';
617
+ return this._notationTint && typeof color === 'string' && color
618
+ ? this._notationTint.resolve(value, use)
619
+ : value;
561
620
  }
562
- getColorValue(color, fallback, real) {
563
- return (this.provider.getColorValue?.(color, fallback, real) ?? 'transparent');
621
+ /**
622
+ * The colour of words drawn on a fill of the same element: tinted like any
623
+ * ink, unless the fill is painted as stored, in which case the words keep
624
+ * the colour they were chosen for on it ("ink follows its paper", ADR 0039).
625
+ * `fill` is `null` when the element draws no fill.
626
+ */
627
+ getInkColorValue(color, fallback, fill) {
628
+ const tint = this._notationTint;
629
+ if (tint && typeof fill === 'string' && fill && tint.keepsInkOn(fill)) {
630
+ return (this.provider.getColorValue?.(color, fallback, true, this._schemeOverride ?? undefined) ?? 'transparent');
631
+ }
632
+ return this.getColorValue(color, fallback, true);
564
633
  }
565
634
  getPropertyValue(property) {
566
- return this.provider.getPropertyValue?.(property) ?? '';
635
+ return (this.provider.getPropertyValue?.(property, this._schemeOverride ?? undefined) ?? '');
636
+ }
637
+ /**
638
+ * Paint with `scheme` whatever the canvas shows. The SVG export paints
639
+ * light: a file is printed on white and read back by colour (ADR 0039).
640
+ */
641
+ withColorScheme(scheme, paint) {
642
+ const previous = this._schemeOverride;
643
+ this._schemeOverride = scheme;
644
+ try {
645
+ return paint();
646
+ }
647
+ finally {
648
+ this._schemeOverride = previous;
649
+ }
650
+ }
651
+ /**
652
+ * "Ink follows its paper" for words that are elements of their own (a C4
653
+ * title, an Event Storming label): text with no fill of its own, drawn on
654
+ * the topmost filled element under it that keeps its stored fill, keeps its
655
+ * stored colour too (ADR 0039). `papers` are the filled elements painted
656
+ * before it in this pass, in z-order.
657
+ */
658
+ _sitsOnKeptPaper(element, papers, tint) {
659
+ if (paperOf(element))
660
+ return false;
661
+ const bound = element.elementBound;
662
+ for (let i = papers.length - 1; i >= 0; i--) {
663
+ const paper = papers[i];
664
+ if (paper.elementBound.contains(bound)) {
665
+ return tint.keepsInkOn(paperOf(paper));
666
+ }
667
+ }
668
+ return false;
669
+ }
670
+ /** The dark table when this pass paints dark, else `null`. */
671
+ _tintForPass() {
672
+ if (this.getColorScheme() !== ColorScheme.Dark)
673
+ return null;
674
+ this._darkTint ??= new NotationTint([
675
+ ...this.std.provider.getAll(NotationDarkPaletteIdentifier).values(),
676
+ ]);
677
+ return this._darkTint;
678
+ }
679
+ /**
680
+ * Whether an element is part of a notation, read off the document as the
681
+ * colour carousel reads it (ADR 0027): its own role or its being a board,
682
+ * else (a connector) a role at either end, else a framework board it sits
683
+ * inside. A neutral element anywhere else is painted exactly as stored.
684
+ */
685
+ _isNotation(element, boards) {
686
+ if (element.role)
687
+ return true;
688
+ // A board is notation whether or not it stamps a role (Cynefin does not).
689
+ if (element instanceof FrameworkBackgroundElementModel)
690
+ return true;
691
+ if (element instanceof ConnectorElementModel) {
692
+ for (const end of [element.source?.id, element.target?.id]) {
693
+ const model = end ? this._gfx.getElementById(end) : null;
694
+ if (model?.role)
695
+ return true;
696
+ }
697
+ }
698
+ const bound = element.elementBound;
699
+ return boards.some(board => board !== element && board.elementBound.contains(bound));
567
700
  }
568
701
  getDebugMetrics() {
569
702
  return {
@@ -0,0 +1,92 @@
1
+ import type { FrameworkId } from '../../std/index.js';
2
+ import type { ExtensionType } from '../../store/index.js';
3
+ /**
4
+ * The dark variant of the framework notations, resolved AT RENDER TIME
5
+ * (ADR 0039).
6
+ *
7
+ * A notation element stores plain colour values (`'#438dd5'`, the scale's
8
+ * `'#1f2328'`). On a dark canvas those values are not rewritten: the painter
9
+ * asks this table what to paint instead, so the document, the undo stack, the
10
+ * sync and every reader of a stored colour see exactly what they saw before.
11
+ *
12
+ * The rule is "the paper inverts, the hues stay":
13
+ *
14
+ * - the shared neutrals ({@link NOTATION_NEUTRALS}) map key for key onto
15
+ * {@link NOTATION_NEUTRALS_DARK};
16
+ * - a framework adds the few values of its own that would vanish on the dark
17
+ * card (a stencil ink, a near-black label, a pale wash behind words) through
18
+ * {@link NotationDarkPaletteExtension};
19
+ * - everything else, every hue of a standard included, is painted as stored.
20
+ * EDGY's facets, the C4 ladder, a BPMN end event and a colour typed by hand
21
+ * all stay what they are.
22
+ *
23
+ * White is the one value with two jobs: as a SURFACE (a node body, the board
24
+ * card) it is the paper and inverts; as an INK (white words on a C4 person,
25
+ * EDGY's white pictos) it stays. Hence the two uses below.
26
+ */
27
+ /** What a colour is painted as: a line or words, or a surface. */
28
+ export type NotationColorUse = 'ink' | 'fill';
29
+ /**
30
+ * One framework's dark entries, light value → dark value. Keys are `#rrggbb`
31
+ * (any case); a stored `#rrggbbaa` keeps its alpha.
32
+ *
33
+ * `kept` lists the swatches of the framework's palette that deliberately have
34
+ * no dark entry (a standard's hue that reads on the dark card as it is), so a
35
+ * swatch added later without a decision fails `notation-dark-palettes`.
36
+ */
37
+ export type NotationDarkPalette = {
38
+ framework: FrameworkId;
39
+ colors: Readonly<Record<string, string>>;
40
+ kept?: readonly string[];
41
+ };
42
+ /**
43
+ * Multi-instance, keyed by framework, like `FrameworkPaletteIdentifier`: two
44
+ * registrations for one framework collide at set-up.
45
+ */
46
+ export declare const NotationDarkPaletteIdentifier: import("../../global/di/types.js").ServiceIdentifier<NotationDarkPalette> & (<U extends NotationDarkPalette = NotationDarkPalette>(variant: import("../../global/di/types.js").ServiceVariant) => import("../../global/di/types.js").ServiceIdentifier<U>);
47
+ /**
48
+ * Register a framework's dark entries. Call it from the ALWAYS-ON render view
49
+ * extension, beside the renderers: painting a stored document is content, so
50
+ * it never depends on the framework's flag (ADR 0009).
51
+ */
52
+ export declare function NotationDarkPaletteExtension(palette: NotationDarkPalette): ExtensionType;
53
+ /**
54
+ * A resolved dark table: the neutrals plus every registered framework's
55
+ * entries. Pure; the canvas renderer builds one per editor and hands it to
56
+ * the painters of notation elements only.
57
+ */
58
+ export declare class NotationTint {
59
+ private readonly _inks;
60
+ private readonly _surfaces;
61
+ /**
62
+ * `palettes` in any order. A light value two frameworks map differently is
63
+ * a declaration bug; the first one wins here and
64
+ * `notation-dark-palettes.unit.spec.ts` names it.
65
+ */
66
+ constructor(palettes?: readonly NotationDarkPalette[]);
67
+ /**
68
+ * What to paint for a stored or declared `value`. Anything that is not a
69
+ * hex literal (`rgba(…)`, `transparent`, a variable) is returned untouched.
70
+ */
71
+ resolve(value: string, use?: NotationColorUse): string;
72
+ /**
73
+ * Whether words drawn on this fill keep their stored colour ("ink follows
74
+ * its paper"): true when the fill is painted as stored and is opaque enough
75
+ * to be what the words sit on. A fill that inverts, or a wash below half
76
+ * opacity, puts the words on the dark card, so they invert too.
77
+ *
78
+ * This is what keeps black words on a C4 component and dark words on an
79
+ * Event Storming sticky readable while the same black on the bare card turns
80
+ * light.
81
+ */
82
+ keepsInkOn(fill: string): boolean;
83
+ }
84
+ /**
85
+ * The colour a painter of a notation element should use for a CONSTANT it
86
+ * holds (a board's declared wash, a glyph's stencil ink).
87
+ *
88
+ * `renderer` is optional and duck-typed on purpose: a unit test drives a
89
+ * painter with a bare stub, or with no renderer at all, and both must paint
90
+ * the value as given.
91
+ */
92
+ export declare function notationColor(renderer: unknown, value: string, use?: NotationColorUse): string;
@@ -0,0 +1,113 @@
1
+ import { NOTATION_NEUTRALS, NOTATION_NEUTRALS_DARK, } from '../../affine-shared/consts/index.js';
2
+ import { createIdentifier } from '../../global/di/index.js';
3
+ /**
4
+ * Multi-instance, keyed by framework, like `FrameworkPaletteIdentifier`: two
5
+ * registrations for one framework collide at set-up.
6
+ */
7
+ export const NotationDarkPaletteIdentifier = createIdentifier('NotationDarkPalette');
8
+ /**
9
+ * Register a framework's dark entries. Call it from the ALWAYS-ON render view
10
+ * extension, beside the renderers: painting a stored document is content, so
11
+ * it never depends on the framework's flag (ADR 0009).
12
+ */
13
+ export function NotationDarkPaletteExtension(palette) {
14
+ return {
15
+ setup: di => {
16
+ di.addImpl(NotationDarkPaletteIdentifier(palette.framework), () => palette);
17
+ },
18
+ };
19
+ }
20
+ const HEX = /^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
21
+ /** `#rgb` / `#rrggbb` / `#rrggbbaa` → lower-case `#rrggbb` and the alpha tail. */
22
+ function splitHex(value) {
23
+ const match = HEX.exec(value.trim());
24
+ if (!match)
25
+ return null;
26
+ const digits = match[1].toLowerCase();
27
+ if (digits.length === 3) {
28
+ return { rgb: `#${[...digits].map(d => d + d).join('')}`, alpha: '' };
29
+ }
30
+ return { rgb: `#${digits.slice(0, 6)}`, alpha: digits.slice(6) };
31
+ }
32
+ function normalised(table) {
33
+ const out = new Map();
34
+ for (const [light, dark] of Object.entries(table)) {
35
+ const key = splitHex(light);
36
+ if (key)
37
+ out.set(key.rgb, dark.toLowerCase());
38
+ }
39
+ return out;
40
+ }
41
+ const NEUTRAL_KEYS = Object.keys(NOTATION_NEUTRALS);
42
+ /** The scale, light → dark, minus the card: white is a surface only. */
43
+ const NEUTRAL_INKS = Object.fromEntries(NEUTRAL_KEYS.filter(key => key !== 'cardFill').map(key => [
44
+ NOTATION_NEUTRALS[key],
45
+ NOTATION_NEUTRALS_DARK[key],
46
+ ]));
47
+ /**
48
+ * A resolved dark table: the neutrals plus every registered framework's
49
+ * entries. Pure; the canvas renderer builds one per editor and hands it to
50
+ * the painters of notation elements only.
51
+ */
52
+ export class NotationTint {
53
+ /**
54
+ * `palettes` in any order. A light value two frameworks map differently is
55
+ * a declaration bug; the first one wins here and
56
+ * `notation-dark-palettes.unit.spec.ts` names it.
57
+ */
58
+ constructor(palettes = []) {
59
+ this._inks = normalised(NEUTRAL_INKS);
60
+ for (const palette of palettes) {
61
+ for (const [light, dark] of normalised(palette.colors)) {
62
+ if (!this._inks.has(light))
63
+ this._inks.set(light, dark);
64
+ }
65
+ }
66
+ this._surfaces = normalised({
67
+ [NOTATION_NEUTRALS.cardFill]: NOTATION_NEUTRALS_DARK.cardFill,
68
+ });
69
+ }
70
+ /**
71
+ * What to paint for a stored or declared `value`. Anything that is not a
72
+ * hex literal (`rgba(…)`, `transparent`, a variable) is returned untouched.
73
+ */
74
+ resolve(value, use = 'ink') {
75
+ const parts = splitHex(value);
76
+ if (!parts)
77
+ return value;
78
+ const dark = (use === 'fill' ? this._surfaces.get(parts.rgb) : undefined) ??
79
+ this._inks.get(parts.rgb);
80
+ return dark ? dark + parts.alpha : value;
81
+ }
82
+ /**
83
+ * Whether words drawn on this fill keep their stored colour ("ink follows
84
+ * its paper"): true when the fill is painted as stored and is opaque enough
85
+ * to be what the words sit on. A fill that inverts, or a wash below half
86
+ * opacity, puts the words on the dark card, so they invert too.
87
+ *
88
+ * This is what keeps black words on a C4 component and dark words on an
89
+ * Event Storming sticky readable while the same black on the bare card turns
90
+ * light.
91
+ */
92
+ keepsInkOn(fill) {
93
+ const parts = splitHex(fill);
94
+ if (!parts)
95
+ return false;
96
+ if (parts.alpha && Number.parseInt(parts.alpha, 16) < 128)
97
+ return false;
98
+ return this.resolve(fill, 'fill') === fill;
99
+ }
100
+ }
101
+ /**
102
+ * The colour a painter of a notation element should use for a CONSTANT it
103
+ * holds (a board's declared wash, a glyph's stencil ink).
104
+ *
105
+ * `renderer` is optional and duck-typed on purpose: a unit test drives a
106
+ * painter with a bare stub, or with no renderer at all, and both must paint
107
+ * the value as given.
108
+ */
109
+ export function notationColor(renderer, value, use = 'ink') {
110
+ const tint = renderer
111
+ ?.notationTint;
112
+ return tint ? tint.resolve(value, use) : value;
113
+ }
@@ -152,9 +152,9 @@ let SurfaceBlockComponent = (() => {
152
152
  gridManager: gfx.grid,
153
153
  provider: {
154
154
  generateColorProperty: (color, fallback) => themeService.generateColorProperty(color, fallback, themeService.edgelessTheme),
155
- getColorValue: (color, fallback, real) => themeService.getColorValue(color, fallback, real, themeService.edgelessTheme),
155
+ getColorValue: (color, fallback, real, theme) => themeService.getColorValue(color, fallback, real, theme ?? themeService.edgelessTheme),
156
156
  getColorScheme: () => themeService.edgelessTheme,
157
- getPropertyValue: (property) => themeService.getCssVariableColor(property, themeService.edgelessTheme),
157
+ getPropertyValue: (property, theme) => themeService.getCssVariableColor(property, theme ?? themeService.edgelessTheme),
158
158
  selectedElements: () => gfx.selection.selectedIds,
159
159
  },
160
160
  surfaceModel: this.model,
@@ -76,7 +76,7 @@ export const COLOR_PICKER_STYLE = css `
76
76
  box-sizing: border-box;
77
77
 
78
78
  font-weight: 400;
79
- color: #8e8d91;
79
+ color: var(--affine-text-secondary-color);
80
80
  }
81
81
  .modes .mode.light button {
82
82
  background: white;
@@ -77,7 +77,8 @@ let MenuInput = (() => {
77
77
 
78
78
  .affine-menu-input:focus {
79
79
  border: 1px solid ${unsafeCSSVarV2('layer/insideBorder/primaryBorder')};
80
- box-shadow: 0px 0px 0px 2px rgba(28, 158, 228, 0.3);
80
+ box-shadow: 0px 0px 0px 2px
81
+ color-mix(in srgb, var(--affine-primary-color) 30%, transparent);
81
82
  }
82
83
  `; }
83
84
  complete() {
@@ -34,6 +34,11 @@ export declare class MobileMenuComponent extends MobileMenuComponent_base implem
34
34
  getFirstFocusableElement(): HTMLElement | null;
35
35
  getFocusableElements(): HTMLElement[];
36
36
  render(): import("lit-html").TemplateResult<1>;
37
+ /**
38
+ * Back, title, "Done": the two ends share what the title leaves, so the
39
+ * title stays centred, and "Done" is never cut, whatever its translation
40
+ * ("Terminé" overflowed the 50px it had).
41
+ */
37
42
  renderTitle(): import("lit-html").TemplateResult<1>;
38
43
  accessor menu: Menu;
39
44
  }
@@ -48,6 +53,8 @@ export type PopupTarget = {
48
53
  targetRect: ReferenceElement;
49
54
  root: HTMLElement;
50
55
  popupStart: () => () => void;
56
+ /** The element the popup opens from, when there is one. */
57
+ element?: HTMLElement;
51
58
  };
52
59
  export declare const popupTargetFromElement: (element: HTMLElement) => PopupTarget;
53
60
  export declare const createPopup: (target: PopupTarget, content: HTMLElement, options?: {
@@ -40,7 +40,7 @@ import { ArrowLeftBigIcon, ArrowLeftSmallIcon, CloseIcon, SearchIcon, } from '@b
40
40
  import { ShadowlessElement, stdContext } from '../../std/index.js';
41
41
  import { RANGE_SYNC_EXCLUDE_ATTR } from '../../std/inline/index.js';
42
42
  import { autoPlacement, autoUpdate, computePosition, offset, shift, } from '@floating-ui/dom';
43
- import { consume } from '@lit/context';
43
+ import { consume, ContextEvent, ContextProvider } from '@lit/context';
44
44
  import { css, html, nothing } from 'lit';
45
45
  import { property } from 'lit/decorators.js';
46
46
  import { createRef, ref } from 'lit/directives/ref.js';
@@ -299,17 +299,22 @@ let MobileMenuComponent = (() => {
299
299
  get std() { return this.#std_accessor_storage; }
300
300
  set std(value) { this.#std_accessor_storage = value; }
301
301
  static { this.styles = css `
302
+ /* A bottom action sheet, not a full-screen page: the view stays visible
303
+ above it and a tap on the scrim closes it (popMobileMenu). */
302
304
  mobile-menu {
303
- height: 100%;
305
+ max-height: 85%;
304
306
  font-family: var(--affine-font-family);
305
307
  display: flex;
306
308
  flex-direction: column;
307
309
  user-select: none;
308
310
  width: 100%;
309
311
  background-color: ${unsafeCSSVarV2('layer/background/secondary')};
310
- padding: calc(8px + env(safe-area-inset-top, 0px)) 8px
311
- calc(8px + env(safe-area-inset-bottom, 0px)) 8px;
312
+ padding: 8px 8px calc(8px + env(safe-area-inset-bottom, 0px)) 8px;
312
313
  position: absolute;
314
+ left: 0;
315
+ bottom: 0;
316
+ border-radius: 12px 12px 0 0;
317
+ box-shadow: var(--affine-overlay-shadow);
313
318
  z-index: 999;
314
319
  color: ${unsafeCSSVarV2('text/primary')};
315
320
  }
@@ -339,6 +344,11 @@ let MobileMenuComponent = (() => {
339
344
  <div class="mobile-menu-body">${result}</div>
340
345
  `;
341
346
  }
347
+ /**
348
+ * Back, title, "Done": the two ends share what the title leaves, so the
349
+ * title stays centred, and "Done" is never cut, whatever its translation
350
+ * ("Terminé" overflowed the 50px it had).
351
+ */
342
352
  renderTitle() {
343
353
  const title = this.menu.options.title;
344
354
  return html `
@@ -346,7 +356,7 @@ let MobileMenuComponent = (() => {
346
356
  style="display:flex;align-items:center;height: 44px;"
347
357
  @mouseenter="${() => this.menu.closeSubMenu()}"
348
358
  >
349
- <div style="width: 50px;flex-shrink: 0;margin-left: 10px;">
359
+ <div style="flex: 1 1 0;min-width: 50px;margin-left: 10px;">
350
360
  ${title?.onBack
351
361
  ? html ` <div
352
362
  @click="${() => {
@@ -365,27 +375,33 @@ let MobileMenuComponent = (() => {
365
375
  </div>
366
376
  <div
367
377
  style="
368
- flex:1;
378
+ flex: 0 1 auto;
379
+ min-width: 0;
380
+ overflow: hidden;
381
+ text-overflow: ellipsis;
382
+ white-space: nowrap;
369
383
  font-size: 17px;
370
384
  font-style: normal;
371
385
  font-weight: 500;
372
386
  line-height: 22px;
373
387
  color: var(--affine-text-primary-color);
374
- display: flex;
375
- justify-content: center;
388
+ text-align: center;
376
389
  "
377
390
  >
378
391
  ${title?.text}
379
392
  </div>
380
393
  <div
381
394
  @click="${this.onClose}"
395
+ data-testid="mobile-menu-done"
382
396
  style="
383
397
  display:flex;
398
+ justify-content: flex-end;
384
399
  font-weight: 500;
385
400
  font-size: 17px;
386
401
  color: ${unsafeCSSVarV2('button/primary')};
387
- width: 50px;
388
- flex-shrink: 0;
402
+ flex: 1 1 0;
403
+ min-width: max-content;
404
+ white-space: nowrap;
389
405
  margin-right: 10px;
390
406
  "
391
407
  >
@@ -452,6 +468,7 @@ export const popupTargetFromElement = (element) => {
452
468
  },
453
469
  },
454
470
  root: getDefaultModalRoot(element),
471
+ element,
455
472
  popupStart: () => {
456
473
  if (!count) {
457
474
  isActive = element.classList.contains('active');
@@ -509,9 +526,32 @@ export const createPopup = (target, content, options) => {
509
526
  };
510
527
  return close;
511
528
  };
512
- const popMobileMenu = (options) => {
529
+ /** The editor an element sits in, asked the way a `@consume` asks. */
530
+ const stdAround = (element) => {
531
+ let std;
532
+ element?.dispatchEvent(new ContextEvent(stdContext, element, value => {
533
+ std = value;
534
+ }));
535
+ return std;
536
+ };
537
+ /**
538
+ * A phone's sheet hangs from the body, out of the editor's DOM, where nothing
539
+ * provides `std`: its modal provides the editor of the element it opens from,
540
+ * so the sheet's "Done" and whatever it renders speak the host's catalogue,
541
+ * and a sub-menu opened from it finds the same editor again.
542
+ */
543
+ const popMobileMenu = (options, target) => {
513
544
  const model = createModal(document.body);
545
+ const std = stdAround(target?.element);
546
+ if (std) {
547
+ new ContextProvider(model, { context: stdContext, initialValue: std });
548
+ }
514
549
  model.style.position = 'fixed';
550
+ model.style.background = 'var(--affine-v2-layer-background-mobile-modal)';
551
+ model.addEventListener('click', e => {
552
+ if (e.target === model)
553
+ menu.close();
554
+ });
515
555
  const menu = new Menu({
516
556
  ...options,
517
557
  onClose: () => {
@@ -530,13 +570,13 @@ const popMobileMenu = (options) => {
530
570
  menu,
531
571
  reopen: () => {
532
572
  menu.close();
533
- popMobileMenu(options);
573
+ popMobileMenu(options, target);
534
574
  },
535
575
  };
536
576
  };
537
577
  export const popMenu = (target, props) => {
538
578
  if (IS_MOBILE) {
539
- return popMobileMenu(props.options);
579
+ return popMobileMenu(props.options, target);
540
580
  }
541
581
  const popupEnd = target.popupStart();
542
582
  const onClose = () => {
@@ -1,4 +1,5 @@
1
1
  import { NoteBlockModel } from '../../affine-model/index.js';
2
+ import { pageDropVerdict } from '../../affine-shared/services/index.js';
2
3
  import { calcDropTarget, getClosestBlockComponentByPoint, isInsidePageEditor, matchModels, } from '../../affine-shared/utils/index.js';
3
4
  import { createIdentifier } from '../../global/di/index.js';
4
5
  import { Point } from '../../global/gfx/index.js';
@@ -59,7 +60,21 @@ export class FileDropExtension extends LifeCycleWatcher {
59
60
  this.point$.value = null;
60
61
  };
61
62
  this.onDragOver = (event) => {
63
+ // A page the host refuses here (`PageDropProvider`): no drop line, and
64
+ // no preventDefault, so the browser shows its no-drop cursor and never
65
+ // fires the drop.
66
+ const verdict = pageDropVerdict(this.std, event.dataTransfer);
67
+ if (verdict === 'refuse') {
68
+ if (event.dataTransfer)
69
+ event.dataTransfer.dropEffect = 'none';
70
+ this.onDragLeave();
71
+ return;
72
+ }
62
73
  event.preventDefault();
74
+ // The host's drag allows 'move' only: say so, or the drop never fires.
75
+ if (verdict === 'accept' && event.dataTransfer) {
76
+ event.dataTransfer.dropEffect = 'move';
77
+ }
63
78
  if (this.shouldIgnoreEvent(event))
64
79
  return;
65
80
  this.updatePoint(event);
@@ -18,8 +18,10 @@ const shapeRenderers = {
18
18
  polygon,
19
19
  };
20
20
  export const shape = (model, ctx, matrix, renderer, rc) => {
21
- const color = renderer.getColorValue(model.color, DefaultTheme.shapeTextColor, true);
22
- const fillColor = renderer.getColorValue(model.fillColor, DefaultTheme.shapeFillColor, true);
21
+ // A notation body is a surface: on a dark canvas a white one inverts with
22
+ // the paper, and the words on it follow their paper (ADR 0039).
23
+ const fillColor = renderer.getColorValue(model.fillColor, DefaultTheme.shapeFillColor, true, 'fill');
24
+ const color = renderer.getInkColorValue(model.color, DefaultTheme.shapeTextColor, model.filled ? model.fillColor : null);
23
25
  const strokeColor = renderer.getColorValue(model.strokeColor, DefaultTheme.shapeStrokeColor, true);
24
26
  const colors = { color, fillColor, strokeColor };
25
27
  shapeRenderers[model.shapeType](model, ctx, matrix, renderer, rc, colors);
@@ -34,7 +34,7 @@ const editLinkStyle = css `
34
34
  .affine-link-edit-popover input::placeholder {
35
35
  color: var(--affine-placeholder-color);
36
36
  }
37
- input:focus {
37
+ link-popup input:focus {
38
38
  outline: none;
39
39
  }
40
40
  .affine-link-edit-popover input:focus ~ label,