@formicoidea/labre-core 0.46.2 → 0.47.1

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 (91) hide show
  1. package/dist/_pkgs/affine-block-attachment/configs/slash-menu.js +2 -2
  2. package/dist/_pkgs/affine-block-attachment/translations.js +2 -2
  3. package/dist/_pkgs/affine-block-data-view/columns/index.d.ts +1 -1
  4. package/dist/_pkgs/affine-block-database/data-source.d.ts +1 -1
  5. package/dist/_pkgs/affine-block-database/properties/converts.d.ts +4 -4
  6. package/dist/_pkgs/affine-block-database/properties/index.d.ts +1 -1
  7. package/dist/_pkgs/affine-block-embed/embed-html-block/configs/toolbar.js +1 -1
  8. package/dist/_pkgs/affine-block-embed/embed-iframe-block/configs/toolbar.d.ts +1 -1
  9. package/dist/_pkgs/affine-block-embed/embed-iframe-block/configs/toolbar.js +1 -1
  10. package/dist/_pkgs/affine-block-embed/translations.d.ts +1 -1
  11. package/dist/_pkgs/affine-block-embed/translations.js +2 -2
  12. package/dist/_pkgs/affine-block-embed-doc/embed-linked-doc-block/configs/slash-menu.js +6 -6
  13. package/dist/_pkgs/affine-block-embed-doc/embed-linked-doc-block/configs/toolbar.js +1 -1
  14. package/dist/_pkgs/affine-block-embed-doc/embed-synced-doc-block/configs/toolbar.js +3 -3
  15. package/dist/_pkgs/affine-block-embed-doc/translations.d.ts +1 -1
  16. package/dist/_pkgs/affine-block-embed-doc/translations.js +8 -8
  17. package/dist/_pkgs/affine-block-frame/commands.js +41 -1
  18. package/dist/_pkgs/affine-block-frame/frame-manager.d.ts +1 -1
  19. package/dist/_pkgs/affine-block-frame/frame-toolbar.js +2 -2
  20. package/dist/_pkgs/affine-block-frame/translations.js +1 -1
  21. package/dist/_pkgs/affine-block-frame/view.js +7 -2
  22. package/dist/_pkgs/affine-block-note/components/edgeless-page-block-title.d.ts +7 -0
  23. package/dist/_pkgs/affine-block-note/components/edgeless-page-block-title.js +15 -1
  24. package/dist/_pkgs/affine-block-note/translations.js +8 -8
  25. package/dist/_pkgs/affine-block-root/commands/copy-link-commands.js +2 -2
  26. package/dist/_pkgs/affine-block-root/configs/toolbar.d.ts +1 -1
  27. package/dist/_pkgs/affine-block-root/configs/toolbar.js +1 -1
  28. package/dist/_pkgs/affine-block-root/edgeless/configs/toolbar/misc.d.ts +1 -1
  29. package/dist/_pkgs/affine-block-root/edgeless/configs/toolbar/more.d.ts +1 -1
  30. package/dist/_pkgs/affine-block-root/edgeless/configs/toolbar/more.js +1 -1
  31. package/dist/_pkgs/affine-block-root/translations.d.ts +1 -1
  32. package/dist/_pkgs/affine-block-root/translations.js +2 -2
  33. package/dist/_pkgs/affine-block-surface/extensions/canvas-grid/commands.js +21 -7
  34. package/dist/_pkgs/affine-block-surface/extensions/export-manager/commands.d.ts +2 -0
  35. package/dist/_pkgs/affine-block-surface/extensions/export-manager/commands.js +79 -0
  36. package/dist/_pkgs/affine-block-surface/extensions/export-manager/export-manager.d.ts +18 -1
  37. package/dist/_pkgs/affine-block-surface/extensions/export-manager/export-manager.js +53 -30
  38. package/dist/_pkgs/affine-block-surface/extensions/export-manager/index.d.ts +1 -0
  39. package/dist/_pkgs/affine-block-surface/extensions/export-manager/index.js +1 -0
  40. package/dist/_pkgs/affine-block-surface/extensions/interchange-import.js +1 -1
  41. package/dist/_pkgs/affine-block-surface/extensions/selection-pane/commands.js +1 -1
  42. package/dist/_pkgs/affine-block-surface/renderer/element-highlight-overlay.d.ts +8 -2
  43. package/dist/_pkgs/affine-block-surface/renderer/element-highlight-overlay.js +11 -13
  44. package/dist/_pkgs/affine-block-surface/view.js +11 -2
  45. package/dist/_pkgs/affine-block-surface-ref/translations.js +8 -8
  46. package/dist/_pkgs/affine-components/translations.js +6 -6
  47. package/dist/_pkgs/affine-fragment-doc-title/doc-title.d.ts +8 -0
  48. package/dist/_pkgs/affine-fragment-doc-title/doc-title.js +65 -4
  49. package/dist/_pkgs/affine-fragment-doc-title/effects.d.ts +2 -0
  50. package/dist/_pkgs/affine-fragment-doc-title/effects.js +2 -0
  51. package/dist/_pkgs/affine-fragment-doc-title/index.d.ts +2 -0
  52. package/dist/_pkgs/affine-fragment-doc-title/index.js +2 -0
  53. package/dist/_pkgs/affine-fragment-doc-title/title-band/format.d.ts +69 -0
  54. package/dist/_pkgs/affine-fragment-doc-title/title-band/format.js +182 -0
  55. package/dist/_pkgs/affine-fragment-doc-title/title-band/title-band.d.ts +62 -0
  56. package/dist/_pkgs/affine-fragment-doc-title/title-band/title-band.js +649 -0
  57. package/dist/_pkgs/affine-fragment-doc-title/translations.d.ts +21 -0
  58. package/dist/_pkgs/affine-fragment-doc-title/translations.js +58 -0
  59. package/dist/_pkgs/affine-gfx-group/toolbar/config.d.ts +2 -2
  60. package/dist/_pkgs/affine-gfx-group/toolbar/config.js +2 -2
  61. package/dist/_pkgs/affine-gfx-group/translations.js +1 -1
  62. package/dist/_pkgs/affine-gfx-note/translations.js +2 -2
  63. package/dist/_pkgs/affine-shared/services/index.d.ts +2 -0
  64. package/dist/_pkgs/affine-shared/services/index.js +2 -0
  65. package/dist/_pkgs/affine-shared/services/linked-doc-creation-service.d.ts +1 -1
  66. package/dist/_pkgs/affine-shared/services/properties-panel-service.d.ts +50 -0
  67. package/dist/_pkgs/affine-shared/services/properties-panel-service.js +17 -0
  68. package/dist/_pkgs/affine-shared/services/title-band-service.d.ts +124 -0
  69. package/dist/_pkgs/affine-shared/services/title-band-service.js +17 -0
  70. package/dist/_pkgs/affine-shared/services/translation-service/chrome.d.ts +3 -3
  71. package/dist/_pkgs/affine-shared/services/translation-service/chrome.js +17 -17
  72. package/dist/_pkgs/affine-widget-edgeless-auto-connect/translations.js +2 -2
  73. package/dist/_pkgs/affine-widget-edgeless-selected-rect/translations.js +1 -1
  74. package/dist/_pkgs/affine-widget-linked-doc/translations.js +3 -3
  75. package/dist/_pkgs/affine-widget-slash-menu/translations.js +1 -1
  76. package/dist/_pkgs/data-view/property-presets/converts.d.ts +2 -2
  77. package/dist/_pkgs/data-view/property-presets/index.d.ts +1 -1
  78. package/dist/_pkgs/data-view/property-presets/number/cell-renderer.d.ts +1 -1
  79. package/dist/_pkgs/data-view/property-presets/number/define.d.ts +1 -1
  80. package/dist/_pkgs/data-view/property-presets/number/types.d.ts +2 -2
  81. package/dist/_pkgs/data-view/property-presets/pure-index.d.ts +2 -1
  82. package/dist/_pkgs/data-view/property-presets/pure-index.js +4 -0
  83. package/dist/_pkgs/std/extension/command-registry.d.ts +50 -1
  84. package/dist/_pkgs/std/extension/command-registry.js +26 -7
  85. package/dist/commands.js +2 -1
  86. package/dist/host-panels.d.ts +83 -2
  87. package/dist/host-panels.js +82 -1
  88. package/dist/translations.js +1 -1
  89. package/package.json +1 -1
  90. package/dist/_pkgs/affine-block-surface/extensions/export-manager/file-exporter.d.ts +0 -26
  91. package/dist/_pkgs/affine-block-surface/extensions/export-manager/file-exporter.js +0 -73
@@ -0,0 +1,649 @@
1
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
2
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
3
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
4
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
5
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
6
+ var _, done = false;
7
+ for (var i = decorators.length - 1; i >= 0; i--) {
8
+ var context = {};
9
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
10
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
11
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
12
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
13
+ if (kind === "accessor") {
14
+ if (result === void 0) continue;
15
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
16
+ if (_ = accept(result.get)) descriptor.get = _;
17
+ if (_ = accept(result.set)) descriptor.set = _;
18
+ if (_ = accept(result.init)) initializers.unshift(_);
19
+ }
20
+ else if (_ = accept(result)) {
21
+ if (kind === "field") initializers.unshift(_);
22
+ else descriptor[key] = _;
23
+ }
24
+ }
25
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
26
+ done = true;
27
+ };
28
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
29
+ var useValue = arguments.length > 2;
30
+ for (var i = 0; i < initializers.length; i++) {
31
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
32
+ }
33
+ return useValue ? value : void 0;
34
+ };
35
+ import { menu, popMenu, popupTargetFromElement, } from '../../affine-components/context-menu/index.js';
36
+ import { DatePicker } from '../../affine-components/date-picker/index.js';
37
+ import { RefNodeSlotsProvider } from '../../affine-inline-reference/index.js';
38
+ import { DOC_UNTITLED, formatLocale, PropertiesPanelProvider, TitleBandProvider, translateKey, } from '../../affine-shared/services/index.js';
39
+ import { stopPropagation } from '../../affine-shared/utils/index.js';
40
+ import { SignalWatcher, WithDisposable } from '../../global/lit/index.js';
41
+ import { ShadowlessElement } from '../../std/index.js';
42
+ import { CheckBoxCheckSolidIcon, CheckBoxUnIcon, LinkedPageIcon, PlusIcon, SidebarIcon, ToggleDownIcon, ToggleRightIcon, } from '@blocksuite/icons/lit';
43
+ import { computed, signal } from '@preact/signals-core';
44
+ import { css, html, nothing } from 'lit';
45
+ import { property } from 'lit/decorators.js';
46
+ import { repeat } from 'lit/directives/repeat.js';
47
+ import { styleMap } from 'lit/directives/style-map.js';
48
+ import { TITLE_BAND_ADD, TITLE_BAND_CLEAR, TITLE_BAND_COLLAPSE, TITLE_BAND_EMPTY, TITLE_BAND_EXPAND, TITLE_BAND_LABEL, TITLE_BAND_LOADING, TITLE_BAND_NOT_SHOWN, TITLE_BAND_OPEN_PANEL, } from '../translations.js';
49
+ import { chipEditorKind, chipOptions, chipValueView, parseChipNumber, } from './format.js';
50
+ export const LABRE_TITLE_BAND = 'labre-title-band';
51
+ const NO_PAGES = [];
52
+ /**
53
+ * The band of property chips under the page title and at the head of the
54
+ * page's note on the canvas (ADR 0035 §11, REQ-PR-1 and PR-7).
55
+ *
56
+ * One component for both views: `<doc-title>` mounts it under the title
57
+ * (`surface="page"`), `edgeless-page-block-title` at the head of the page's
58
+ * note (`surface="canvas"`). On the canvas it sits inside the note's own DOM,
59
+ * so it follows the zoom like the rest of the note (REQ-PR-7), and it is
60
+ * interactive when the note is, i.e. once the note is being edited.
61
+ *
62
+ * Fed by `TitleBandProvider` alone (§9d): no provider, nothing at all. The
63
+ * chips are the sheet's visible properties in the host's order, an empty one
64
+ * included (REQ-PR-1); a click opens the editor of the property's data-view
65
+ * type and hands the value back through `set`, showing the chip as pending
66
+ * until the host settles it. The band never writes to the document and never
67
+ * applies a value. The "+" is drawn when the host passes `add`; the panel
68
+ * button when a `PropertiesPanelProvider` is registered; the "Not shown in
69
+ * this document" row only on the canvas (the page view's row is the host's).
70
+ */
71
+ let TitleBand = (() => {
72
+ let _classSuper = SignalWatcher(WithDisposable(ShadowlessElement));
73
+ let _std_decorators;
74
+ let _std_initializers = [];
75
+ let _std_extraInitializers = [];
76
+ let _docId_decorators;
77
+ let _docId_initializers = [];
78
+ let _docId_extraInitializers = [];
79
+ let _surface_decorators;
80
+ let _surface_initializers = [];
81
+ let _surface_extraInitializers = [];
82
+ return class TitleBand extends _classSuper {
83
+ static {
84
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
85
+ _std_decorators = [property({ attribute: false })];
86
+ _docId_decorators = [property({ attribute: false })];
87
+ _surface_decorators = [property({ attribute: false })];
88
+ __esDecorate(this, null, _std_decorators, { kind: "accessor", name: "std", static: false, private: false, access: { has: obj => "std" in obj, get: obj => obj.std, set: (obj, value) => { obj.std = value; } }, metadata: _metadata }, _std_initializers, _std_extraInitializers);
89
+ __esDecorate(this, null, _docId_decorators, { kind: "accessor", name: "docId", static: false, private: false, access: { has: obj => "docId" in obj, get: obj => obj.docId, set: (obj, value) => { obj.docId = value; } }, metadata: _metadata }, _docId_initializers, _docId_extraInitializers);
90
+ __esDecorate(this, null, _surface_decorators, { kind: "accessor", name: "surface", static: false, private: false, access: { has: obj => "surface" in obj, get: obj => obj.surface, set: (obj, value) => { obj.surface = value; } }, metadata: _metadata }, _surface_initializers, _surface_extraInitializers);
91
+ if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
92
+ }
93
+ static { this.styles = css `
94
+ labre-title-band {
95
+ display: block;
96
+ }
97
+
98
+ .labre-title-band {
99
+ display: flex;
100
+ flex-direction: column;
101
+ gap: 6px;
102
+ font-family: var(--affine-font-family);
103
+ font-size: var(--affine-font-sm);
104
+ line-height: 20px;
105
+ color: var(--affine-text-primary-color);
106
+ }
107
+
108
+ .labre-title-band[data-surface='canvas'] {
109
+ padding-bottom: 12px;
110
+ }
111
+
112
+ .labre-title-band-row {
113
+ display: flex;
114
+ flex-wrap: wrap;
115
+ align-items: center;
116
+ gap: 4px;
117
+ min-height: 28px;
118
+ }
119
+
120
+ .labre-title-band button {
121
+ all: unset;
122
+ box-sizing: border-box;
123
+ display: inline-flex;
124
+ align-items: center;
125
+ gap: 6px;
126
+ height: 28px;
127
+ max-width: 100%;
128
+ padding: 0 8px;
129
+ border-radius: 6px;
130
+ cursor: pointer;
131
+ color: inherit;
132
+ transition: background-color 150ms;
133
+ }
134
+
135
+ .labre-title-band button:hover {
136
+ background-color: var(--affine-hover-color);
137
+ }
138
+
139
+ .labre-title-band button:focus-visible {
140
+ outline: 2px solid var(--affine-primary-color);
141
+ outline-offset: 1px;
142
+ }
143
+
144
+ .labre-title-band button[disabled] {
145
+ cursor: progress;
146
+ opacity: 0.6;
147
+ }
148
+
149
+ .labre-title-band button.static {
150
+ cursor: default;
151
+ }
152
+
153
+ .labre-title-band button.static:hover {
154
+ background-color: transparent;
155
+ }
156
+
157
+ .labre-title-band svg {
158
+ flex: none;
159
+ width: 16px;
160
+ height: 16px;
161
+ }
162
+
163
+ .labre-title-band-icon-button {
164
+ color: var(--affine-icon-color) !important;
165
+ padding: 0 6px !important;
166
+ }
167
+
168
+ .labre-title-band-chip-name {
169
+ color: var(--affine-text-secondary-color);
170
+ white-space: nowrap;
171
+ }
172
+
173
+ .labre-title-band-chip-value {
174
+ display: inline-flex;
175
+ align-items: center;
176
+ gap: 4px;
177
+ overflow: hidden;
178
+ text-overflow: ellipsis;
179
+ white-space: nowrap;
180
+ max-width: 240px;
181
+ }
182
+
183
+ .labre-title-band-chip[data-empty='true'] .labre-title-band-chip-value {
184
+ color: var(--affine-placeholder-color);
185
+ }
186
+
187
+ .labre-title-band-tag {
188
+ display: inline-block;
189
+ padding: 0 6px;
190
+ border-radius: 4px;
191
+ max-width: 160px;
192
+ overflow: hidden;
193
+ text-overflow: ellipsis;
194
+ white-space: nowrap;
195
+ background-color: var(--affine-tag-white);
196
+ }
197
+
198
+ .labre-title-band-skeleton {
199
+ display: inline-block;
200
+ width: 96px;
201
+ height: 20px;
202
+ border-radius: 6px;
203
+ background-color: var(--affine-hover-color);
204
+ }
205
+
206
+ .labre-title-band-not-shown-label {
207
+ color: var(--affine-text-secondary-color);
208
+ padding-right: 4px;
209
+ }
210
+
211
+ @media (prefers-reduced-motion: reduce) {
212
+ .labre-title-band button {
213
+ transition: none;
214
+ }
215
+ }
216
+
217
+ /* A 44px target under a coarse pointer, without changing the drawing. */
218
+ @media (pointer: coarse) {
219
+ .labre-title-band button {
220
+ position: relative;
221
+ }
222
+ .labre-title-band button::after {
223
+ content: '';
224
+ position: absolute;
225
+ inset: -8px 0;
226
+ }
227
+ }
228
+ `; }
229
+ _pop(target, title, items) {
230
+ this._menu?.close();
231
+ const handler = popMenu(popupTargetFromElement(target), {
232
+ options: {
233
+ title: { text: title },
234
+ items,
235
+ onClose: () => {
236
+ if (this._menu === handler)
237
+ this._menu = null;
238
+ },
239
+ },
240
+ });
241
+ this._menu = handler;
242
+ return handler;
243
+ }
244
+ /** Read inside `render` it subscribes, through `SignalWatcher`. */
245
+ get _readonly() {
246
+ return this.std?.store.readonly$.value ?? true;
247
+ }
248
+ get _locale() {
249
+ return this.std ? formatLocale(this.std) : 'en-US';
250
+ }
251
+ _t(wording, count) {
252
+ if (!this.std)
253
+ return wording[1];
254
+ return translateKey(this.std, wording[0], wording[1], count === undefined ? undefined : { count });
255
+ }
256
+ _getSources(service) {
257
+ const cached = this._sources;
258
+ if (cached && cached.service === service && cached.docId === this.docId) {
259
+ return cached;
260
+ }
261
+ const docId = this.docId;
262
+ const sources = {
263
+ service,
264
+ docId,
265
+ sheet$: service.sheet$(docId),
266
+ collapsed$: service.collapsed$?.(docId) ?? null,
267
+ notShown$: this.surface === 'canvas' ? (service.notShown$?.(docId) ?? null) : null,
268
+ };
269
+ this._sources = sources;
270
+ return sources;
271
+ }
272
+ _isCollapsed(sources) {
273
+ return sources.collapsed$?.value ?? this._localCollapsed$.value;
274
+ }
275
+ _setCollapsed(sources, collapsed) {
276
+ this._localCollapsed$.value = collapsed;
277
+ sources.service.setCollapsed?.(sources.docId, collapsed);
278
+ }
279
+ _write(property, value) {
280
+ const sources = this._sources;
281
+ if (!sources || property.readonly || this.std?.store.readonly !== false)
282
+ return;
283
+ if (this._pending$.peek().has(property.id))
284
+ return;
285
+ const { id } = property;
286
+ const settle = () => {
287
+ const next = new Set(this._pending$.peek());
288
+ next.delete(id);
289
+ this._pending$.value = next;
290
+ };
291
+ this._pending$.value = new Set([...this._pending$.peek(), id]);
292
+ let written;
293
+ try {
294
+ written = sources.service.set(sources.docId, id, value);
295
+ }
296
+ catch (error) {
297
+ settle();
298
+ console.error('[title-band] set threw', error);
299
+ return;
300
+ }
301
+ // The host reports and rolls back a refused write; the band only stops
302
+ // showing it as pending.
303
+ Promise.resolve(written).then(settle, (error) => {
304
+ settle();
305
+ console.error('[title-band] set was refused', error);
306
+ });
307
+ }
308
+ _tagLabel(tag) {
309
+ return html `<span
310
+ class="labre-title-band-tag"
311
+ style=${styleMap(tag.color ? { backgroundColor: tag.color } : {})}
312
+ >${tag.label}</span
313
+ >`;
314
+ }
315
+ _clearItem(prop) {
316
+ return menu.action({
317
+ name: this._t(TITLE_BAND_CLEAR),
318
+ select: () => this._write(prop, null),
319
+ });
320
+ }
321
+ _popSelect(target, prop) {
322
+ const items = chipOptions(prop).map(tag => menu.action({
323
+ name: tag.label,
324
+ label: () => this._tagLabel(tag),
325
+ isSelected: tag.id === prop.value,
326
+ select: () => {
327
+ if (tag.id !== prop.value)
328
+ this._write(prop, tag.id);
329
+ },
330
+ }));
331
+ if (prop.value != null)
332
+ items.push(this._clearItem(prop));
333
+ this._pop(target, prop.name, items);
334
+ }
335
+ _popMultiSelect(target, prop) {
336
+ // A draft, because the host's answer arrives later through `sheet$`: two
337
+ // ticks in one open menu must not both start from the stale value.
338
+ const draft$ = signal(Array.isArray(prop.value)
339
+ ? prop.value.filter((id) => typeof id === 'string')
340
+ : []);
341
+ const items = chipOptions(prop).map(tag => menu.checkbox({
342
+ name: tag.label,
343
+ label: () => this._tagLabel(tag),
344
+ checked: computed(() => draft$.value.includes(tag.id)),
345
+ select: checked => {
346
+ const next = checked
347
+ ? draft$.peek().filter(id => id !== tag.id)
348
+ : [...draft$.peek(), tag.id];
349
+ draft$.value = next;
350
+ this._write(prop, next);
351
+ return !checked;
352
+ },
353
+ }));
354
+ this._pop(target, prop.name, items);
355
+ }
356
+ _popDate(target, prop) {
357
+ let close = () => { };
358
+ const picker = new DatePicker();
359
+ picker.padding = 0;
360
+ picker.value = typeof prop.value === 'number' ? prop.value : Date.now();
361
+ picker.onChange = (date) => {
362
+ if (date.getTime() !== prop.value)
363
+ this._write(prop, date.getTime());
364
+ close();
365
+ };
366
+ picker.onClear = () => {
367
+ this._write(prop, null);
368
+ close();
369
+ };
370
+ picker.onEscape = () => close();
371
+ const items = [() => html `${picker}`];
372
+ const handler = this._pop(target, prop.name, items);
373
+ close = () => handler.close();
374
+ }
375
+ _popInput(target, prop, kind) {
376
+ const initial = typeof prop.value === 'string' || typeof prop.value === 'number'
377
+ ? String(prop.value)
378
+ : '';
379
+ this._pop(target, prop.name, [
380
+ menu.input({
381
+ initialValue: initial,
382
+ onComplete: text => {
383
+ if (text === initial)
384
+ return;
385
+ if (kind === 'number') {
386
+ const parsed = parseChipNumber(text);
387
+ this._write(prop, Number.isNaN(parsed) ? null : parsed);
388
+ return;
389
+ }
390
+ this._write(prop, text.trim() === '' ? null : text);
391
+ },
392
+ }),
393
+ ]);
394
+ }
395
+ _renderValue(prop) {
396
+ const view = chipValueView(prop, this._locale);
397
+ switch (view.kind) {
398
+ case 'checkbox':
399
+ return view.checked ? CheckBoxCheckSolidIcon() : CheckBoxUnIcon();
400
+ case 'tags':
401
+ return repeat(view.tags, tag => tag.id, tag => this._tagLabel(tag));
402
+ case 'text':
403
+ return view.text;
404
+ default:
405
+ return this._t(TITLE_BAND_EMPTY);
406
+ }
407
+ }
408
+ _renderChip(prop, hasPanel) {
409
+ const pending = this._pending$.value.has(prop.id);
410
+ const editable = !this._readonly && chipEditorKind(prop) !== 'none';
411
+ const clickable = editable || hasPanel;
412
+ const view = chipValueView(prop, this._locale);
413
+ return html `<button
414
+ class="labre-title-band-chip ${clickable ? '' : 'static'}"
415
+ data-testid="title-band-chip"
416
+ data-property-id=${prop.id}
417
+ data-empty=${view.kind === 'empty' ? 'true' : 'false'}
418
+ aria-busy=${pending ? 'true' : 'false'}
419
+ ?disabled=${pending || !clickable}
420
+ @click=${(event) => this._onChip(event, prop)}
421
+ >
422
+ <span class="labre-title-band-chip-name">${prop.name}</span>
423
+ <span class="labre-title-band-chip-value"
424
+ >${this._renderValue(prop)}</span
425
+ >
426
+ </button>`;
427
+ }
428
+ _renderTools(sources, hasPanel) {
429
+ const canAdd = !!sources.service.add && !this._readonly;
430
+ return html `${canAdd
431
+ ? html `<button
432
+ class="labre-title-band-icon-button"
433
+ data-testid="title-band-add"
434
+ aria-label=${this._t(TITLE_BAND_ADD)}
435
+ title=${this._t(TITLE_BAND_ADD)}
436
+ @click=${(event) => {
437
+ event.stopPropagation();
438
+ sources.service.add?.(sources.docId);
439
+ }}
440
+ >
441
+ ${PlusIcon()}
442
+ </button>`
443
+ : nothing}
444
+ ${hasPanel
445
+ ? html `<button
446
+ class="labre-title-band-icon-button"
447
+ data-testid="title-band-open-panel"
448
+ aria-label=${this._t(TITLE_BAND_OPEN_PANEL)}
449
+ title=${this._t(TITLE_BAND_OPEN_PANEL)}
450
+ @click=${(event) => {
451
+ event.stopPropagation();
452
+ this._openPanel();
453
+ }}
454
+ >
455
+ ${SidebarIcon()}
456
+ </button>`
457
+ : nothing}`;
458
+ }
459
+ _renderSheet(sources) {
460
+ const state = sources.sheet$.value;
461
+ if (state.status === 'loading') {
462
+ return html `<div
463
+ class="labre-title-band-row"
464
+ role="status"
465
+ aria-busy="true"
466
+ aria-label=${this._t(TITLE_BAND_LOADING)}
467
+ data-testid="title-band-loading"
468
+ >
469
+ <span class="labre-title-band-skeleton"></span>
470
+ <span class="labre-title-band-skeleton"></span>
471
+ </div>`;
472
+ }
473
+ if (state.status !== 'ready')
474
+ return nothing;
475
+ const hasPanel = !!this.std?.getOptional(PropertiesPanelProvider);
476
+ const { properties } = state;
477
+ const canAdd = !!sources.service.add && !this._readonly;
478
+ if (properties.length === 0 && !canAdd && !hasPanel)
479
+ return nothing;
480
+ if (properties.length > 0 && this._isCollapsed(sources)) {
481
+ return html `<div
482
+ class="labre-title-band-row"
483
+ role="group"
484
+ aria-label=${this._t(TITLE_BAND_LABEL)}
485
+ >
486
+ <button
487
+ data-testid="title-band-expand"
488
+ aria-expanded="false"
489
+ @click=${(event) => {
490
+ event.stopPropagation();
491
+ this._setCollapsed(sources, false);
492
+ }}
493
+ >
494
+ ${ToggleRightIcon()}
495
+ <span class="labre-title-band-chip-name"
496
+ >${this._t(TITLE_BAND_EXPAND, properties.length)}</span
497
+ >
498
+ </button>
499
+ ${this._renderTools(sources, hasPanel)}
500
+ </div>`;
501
+ }
502
+ return html `<div
503
+ class="labre-title-band-row"
504
+ role="group"
505
+ aria-label=${this._t(TITLE_BAND_LABEL)}
506
+ >
507
+ ${properties.length > 0
508
+ ? html `<button
509
+ class="labre-title-band-icon-button"
510
+ data-testid="title-band-collapse"
511
+ aria-expanded="true"
512
+ aria-label=${this._t(TITLE_BAND_COLLAPSE)}
513
+ title=${this._t(TITLE_BAND_COLLAPSE)}
514
+ @click=${(event) => {
515
+ event.stopPropagation();
516
+ this._setCollapsed(sources, true);
517
+ }}
518
+ >
519
+ ${ToggleDownIcon()}
520
+ </button>`
521
+ : nothing}
522
+ ${repeat(properties, prop => prop.id, prop => this._renderChip(prop, hasPanel))}
523
+ ${this._renderTools(sources, hasPanel)}
524
+ </div>`;
525
+ }
526
+ _renderNotShown(sources) {
527
+ const pages = sources.notShown$?.value ?? NO_PAGES;
528
+ const std = this.std;
529
+ const slots = std?.getOptional(RefNodeSlotsProvider);
530
+ // A link that opens nothing is a lie: no linked-document path, no row.
531
+ if (!std || !slots || pages.length === 0)
532
+ return nothing;
533
+ return html `<div
534
+ class="labre-title-band-row"
535
+ data-testid="title-band-not-shown"
536
+ >
537
+ <span class="labre-title-band-not-shown-label"
538
+ >${this._t(TITLE_BAND_NOT_SHOWN)}</span
539
+ >
540
+ ${repeat(pages, page => page.docId, page => html `<button
541
+ data-testid="title-band-not-shown-page"
542
+ data-doc-id=${page.docId}
543
+ @click=${(event) => {
544
+ event.stopPropagation();
545
+ slots.docLinkClicked.next({
546
+ pageId: page.docId,
547
+ host: std.host,
548
+ event,
549
+ });
550
+ }}
551
+ >
552
+ ${LinkedPageIcon()}
553
+ <span class="labre-title-band-chip-value"
554
+ >${page.title.trim() === ''
555
+ ? this._t(DOC_UNTITLED)
556
+ : page.title}</span
557
+ >
558
+ </button>`)}
559
+ </div>`;
560
+ }
561
+ connectedCallback() {
562
+ super.connectedCallback();
563
+ // The band lives inside the title and inside the note: a key or a press
564
+ // on one of its buttons is the band's, not the title editor's (Enter would
565
+ // split the title) nor the canvas's (a press would start a drag).
566
+ this._disposables.addFromEvent(this, 'keydown', stopPropagation);
567
+ this._disposables.addFromEvent(this, 'pointerdown', stopPropagation);
568
+ this._disposables.add(() => this._menu?.close());
569
+ }
570
+ render() {
571
+ const std = this.std;
572
+ if (!std || !this.docId)
573
+ return nothing;
574
+ const service = std.getOptional(TitleBandProvider);
575
+ if (!service)
576
+ return nothing;
577
+ const sources = this._getSources(service);
578
+ const sheet = this._renderSheet(sources);
579
+ const notShown = this.surface === 'canvas' ? this._renderNotShown(sources) : nothing;
580
+ if (sheet === nothing && notShown === nothing)
581
+ return nothing;
582
+ return html `<div
583
+ class="labre-title-band"
584
+ data-testid="title-band"
585
+ data-surface=${this.surface}
586
+ >
587
+ ${sheet} ${notShown}
588
+ </div>`;
589
+ }
590
+ #std_accessor_storage;
591
+ get std() { return this.#std_accessor_storage; }
592
+ set std(value) { this.#std_accessor_storage = value; }
593
+ #docId_accessor_storage;
594
+ get docId() { return this.#docId_accessor_storage; }
595
+ set docId(value) { this.#docId_accessor_storage = value; }
596
+ #surface_accessor_storage;
597
+ /** Where the band is drawn; only the canvas draws the "Not shown" row. */
598
+ get surface() { return this.#surface_accessor_storage; }
599
+ set surface(value) { this.#surface_accessor_storage = value; }
600
+ constructor() {
601
+ super(...arguments);
602
+ this._sources = null;
603
+ /** The editor menu open now, put away with the band. */
604
+ this._menu = null;
605
+ /** Collapse state when the host does not remember one. */
606
+ this._localCollapsed$ = signal(false);
607
+ /** Property ids whose `set` has not settled yet. */
608
+ this._pending$ = signal(new Set());
609
+ this._onChip = (event, prop) => {
610
+ event.stopPropagation();
611
+ const target = event.currentTarget;
612
+ switch (chipEditorKind(prop)) {
613
+ case 'toggle':
614
+ this._write(prop, prop.value !== true);
615
+ return;
616
+ case 'select':
617
+ this._popSelect(target, prop);
618
+ return;
619
+ case 'multi-select':
620
+ this._popMultiSelect(target, prop);
621
+ return;
622
+ case 'date':
623
+ this._popDate(target, prop);
624
+ return;
625
+ case 'number':
626
+ this._popInput(target, prop, 'number');
627
+ return;
628
+ case 'text':
629
+ this._popInput(target, prop, 'text');
630
+ return;
631
+ default:
632
+ this._openPanel();
633
+ }
634
+ };
635
+ this._openPanel = () => {
636
+ if (!this.std)
637
+ return;
638
+ this.std
639
+ .getOptional(PropertiesPanelProvider)
640
+ ?.open({ scope: 'page', docId: this.docId });
641
+ };
642
+ this.#std_accessor_storage = __runInitializers(this, _std_initializers, null);
643
+ this.#docId_accessor_storage = (__runInitializers(this, _std_extraInitializers), __runInitializers(this, _docId_initializers, ''));
644
+ this.#surface_accessor_storage = (__runInitializers(this, _docId_extraInitializers), __runInitializers(this, _surface_initializers, 'page'));
645
+ __runInitializers(this, _surface_extraInitializers);
646
+ }
647
+ };
648
+ })();
649
+ export { TitleBand };
@@ -9,5 +9,26 @@ import type { ChromeWording } from '../affine-shared/services/index.js';
9
9
  * `content: attr(...)` can read.
10
10
  */
11
11
  export declare const DOC_TITLE_PLACEHOLDER: ChromeWording;
12
+ /** The band's accessible name: the group of property chips. */
13
+ export declare const TITLE_BAND_LABEL: ChromeWording;
14
+ /** What an empty chip says after the property's name (REQ-PR-1). */
15
+ export declare const TITLE_BAND_EMPTY: ChromeWording;
16
+ /** The "+" (REQ-PR-4); what it does is the host's. */
17
+ export declare const TITLE_BAND_ADD: ChromeWording;
18
+ /** The button beside the "+" that opens the host's properties panel. */
19
+ export declare const TITLE_BAND_OPEN_PANEL: ChromeWording;
20
+ /** Folds the chips away. */
21
+ export declare const TITLE_BAND_COLLAPSE: ChromeWording;
22
+ /** The folded band's only button; `count` is the number of chips. */
23
+ export declare const TITLE_BAND_EXPAND: ChromeWording;
24
+ /** The accessible name of the skeleton drawn while the sheet loads. */
25
+ export declare const TITLE_BAND_LOADING: ChromeWording;
26
+ /** Clears a select or a date from its editor. */
27
+ export declare const TITLE_BAND_CLEAR: ChromeWording;
28
+ /**
29
+ * The note header's row of child pages the document holds but does not
30
+ * display (ADR 0035 §9d, canvas view only).
31
+ */
32
+ export declare const TITLE_BAND_NOT_SHOWN: ChromeWording;
12
33
  /** Every wording this package declares, in the order it renders them. */
13
34
  export declare const DOC_TITLE_WORDINGS: readonly ChromeWording[];