@formicoidea/labre-core 0.47.0 → 0.48.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/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-database/view.js +5 -0
  4. package/dist/_pkgs/affine-block-embed/embed-html-block/configs/toolbar.js +1 -1
  5. package/dist/_pkgs/affine-block-embed/embed-iframe-block/configs/toolbar.d.ts +1 -1
  6. package/dist/_pkgs/affine-block-embed/embed-iframe-block/configs/toolbar.js +1 -1
  7. package/dist/_pkgs/affine-block-embed/translations.d.ts +1 -1
  8. package/dist/_pkgs/affine-block-embed/translations.js +2 -2
  9. package/dist/_pkgs/affine-block-embed-doc/embed-linked-doc-block/configs/new-doc.d.ts +25 -0
  10. package/dist/_pkgs/affine-block-embed-doc/embed-linked-doc-block/configs/new-doc.js +71 -0
  11. package/dist/_pkgs/affine-block-embed-doc/embed-linked-doc-block/configs/slash-menu.js +9 -17
  12. package/dist/_pkgs/affine-block-embed-doc/embed-linked-doc-block/configs/toolbar.js +1 -1
  13. package/dist/_pkgs/affine-block-embed-doc/embed-synced-doc-block/configs/toolbar.js +3 -3
  14. package/dist/_pkgs/affine-block-embed-doc/translations.d.ts +1 -1
  15. package/dist/_pkgs/affine-block-embed-doc/translations.js +8 -8
  16. package/dist/_pkgs/affine-block-frame/frame-toolbar.js +2 -2
  17. package/dist/_pkgs/affine-block-frame/translations.js +1 -1
  18. package/dist/_pkgs/affine-block-note/translations.js +8 -8
  19. package/dist/_pkgs/affine-block-root/commands/copy-link-commands.js +2 -2
  20. package/dist/_pkgs/affine-block-root/configs/toolbar.d.ts +1 -1
  21. package/dist/_pkgs/affine-block-root/configs/toolbar.js +1 -1
  22. package/dist/_pkgs/affine-block-root/edgeless/configs/toolbar/misc.d.ts +1 -1
  23. package/dist/_pkgs/affine-block-root/edgeless/configs/toolbar/more.d.ts +1 -1
  24. package/dist/_pkgs/affine-block-root/edgeless/configs/toolbar/more.js +1 -1
  25. package/dist/_pkgs/affine-block-root/translations.d.ts +1 -1
  26. package/dist/_pkgs/affine-block-root/translations.js +2 -2
  27. package/dist/_pkgs/affine-block-surface/extensions/canvas-grid/commands.js +1 -1
  28. package/dist/_pkgs/affine-block-surface/extensions/interchange-import.js +1 -1
  29. package/dist/_pkgs/affine-block-surface/extensions/selection-pane/commands.js +1 -1
  30. package/dist/_pkgs/affine-block-surface/renderer/element-highlight-overlay.d.ts +8 -2
  31. package/dist/_pkgs/affine-block-surface/renderer/element-highlight-overlay.js +11 -13
  32. package/dist/_pkgs/affine-block-surface-ref/translations.js +8 -8
  33. package/dist/_pkgs/affine-components/translations.js +6 -6
  34. package/dist/_pkgs/affine-fragment-doc-title/index.d.ts +1 -1
  35. package/dist/_pkgs/affine-fragment-doc-title/index.js +1 -1
  36. package/dist/_pkgs/affine-fragment-doc-title/title-band/band-cell.d.ts +22 -0
  37. package/dist/_pkgs/affine-fragment-doc-title/title-band/band-cell.js +125 -0
  38. package/dist/_pkgs/affine-fragment-doc-title/title-band/format.d.ts +7 -19
  39. package/dist/_pkgs/affine-fragment-doc-title/title-band/format.js +0 -38
  40. package/dist/_pkgs/affine-fragment-doc-title/title-band/show-here.d.ts +19 -0
  41. package/dist/_pkgs/affine-fragment-doc-title/title-band/show-here.js +61 -0
  42. package/dist/_pkgs/affine-fragment-doc-title/title-band/title-band.d.ts +28 -18
  43. package/dist/_pkgs/affine-fragment-doc-title/title-band/title-band.js +225 -207
  44. package/dist/_pkgs/affine-fragment-doc-title/translations.d.ts +7 -4
  45. package/dist/_pkgs/affine-fragment-doc-title/translations.js +12 -9
  46. package/dist/_pkgs/affine-gfx-group/toolbar/config.d.ts +2 -2
  47. package/dist/_pkgs/affine-gfx-group/toolbar/config.js +2 -2
  48. package/dist/_pkgs/affine-gfx-group/translations.js +1 -1
  49. package/dist/_pkgs/affine-gfx-note/translations.js +2 -2
  50. package/dist/_pkgs/affine-shared/services/linked-doc-creation-service.d.ts +15 -6
  51. package/dist/_pkgs/affine-shared/services/title-band-service.d.ts +10 -7
  52. package/dist/_pkgs/affine-shared/services/translation-service/chrome.d.ts +3 -3
  53. package/dist/_pkgs/affine-shared/services/translation-service/chrome.js +17 -17
  54. package/dist/_pkgs/affine-shared/utils/model/doc.d.ts +10 -2
  55. package/dist/_pkgs/affine-shared/utils/model/doc.js +17 -7
  56. package/dist/_pkgs/affine-widget-edgeless-auto-connect/translations.js +2 -2
  57. package/dist/_pkgs/affine-widget-edgeless-selected-rect/translations.js +1 -1
  58. package/dist/_pkgs/affine-widget-linked-doc/translations.js +3 -3
  59. package/dist/_pkgs/affine-widget-slash-menu/translations.js +1 -1
  60. package/dist/_pkgs/data-view/core/component/tags/multi-tag-select.d.ts +7 -2
  61. package/dist/_pkgs/data-view/core/component/tags/multi-tag-select.js +22 -15
  62. package/dist/_pkgs/data-view/core/effect.js +2 -0
  63. package/dist/_pkgs/data-view/core/property/index.d.ts +1 -0
  64. package/dist/_pkgs/data-view/core/property/index.js +1 -0
  65. package/dist/_pkgs/data-view/core/property/standalone-cell.d.ts +95 -0
  66. package/dist/_pkgs/data-view/core/property/standalone-cell.js +306 -0
  67. package/dist/_pkgs/data-view/core/view-manager/property.d.ts +6 -0
  68. package/dist/_pkgs/data-view/property-presets/multi-select/cell-renderer.js +1 -1
  69. package/dist/_pkgs/data-view/property-presets/select/cell-renderer.js +1 -1
  70. package/dist/_pkgs/data-view/property-presets/text/cell-renderer.js +3 -1
  71. package/dist/translations.js +1 -1
  72. package/package.json +1 -1
@@ -32,21 +32,21 @@ var __runInitializers = (this && this.__runInitializers) || function (thisArg, i
32
32
  }
33
33
  return useValue ? value : void 0;
34
34
  };
35
- import { menu, popMenu, popupTargetFromElement, } from '../../affine-components/context-menu/index.js';
36
- import { DatePicker } from '../../affine-components/date-picker/index.js';
37
35
  import { RefNodeSlotsProvider } from '../../affine-inline-reference/index.js';
38
- import { DOC_UNTITLED, formatLocale, PropertiesPanelProvider, TitleBandProvider, translateKey, } from '../../affine-shared/services/index.js';
36
+ import { DOC_UNTITLED, FeatureFlagService, formatLocale, PropertiesPanelProvider, TitleBandProvider, translateKey, } from '../../affine-shared/services/index.js';
39
37
  import { stopPropagation } from '../../affine-shared/utils/index.js';
38
+ import { createStandaloneCell, StandaloneCellMetaProvider, } from '../../data-view/index.js';
40
39
  import { SignalWatcher, WithDisposable } from '../../global/lit/index.js';
41
40
  import { ShadowlessElement } from '../../std/index.js';
42
- import { CheckBoxCheckSolidIcon, CheckBoxUnIcon, LinkedPageIcon, PlusIcon, SidebarIcon, ToggleDownIcon, ToggleRightIcon, } from '@blocksuite/icons/lit';
41
+ import { LinkedPageIcon, PlusIcon, SidebarIcon, ToggleDownIcon, ToggleRightIcon, } from '@blocksuite/icons/lit';
43
42
  import { computed, signal } from '@preact/signals-core';
44
43
  import { css, html, nothing } from 'lit';
45
44
  import { property } from 'lit/decorators.js';
46
45
  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';
46
+ import { TITLE_BAND_ADD, TITLE_BAND_COLLAPSE, TITLE_BAND_EMPTY, TITLE_BAND_EXPAND, TITLE_BAND_LABEL, TITLE_BAND_LOADING, TITLE_BAND_NOT_SHOWN, TITLE_BAND_OPEN_PANEL, TITLE_BAND_SHOW_HERE, } from '../translations.js';
47
+ import { bandCellData, bandCellMeta, bandCellValue, bandWriteValue, isBandReadonlyType, sameBandValue, } from './band-cell.js';
48
+ import { formatPropertyValue, isPropertyValueEmpty } from './format.js';
49
+ import { showPageHere } from './show-here.js';
50
50
  export const LABRE_TITLE_BAND = 'labre-title-band';
51
51
  const NO_PAGES = [];
52
52
  /**
@@ -61,12 +61,19 @@ const NO_PAGES = [];
61
61
  *
62
62
  * Fed by `TitleBandProvider` alone (§9d): no provider, nothing at all. The
63
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).
64
+ * included (REQ-PR-1). Each value is drawn and edited by the cell the inline
65
+ * table mounts for its data-view type (`<dv-standalone-cell>`, ADR 0035
66
+ * §11 amended 2026-10-13): the checkbox, the tag menu of a select, the date
67
+ * picker, the number, text and link inputs, with the table's own styles. The
68
+ * value the cell commits goes back through `set`, the chip shows pending
69
+ * until the host settles it, and a type with no cell keeps a plain-text
70
+ * reading that opens the properties panel. The band never applies a value, and writes to
71
+ * the document only the card "Show here" inserts. The "+" is drawn when the
72
+ * host passes `add`; the panel button when a `PropertiesPanelProvider` is
73
+ * registered. The "Not shown on this page" row is drawn in both views
74
+ * from the host's `notShown$` (ADR 0035 §9d, amended 2026-10-13): each page
75
+ * opens through the linked-document path, and "Show here" inserts a card of
76
+ * it into this document; the host then drops the page from its list.
70
77
  */
71
78
  let TitleBand = (() => {
72
79
  let _classSuper = SignalWatcher(WithDisposable(ShadowlessElement));
@@ -154,7 +161,7 @@ let TitleBand = (() => {
154
161
  background-color: transparent;
155
162
  }
156
163
 
157
- .labre-title-band svg {
164
+ .labre-title-band button > svg {
158
165
  flex: none;
159
166
  width: 16px;
160
167
  height: 16px;
@@ -165,36 +172,57 @@ let TitleBand = (() => {
165
172
  padding: 0 6px !important;
166
173
  }
167
174
 
175
+ .labre-title-band-chip {
176
+ display: inline-flex;
177
+ align-items: center;
178
+ gap: 6px;
179
+ min-height: 28px;
180
+ max-width: 100%;
181
+ padding: 0 8px;
182
+ border-radius: 6px;
183
+ transition: background-color 150ms;
184
+ }
185
+
186
+ .labre-title-band-chip[data-clickable='true'] {
187
+ cursor: pointer;
188
+ }
189
+
190
+ .labre-title-band-chip[data-clickable='true']:hover {
191
+ background-color: var(--affine-hover-color);
192
+ }
193
+
194
+ .labre-title-band-chip[aria-busy='true'] {
195
+ cursor: progress;
196
+ opacity: 0.6;
197
+ }
198
+
199
+ .labre-title-band-chip:focus-visible,
200
+ .labre-title-band-chip dv-standalone-cell:focus-visible {
201
+ outline: 2px solid var(--affine-primary-color);
202
+ outline-offset: 1px;
203
+ border-radius: 4px;
204
+ }
205
+
168
206
  .labre-title-band-chip-name {
169
207
  color: var(--affine-text-secondary-color);
170
208
  white-space: nowrap;
171
209
  }
172
210
 
211
+ /* Room only: what is inside is the cell's own drawing. */
173
212
  .labre-title-band-chip-value {
174
- display: inline-flex;
175
- align-items: center;
176
- gap: 4px;
213
+ max-width: 240px;
177
214
  overflow: hidden;
215
+ }
216
+
217
+ .labre-title-band-chip-text {
178
218
  text-overflow: ellipsis;
179
219
  white-space: nowrap;
180
- max-width: 240px;
181
220
  }
182
221
 
183
- .labre-title-band-chip[data-empty='true'] .labre-title-band-chip-value {
222
+ .labre-title-band-chip-text[data-empty='true'] {
184
223
  color: var(--affine-placeholder-color);
185
224
  }
186
225
 
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
226
  .labre-title-band-skeleton {
199
227
  display: inline-block;
200
228
  width: 96px;
@@ -208,39 +236,34 @@ let TitleBand = (() => {
208
236
  padding-right: 4px;
209
237
  }
210
238
 
239
+ /* Layout only: the buttons are the band's own, no drawing of their own. */
240
+ .labre-title-band-not-shown-item {
241
+ display: inline-flex;
242
+ align-items: center;
243
+ max-width: 100%;
244
+ }
245
+
211
246
  @media (prefers-reduced-motion: reduce) {
212
- .labre-title-band button {
247
+ .labre-title-band button,
248
+ .labre-title-band-chip {
213
249
  transition: none;
214
250
  }
215
251
  }
216
252
 
217
253
  /* A 44px target under a coarse pointer, without changing the drawing. */
218
254
  @media (pointer: coarse) {
219
- .labre-title-band button {
255
+ .labre-title-band button,
256
+ .labre-title-band-chip {
220
257
  position: relative;
221
258
  }
222
- .labre-title-band button::after {
259
+ .labre-title-band button::after,
260
+ .labre-title-band-chip::after {
223
261
  content: '';
224
262
  position: absolute;
225
263
  inset: -8px 0;
226
264
  }
227
265
  }
228
266
  `; }
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
267
  /** Read inside `render` it subscribes, through `SignalWatcher`. */
245
268
  get _readonly() {
246
269
  return this.std?.store.readonly$.value ?? true;
@@ -264,7 +287,8 @@ let TitleBand = (() => {
264
287
  docId,
265
288
  sheet$: service.sheet$(docId),
266
289
  collapsed$: service.collapsed$?.(docId) ?? null,
267
- notShown$: this.surface === 'canvas' ? (service.notShown$?.(docId) ?? null) : null,
290
+ notShown$: service.notShown$?.(docId) ?? null,
291
+ cells: new Map(),
268
292
  };
269
293
  this._sources = sources;
270
294
  return sources;
@@ -305,125 +329,109 @@ let TitleBand = (() => {
305
329
  console.error('[title-band] set was refused', error);
306
330
  });
307
331
  }
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);
332
+ /** A value a cell commits: unchanged, nothing is written. */
333
+ _commit(prop, cellType, value) {
334
+ if (sameBandValue(cellType, prop.value, value))
335
+ return;
336
+ this._write(prop, bandWriteValue(value));
355
337
  }
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();
338
+ /** Neither the property nor the document can be written, or the type. */
339
+ _isReadonlyChip(prop) {
340
+ return (!prop || prop.readonly || this._readonly || isBandReadonlyType(prop.type));
374
341
  }
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
- },
342
+ /**
343
+ * The cell that draws `prop`, built once per property and type: its value,
344
+ * name and config are read from the sheet, so a new sheet updates the cell
345
+ * in place and an editor open in it stays open.
346
+ */
347
+ _cellFor(sources, prop) {
348
+ const std = this.std;
349
+ if (!std)
350
+ return null;
351
+ const meta = bandCellMeta(prop.type, cellType => std.getOptional(StandaloneCellMetaProvider(cellType)));
352
+ if (!meta)
353
+ return null;
354
+ const cached = sources.cells.get(prop.id);
355
+ if (cached?.type === prop.type)
356
+ return cached.cell;
357
+ const { id, type } = prop;
358
+ const cellType = meta.type;
359
+ const current$ = computed(() => {
360
+ const state = sources.sheet$.value;
361
+ if (state.status !== 'ready')
362
+ return undefined;
363
+ return state.properties.find(p => p.id === id && p.type === type);
364
+ });
365
+ const cell = createStandaloneCell(meta, {
366
+ propertyId: id,
367
+ name$: computed(() => current$.value?.name ?? ''),
368
+ data$: computed(() => {
369
+ const current = current$.value;
370
+ return current ? bandCellData(cellType, current) : {};
392
371
  }),
393
- ]);
372
+ value$: computed(() => bandCellValue(cellType, current$.value?.value)),
373
+ readonly$: computed(() => this._isReadonlyChip(current$.value)),
374
+ valueSet: value => {
375
+ const current = current$.peek();
376
+ if (current)
377
+ this._commit(current, cellType, value);
378
+ },
379
+ featureFlags$: this._featureFlags$,
380
+ serviceGet: key => std.getOptional(key),
381
+ });
382
+ sources.cells.set(id, { type, cell });
383
+ return cell;
394
384
  }
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
- }
385
+ _renderFallback(prop) {
386
+ const text = formatPropertyValue(prop, this._locale);
387
+ return html `<span
388
+ class="labre-title-band-chip-value labre-title-band-chip-text"
389
+ data-empty=${text ? 'false' : 'true'}
390
+ >${text || this._t(TITLE_BAND_EMPTY)}</span
391
+ >`;
407
392
  }
408
- _renderChip(prop, hasPanel) {
393
+ _renderChip(sources, prop, hasPanel) {
409
394
  const pending = this._pending$.value.has(prop.id);
410
- const editable = !this._readonly && chipEditorKind(prop) !== 'none';
395
+ const cell = this._cellFor(sources, prop);
396
+ const editable = !!cell && !this._isReadonlyChip(prop);
411
397
  const clickable = editable || hasPanel;
412
- const view = chipValueView(prop, this._locale);
413
- return html `<button
414
- class="labre-title-band-chip ${clickable ? '' : 'static'}"
398
+ // Without a cell the chip itself is what a keyboard reaches.
399
+ const focusable = !cell && hasPanel;
400
+ return html `<div
401
+ class="labre-title-band-chip"
415
402
  data-testid="title-band-chip"
416
403
  data-property-id=${prop.id}
417
- data-empty=${view.kind === 'empty' ? 'true' : 'false'}
404
+ data-empty=${isPropertyValueEmpty(prop) ? 'true' : 'false'}
405
+ data-clickable=${clickable ? 'true' : 'false'}
418
406
  aria-busy=${pending ? 'true' : 'false'}
419
- ?disabled=${pending || !clickable}
420
- @click=${(event) => this._onChip(event, prop)}
407
+ role=${focusable ? 'button' : nothing}
408
+ tabindex=${focusable ? '0' : nothing}
409
+ @click=${(event) => {
410
+ if (clickable)
411
+ this._onChip(event, editable);
412
+ }}
413
+ @keydown=${(event) => {
414
+ if (!focusable || event.target !== event.currentTarget)
415
+ return;
416
+ if (event.key !== 'Enter' && event.key !== ' ')
417
+ return;
418
+ event.preventDefault();
419
+ this._onChip(event, false);
420
+ }}
421
421
  >
422
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>`;
423
+ ${cell
424
+ ? html `<dv-standalone-cell
425
+ class="labre-title-band-chip-value"
426
+ data-testid="title-band-cell"
427
+ role=${editable ? 'button' : nothing}
428
+ aria-label=${prop.name}
429
+ .cell=${cell}
430
+ .readonly=${!editable}
431
+ .placeholder=${this._t(TITLE_BAND_EMPTY)}
432
+ ></dv-standalone-cell>`
433
+ : this._renderFallback(prop)}
434
+ </div>`;
427
435
  }
428
436
  _renderTools(sources, hasPanel) {
429
437
  const canAdd = !!sources.service.add && !this._readonly;
@@ -519,7 +527,7 @@ let TitleBand = (() => {
519
527
  ${ToggleDownIcon()}
520
528
  </button>`
521
529
  : nothing}
522
- ${repeat(properties, prop => prop.id, prop => this._renderChip(prop, hasPanel))}
530
+ ${repeat(properties, prop => prop.id, prop => this._renderChip(sources, prop, hasPanel))}
523
531
  ${this._renderTools(sources, hasPanel)}
524
532
  </div>`;
525
533
  }
@@ -530,32 +538,55 @@ let TitleBand = (() => {
530
538
  // A link that opens nothing is a lie: no linked-document path, no row.
531
539
  if (!std || !slots || pages.length === 0)
532
540
  return nothing;
541
+ const label = this._t(TITLE_BAND_NOT_SHOWN);
542
+ const showHere = this._readonly ? null : this._t(TITLE_BAND_SHOW_HERE);
533
543
  return html `<div
534
544
  class="labre-title-band-row"
545
+ role="group"
546
+ aria-label=${label}
535
547
  data-testid="title-band-not-shown"
536
548
  >
537
- <span class="labre-title-band-not-shown-label"
538
- >${this._t(TITLE_BAND_NOT_SHOWN)}</span
549
+ <span class="labre-title-band-not-shown-label" aria-hidden="true"
550
+ >${label}</span
539
551
  >
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
- }}
552
+ ${repeat(pages, page => page.docId, page => {
553
+ const title = page.title.trim() === '' ? this._t(DOC_UNTITLED) : page.title;
554
+ // A group named after the page, so a screen reader hears which
555
+ // page each "Show here" belongs to without a second wording.
556
+ return html `<span
557
+ class="labre-title-band-not-shown-item"
558
+ role="group"
559
+ aria-label=${title}
551
560
  >
552
- ${LinkedPageIcon()}
553
- <span class="labre-title-band-chip-value"
554
- >${page.title.trim() === ''
555
- ? this._t(DOC_UNTITLED)
556
- : page.title}</span
561
+ <button
562
+ data-testid="title-band-not-shown-page"
563
+ data-doc-id=${page.docId}
564
+ @click=${(event) => {
565
+ event.stopPropagation();
566
+ slots.docLinkClicked.next({
567
+ pageId: page.docId,
568
+ host: std.host,
569
+ event,
570
+ });
571
+ }}
557
572
  >
558
- </button>`)}
573
+ ${LinkedPageIcon()}
574
+ <span class="labre-title-band-chip-value">${title}</span>
575
+ </button>
576
+ ${showHere
577
+ ? html `<button
578
+ data-testid="title-band-show-here"
579
+ data-doc-id=${page.docId}
580
+ @click=${(event) => {
581
+ event.stopPropagation();
582
+ showPageHere(std, this.surface, page.docId);
583
+ }}
584
+ >
585
+ ${showHere}
586
+ </button>`
587
+ : nothing}
588
+ </span>`;
589
+ })}
559
590
  </div>`;
560
591
  }
561
592
  connectedCallback() {
@@ -565,7 +596,6 @@ let TitleBand = (() => {
565
596
  // split the title) nor the canvas's (a press would start a drag).
566
597
  this._disposables.addFromEvent(this, 'keydown', stopPropagation);
567
598
  this._disposables.addFromEvent(this, 'pointerdown', stopPropagation);
568
- this._disposables.add(() => this._menu?.close());
569
599
  }
570
600
  render() {
571
601
  const std = this.std;
@@ -576,7 +606,7 @@ let TitleBand = (() => {
576
606
  return nothing;
577
607
  const sources = this._getSources(service);
578
608
  const sheet = this._renderSheet(sources);
579
- const notShown = this.surface === 'canvas' ? this._renderNotShown(sources) : nothing;
609
+ const notShown = this._renderNotShown(sources);
580
610
  if (sheet === nothing && notShown === nothing)
581
611
  return nothing;
582
612
  return html `<div
@@ -594,44 +624,23 @@ let TitleBand = (() => {
594
624
  get docId() { return this.#docId_accessor_storage; }
595
625
  set docId(value) { this.#docId_accessor_storage = value; }
596
626
  #surface_accessor_storage;
597
- /** Where the band is drawn; only the canvas draws the "Not shown" row. */
627
+ /** Where the band is drawn: where "Show here" puts the card. */
598
628
  get surface() { return this.#surface_accessor_storage; }
599
629
  set surface(value) { this.#surface_accessor_storage = value; }
600
630
  constructor() {
601
631
  super(...arguments);
602
632
  this._sources = null;
603
- /** The editor menu open now, put away with the band. */
604
- this._menu = null;
605
633
  /** Collapse state when the host does not remember one. */
606
634
  this._localCollapsed$ = signal(false);
607
635
  /** Property ids whose `set` has not settled yet. */
608
636
  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
- };
637
+ /** The table's own flags, so a number reads as it does in a cell. */
638
+ this._featureFlags$ = computed(() => ({
639
+ enable_number_formatting: this.std
640
+ ?.getOptional(FeatureFlagService)
641
+ ?.getFlag('enable_database_number_formatting') ?? false,
642
+ enable_table_virtual_scroll: false,
643
+ }));
635
644
  this._openPanel = () => {
636
645
  if (!this.std)
637
646
  return;
@@ -639,6 +648,15 @@ let TitleBand = (() => {
639
648
  .getOptional(PropertiesPanelProvider)
640
649
  ?.open({ scope: 'page', docId: this.docId });
641
650
  };
651
+ this._onChip = (event, editable) => {
652
+ event.stopPropagation();
653
+ const chip = event.currentTarget;
654
+ if (editable) {
655
+ chip.querySelector('dv-standalone-cell')?.edit();
656
+ return;
657
+ }
658
+ this._openPanel();
659
+ };
642
660
  this.#std_accessor_storage = __runInitializers(this, _std_initializers, null);
643
661
  this.#docId_accessor_storage = (__runInitializers(this, _std_extraInitializers), __runInitializers(this, _docId_initializers, ''));
644
662
  this.#surface_accessor_storage = (__runInitializers(this, _docId_extraInitializers), __runInitializers(this, _surface_initializers, 'page'));
@@ -23,12 +23,15 @@ export declare const TITLE_BAND_COLLAPSE: ChromeWording;
23
23
  export declare const TITLE_BAND_EXPAND: ChromeWording;
24
24
  /** The accessible name of the skeleton drawn while the sheet loads. */
25
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
26
  /**
29
- * The note header's row of child pages the document holds but does not
30
- * display (ADR 0035 §9d, canvas view only).
27
+ * The row of child pages the document holds but does not display, under the
28
+ * title and in the note header (ADR 0035 §9d and its 2026-10-13 amendment).
31
29
  */
32
30
  export declare const TITLE_BAND_NOT_SHOWN: ChromeWording;
31
+ /**
32
+ * The button beside each page of that row: inserts a card of the page here
33
+ * (labre ADR-0045 §7).
34
+ */
35
+ export declare const TITLE_BAND_SHOW_HERE: ChromeWording;
33
36
  /** Every wording this package declares, in the order it renders them. */
34
37
  export declare const DOC_TITLE_WORDINGS: readonly ChromeWording[];
@@ -47,24 +47,26 @@ export const TITLE_BAND_LOADING = [
47
47
  'com.labre.title-band.loading',
48
48
  'Loading properties',
49
49
  ];
50
- /** Clears a select or a date from its editor. */
51
- export const TITLE_BAND_CLEAR = [
52
- 'com.labre.title-band.clear',
53
- 'Clear value',
54
- ];
55
50
  /**
56
- * The note header's row of child pages the document holds but does not
57
- * display (ADR 0035 §9d, canvas view only).
51
+ * The row of child pages the document holds but does not display, under the
52
+ * title and in the note header (ADR 0035 §9d and its 2026-10-13 amendment).
58
53
  */
59
54
  export const TITLE_BAND_NOT_SHOWN = [
60
55
  'com.labre.title-band.not-shown',
61
- 'Not shown in this document',
56
+ 'Not shown on this page',
57
+ ];
58
+ /**
59
+ * The button beside each page of that row: inserts a card of the page here
60
+ * (labre ADR-0045 §7).
61
+ */
62
+ export const TITLE_BAND_SHOW_HERE = [
63
+ 'com.labre.title-band.show-here',
64
+ 'Show here',
62
65
  ];
63
66
  /** Every wording this package declares, in the order it renders them. */
64
67
  export const DOC_TITLE_WORDINGS = [
65
68
  DOC_TITLE_PLACEHOLDER,
66
69
  TITLE_BAND_ADD,
67
- TITLE_BAND_CLEAR,
68
70
  TITLE_BAND_COLLAPSE,
69
71
  TITLE_BAND_EMPTY,
70
72
  TITLE_BAND_EXPAND,
@@ -72,4 +74,5 @@ export const DOC_TITLE_WORDINGS = [
72
74
  TITLE_BAND_LOADING,
73
75
  TITLE_BAND_NOT_SHOWN,
74
76
  TITLE_BAND_OPEN_PANEL,
77
+ TITLE_BAND_SHOW_HERE,
75
78
  ];
@@ -1,9 +1,9 @@
1
1
  export declare const groupToolbarConfig: {
2
2
  readonly actions: [{
3
3
  readonly id: "a.insert-into-page";
4
- readonly label: "Insert into Document";
4
+ readonly label: "Insert into Page";
5
5
  readonly labelWording: import("../../affine-shared/services/index.js").ChromeWording;
6
- readonly tooltip: "Insert into Document";
6
+ readonly tooltip: "Insert into Page";
7
7
  readonly tooltipWording: import("../../affine-shared/services/index.js").ChromeWording;
8
8
  readonly icon: import("lit-html").TemplateResult<1>;
9
9
  readonly when: (ctx: import("../../affine-shared/services/index.js").ToolbarContext) => boolean;