@input/pen-core 0.1.0 → 0.1.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.
package/dist/index.cjs CHANGED
@@ -157,6 +157,7 @@ __export(index_exports, {
157
157
  nextGraphemeBoundary: () => nextGraphemeBoundary,
158
158
  nextWordBoundary: () => nextWordBoundary,
159
159
  normalizePendingBlocksForImport: () => normalizePendingBlocksForImport,
160
+ orderSlashMenuItemsByGroup: () => orderSlashMenuItemsByGroup,
160
161
  outdent: () => outdent,
161
162
  previousGraphemeBoundary: () => previousGraphemeBoundary,
162
163
  previousWordBoundary: () => previousWordBoundary,
@@ -201,6 +202,7 @@ __export(index_exports, {
201
202
  shouldForceBlockScopedSelectAll: () => shouldForceBlockScopedSelectAll,
202
203
  shouldShowBlockInDefaultMenus: () => shouldShowBlockInDefaultMenus,
203
204
  singleController: () => singleController,
205
+ slashMenuGroupOf: () => slashMenuGroupOf,
204
206
  snapToNormalPosition: () => snapToNormalPosition,
205
207
  snapshotsControllerFacet: () => snapshotsControllerFacet,
206
208
  sortDeltaAttributes: () => sortDeltaAttributes,
@@ -923,6 +925,25 @@ function mergeSchemas(...registries2) {
923
925
  });
924
926
  }
925
927
 
928
+ // src/schema/slashMenuOrder.ts
929
+ var FALLBACK_GROUP = "other";
930
+ function slashMenuGroupOf(display) {
931
+ return display.group ?? FALLBACK_GROUP;
932
+ }
933
+ function orderSlashMenuItemsByGroup(items) {
934
+ const grouped = /* @__PURE__ */ new Map();
935
+ for (const item of items) {
936
+ const group = slashMenuGroupOf(item.display);
937
+ const existing = grouped.get(group);
938
+ if (existing) {
939
+ existing.push(item);
940
+ } else {
941
+ grouped.set(group, [item]);
942
+ }
943
+ }
944
+ return [...grouped.values()].flat();
945
+ }
946
+
926
947
  // src/schema/defineBlock.ts
927
948
  function resolveProps2(config) {
928
949
  const raw = config.props ?? config.propSchema ?? {};
@@ -1202,6 +1223,15 @@ var SchemaEngineImpl = class {
1202
1223
  markDirty(blockId) {
1203
1224
  this.dirtyBlockIds.add(blockId);
1204
1225
  }
1226
+ /**
1227
+ * Drop the cached pass index because `blockOrder` or a `children` array
1228
+ * changed outside this engine — an executing op, or a remote/undo update.
1229
+ * Every structural mutation the engine performs itself already invalidates
1230
+ * at the mutation site, so those callers do not go through here.
1231
+ */
1232
+ notifyStructureChanged() {
1233
+ this.invalidatePassIndex();
1234
+ }
1205
1235
  deferBlock(blockId) {
1206
1236
  this.deferredBlockIds.add(blockId);
1207
1237
  }
@@ -1221,7 +1251,6 @@ var SchemaEngineImpl = class {
1221
1251
  }
1222
1252
  iterations++;
1223
1253
  this.dirtyBlockIds.clear();
1224
- this.invalidatePassIndex();
1225
1254
  this.doc.adapter.transact(this.crdtDoc, () => {
1226
1255
  for (const blockId of snapshot) {
1227
1256
  if (this.deferredBlockIds.has(blockId)) {
@@ -1232,7 +1261,6 @@ var SchemaEngineImpl = class {
1232
1261
  }
1233
1262
  });
1234
1263
  }
1235
- this.invalidatePassIndex();
1236
1264
  if (iterations >= MAX_ITERATIONS) {
1237
1265
  this.onDiagnostic?.({
1238
1266
  code: "normalize-cap",
@@ -1244,6 +1272,7 @@ var SchemaEngineImpl = class {
1244
1272
  }
1245
1273
  }
1246
1274
  normalizeAll() {
1275
+ this.invalidatePassIndex();
1247
1276
  for (const blockId of this.doc.blocks.keys()) {
1248
1277
  this.dirtyBlockIds.add(blockId);
1249
1278
  }
@@ -3456,6 +3485,11 @@ function emitSchemaUnknownBlock(pipeline, type) {
3456
3485
  });
3457
3486
  }
3458
3487
  function reportUnknownBlocksInDocument(pipeline) {
3488
+ const blockCount = pipeline._doc.blocks.size;
3489
+ if (blockCount === pipeline._unknownScanBlockCount) {
3490
+ return;
3491
+ }
3492
+ pipeline._unknownScanBlockCount = blockCount;
3459
3493
  for (const [, rawBlockMap] of pipeline._doc.blocks.entries()) {
3460
3494
  if (!isCRDTMap(rawBlockMap)) {
3461
3495
  continue;
@@ -3755,6 +3789,9 @@ function emitMalformedOpDiagnostic(pipeline, op, error) {
3755
3789
  ...error !== void 0 ? { error } : {}
3756
3790
  });
3757
3791
  }
3792
+ function isStructuralOp(op) {
3793
+ return op.type === "insert-block" || op.type === "delete-block" || op.type === "move-block";
3794
+ }
3758
3795
  function executeOps(pipeline, ops, origin, structural) {
3759
3796
  pipeline._commitDiagnostics = [];
3760
3797
  reportUnknownBlocksInDocument(pipeline);
@@ -3821,6 +3858,9 @@ function executeOps(pipeline, ops, origin, structural) {
3821
3858
  } catch (err) {
3822
3859
  emitMalformedOpDiagnostic(pipeline, op, err);
3823
3860
  }
3861
+ if (isStructuralOp(op)) {
3862
+ pipeline._engine.notifyStructureChanged();
3863
+ }
3824
3864
  }
3825
3865
  for (const blockId of affectedBlocks) {
3826
3866
  pipeline._engine.markDirty(blockId);
@@ -3992,6 +4032,7 @@ var ApplyPipeline = class {
3992
4032
  _applyStormEmitted = false;
3993
4033
  _suppressObserver = false;
3994
4034
  _unknownBlockTypesReported;
4035
+ _unknownScanBlockCount;
3995
4036
  _queue = [];
3996
4037
  _applyBoundaryHooks = [];
3997
4038
  _beforeApplyHooks = [];
@@ -4075,6 +4116,7 @@ var ApplyPipeline = class {
4075
4116
  this._doc = doc;
4076
4117
  this._crdtDoc = crdtDoc;
4077
4118
  this._engine = engine;
4119
+ this._unknownScanBlockCount = void 0;
4078
4120
  }
4079
4121
  };
4080
4122
 
@@ -10860,44 +10902,20 @@ function createBlockIndex(initial) {
10860
10902
  snapshot() {
10861
10903
  return current;
10862
10904
  },
10863
- apply(summary) {
10864
- current = applySummaryToSnapshot(current, summary);
10905
+ applyTextLengths(blockText) {
10906
+ for (const change of blockText) {
10907
+ const previous = current.lengthById.get(change.blockId) ?? 0;
10908
+ current.lengthById.set(
10909
+ change.blockId,
10910
+ lengthAfterSplices(previous, change.splices)
10911
+ );
10912
+ }
10865
10913
  },
10866
10914
  replace(snapshot) {
10867
10915
  current = cloneSnapshot(snapshot);
10868
10916
  }
10869
10917
  };
10870
10918
  }
10871
- function applySummaryToSnapshot(snapshot, summary) {
10872
- const lengthById = new Map(snapshot.lengthById);
10873
- const typeById = new Map(snapshot.typeById);
10874
- const childrenByParentId = cloneChildren(snapshot.childrenByParentId);
10875
- for (const change of summary.structural) {
10876
- applyStructural(change, lengthById, typeById, childrenByParentId);
10877
- }
10878
- for (const text of summary.blockText) {
10879
- const previous = lengthById.get(text.blockId) ?? 0;
10880
- lengthById.set(
10881
- text.blockId,
10882
- lengthAfterSplices(previous, text.splices)
10883
- );
10884
- }
10885
- const roots = [...childrenByParentId.get(null) ?? []];
10886
- const parentById = /* @__PURE__ */ new Map();
10887
- for (const [parentId, children] of childrenByParentId) {
10888
- for (const childId of children) {
10889
- parentById.set(childId, parentId);
10890
- }
10891
- }
10892
- return {
10893
- lengthById,
10894
- typeById,
10895
- parentById,
10896
- childrenByParentId,
10897
- order: flattenOrder(roots, childrenByParentId),
10898
- roots
10899
- };
10900
- }
10901
10919
  function lengthAfterSplices(length, splices) {
10902
10920
  let next = length;
10903
10921
  for (const splice of splices) {
@@ -10915,120 +10933,6 @@ function cloneSnapshot(snapshot) {
10915
10933
  roots: [...snapshot.roots]
10916
10934
  };
10917
10935
  }
10918
- function applyStructural(change, lengthById, typeById, childrenByParentId) {
10919
- switch (change.type) {
10920
- case "block-inserted": {
10921
- insertChild(
10922
- childrenByParentId,
10923
- change.parentId,
10924
- change.index,
10925
- change.blockId
10926
- );
10927
- if (!lengthById.has(change.blockId))
10928
- lengthById.set(change.blockId, 0);
10929
- break;
10930
- }
10931
- case "block-removed": {
10932
- removeChild(childrenByParentId, change.parentId, change.blockId);
10933
- lengthById.delete(change.blockId);
10934
- typeById.delete(change.blockId);
10935
- childrenByParentId.delete(change.blockId);
10936
- break;
10937
- }
10938
- case "block-moved": {
10939
- removeChild(
10940
- childrenByParentId,
10941
- change.fromParentId,
10942
- change.blockId
10943
- );
10944
- insertChild(
10945
- childrenByParentId,
10946
- change.toParentId,
10947
- change.toIndex,
10948
- change.blockId
10949
- );
10950
- break;
10951
- }
10952
- case "block-split": {
10953
- insertAfter(childrenByParentId, change.blockId, change.newBlockId);
10954
- const original = lengthById.get(change.blockId) ?? 0;
10955
- lengthById.set(change.blockId, Math.max(0, change.offset));
10956
- lengthById.set(
10957
- change.newBlockId,
10958
- Math.max(0, original - change.offset)
10959
- );
10960
- if (!typeById.has(change.newBlockId)) {
10961
- typeById.set(
10962
- change.newBlockId,
10963
- typeById.get(change.blockId) ?? ""
10964
- );
10965
- }
10966
- break;
10967
- }
10968
- case "blocks-merged": {
10969
- const parentId = parentOf(childrenByParentId, change.sourceBlockId);
10970
- const targetLength = lengthById.get(change.targetBlockId) ?? 0;
10971
- const sourceLength = lengthById.get(change.sourceBlockId) ?? 0;
10972
- lengthById.set(change.targetBlockId, targetLength + sourceLength);
10973
- removeChild(childrenByParentId, parentId, change.sourceBlockId);
10974
- lengthById.delete(change.sourceBlockId);
10975
- typeById.delete(change.sourceBlockId);
10976
- childrenByParentId.delete(change.sourceBlockId);
10977
- break;
10978
- }
10979
- case "block-props-changed":
10980
- case "table-changed":
10981
- case "apps-changed":
10982
- case "metadata-changed":
10983
- break;
10984
- default: {
10985
- const _exhaustive = change;
10986
- return _exhaustive;
10987
- }
10988
- }
10989
- }
10990
- function insertAfter(childrenByParentId, beforeId, newId) {
10991
- for (const [parentId, children] of childrenByParentId) {
10992
- const index = children.indexOf(beforeId);
10993
- if (index < 0) continue;
10994
- if (!children.includes(newId)) {
10995
- children.splice(index + 1, 0, newId);
10996
- }
10997
- childrenByParentId.set(parentId, children);
10998
- return;
10999
- }
11000
- insertChild(childrenByParentId, null, -1, newId);
11001
- }
11002
- function insertChild(childrenByParentId, parentId, index, blockId) {
11003
- const children = childrenByParentId.get(parentId) ?? [];
11004
- const next = children.filter((id) => id !== blockId);
11005
- const at = index < 0 || index > next.length ? next.length : index;
11006
- next.splice(at, 0, blockId);
11007
- childrenByParentId.set(parentId, next);
11008
- }
11009
- function removeChild(childrenByParentId, parentId, blockId) {
11010
- const children = childrenByParentId.get(parentId);
11011
- if (!children) {
11012
- for (const [id, list] of childrenByParentId) {
11013
- const index2 = list.indexOf(blockId);
11014
- if (index2 >= 0) {
11015
- list.splice(index2, 1);
11016
- childrenByParentId.set(id, list);
11017
- return;
11018
- }
11019
- }
11020
- return;
11021
- }
11022
- const index = children.indexOf(blockId);
11023
- if (index >= 0) children.splice(index, 1);
11024
- childrenByParentId.set(parentId, children);
11025
- }
11026
- function parentOf(childrenByParentId, blockId) {
11027
- for (const [parentId, children] of childrenByParentId) {
11028
- if (children.includes(blockId)) return parentId;
11029
- }
11030
- return null;
11031
- }
11032
10936
  function flattenOrder(roots, childrenByParentId) {
11033
10937
  const order = [];
11034
10938
  const visit = (id) => {
@@ -11616,7 +11520,7 @@ function affectedBlockIdsFromSummary(summary, documentOrder) {
11616
11520
  addStructuralBlockIds(ids, change);
11617
11521
  }
11618
11522
  const collected = [...ids];
11619
- if (!documentOrder || documentOrder.length === 0) {
11523
+ if (collected.length < 2 || !documentOrder || documentOrder.length === 0) {
11620
11524
  return collected;
11621
11525
  }
11622
11526
  const rank = /* @__PURE__ */ new Map();
@@ -12038,14 +11942,22 @@ function installChangeSummaries(host) {
12038
11942
  host._unsubSummary = (0, import_pen_yjs5.createSummarySource)(
12039
11943
  host._crdtDoc,
12040
11944
  (delta) => {
12041
- host._pendingSummary = buildChangeSummary(
11945
+ if (delta.blockOrderDelta.length > 0 || delta.childArrayDeltas.size > 0) {
11946
+ host._engine.notifyStructureChanged();
11947
+ }
11948
+ const summary = buildChangeSummary(
12042
11949
  delta,
12043
11950
  host._blockIndex.snapshot(),
12044
11951
  0
12045
11952
  );
12046
- host._blockIndex.replace(
12047
- createBlockIndexSnapshotFromDocument(host._doc)
12048
- );
11953
+ host._pendingSummary = summary;
11954
+ if (summary.structural.length === 0) {
11955
+ host._blockIndex.applyTextLengths(summary.blockText);
11956
+ } else {
11957
+ host._blockIndex.replace(
11958
+ createBlockIndexSnapshotFromDocument(host._doc)
11959
+ );
11960
+ }
12049
11961
  flushDeferredCRDTEvent(host);
12050
11962
  }
12051
11963
  );
@@ -14601,6 +14513,16 @@ var macosBindings = [
14601
14513
  key: "Alt-Delete",
14602
14514
  command: deleteForward,
14603
14515
  param: { granularity: "word" }
14516
+ },
14517
+ {
14518
+ key: "Meta-Backspace",
14519
+ command: deleteBackward,
14520
+ param: { granularity: "line" }
14521
+ },
14522
+ {
14523
+ key: "Ctrl-k",
14524
+ command: deleteForward,
14525
+ param: { granularity: "line" }
14604
14526
  }
14605
14527
  ];
14606
14528
  var windowsLinuxBindings = [
@@ -15101,6 +15023,7 @@ function mapOffsetThroughSplices(splices, offset, assoc) {
15101
15023
  nextGraphemeBoundary,
15102
15024
  nextWordBoundary,
15103
15025
  normalizePendingBlocksForImport,
15026
+ orderSlashMenuItemsByGroup,
15104
15027
  outdent,
15105
15028
  previousGraphemeBoundary,
15106
15029
  previousWordBoundary,
@@ -15145,6 +15068,7 @@ function mapOffsetThroughSplices(splices, offset, assoc) {
15145
15068
  shouldForceBlockScopedSelectAll,
15146
15069
  shouldShowBlockInDefaultMenus,
15147
15070
  singleController,
15071
+ slashMenuGroupOf,
15148
15072
  snapToNormalPosition,
15149
15073
  snapshotsControllerFacet,
15150
15074
  sortDeltaAttributes,
package/dist/index.d.cts CHANGED
@@ -119,6 +119,25 @@ declare class SchemaRegistryImpl implements ComposableSchema {
119
119
  }
120
120
  declare function mergeSchemas(...registries: SchemaRegistry[]): ComposableSchema;
121
121
 
122
+ /** Display group key used to cluster slash-menu items, or `"other"` when unset. */
123
+ declare function slashMenuGroupOf(display: BlockDisplay): string;
124
+ /**
125
+ * Reorder items so blocks sharing a group sit together, groups keeping the
126
+ * order they first appear in and items their incoming order.
127
+ *
128
+ * A slash menu renders grouped and navigates by index into this same array, so
129
+ * the grouping belongs here rather than at render time: an item's position here
130
+ * is what the menu's `confirm(index)` resolves, and a list that regrouped on
131
+ * its own would hand back the index of a different block.
132
+ *
133
+ * `allBlockDisplays()` returns registration order, which interleaves. The
134
+ * default schema registers the three list blocks between `heading` and
135
+ * `codeBlock`, so `basic` resumes after `lists` has already started.
136
+ */
137
+ declare function orderSlashMenuItemsByGroup<T extends {
138
+ display: BlockDisplay;
139
+ }>(items: readonly T[]): T[];
140
+
122
141
  type DefineBlockConfig = Omit<Partial<BlockSchema<string, Record<string, PropSchema>, ContentType>>, "type" | "propSchema" | "validateProps"> & {
123
142
  props?: Record<string, unknown>;
124
143
  propSchema?: Record<string, unknown>;
@@ -184,6 +203,13 @@ declare class SchemaEngineImpl implements SchemaEngine {
184
203
  constructor(registry: SchemaRegistry, doc: PenDocument, crdtDoc: CRDTDocument, onDiagnostic?: DiagnosticSink);
185
204
  setOnDiagnostic(onDiagnostic: DiagnosticSink | undefined): void;
186
205
  markDirty(blockId: string): void;
206
+ /**
207
+ * Drop the cached pass index because `blockOrder` or a `children` array
208
+ * changed outside this engine — an executing op, or a remote/undo update.
209
+ * Every structural mutation the engine performs itself already invalidates
210
+ * at the mutation site, so those callers do not go through here.
211
+ */
212
+ notifyStructureChanged(): void;
187
213
  deferBlock(blockId: string): void;
188
214
  undeferBlock(blockId: string): void;
189
215
  normalizeDirty(): void;
@@ -995,4 +1021,4 @@ declare function affectedBlockIdsFromSummary(summary: Pick<ChangeSummary, "block
995
1021
  */
996
1022
  declare function mapOffsetThroughSplices(splices: readonly TextSplice[], offset: number, assoc: Assoc): number;
997
1023
 
998
- export { A11Y_MISSING_LABEL_CODE, APPLY_STORM_CODE, APPLY_STORM_QUEUE_LIMIT, type BeforeApplyHook, type BlockDirection, type BlockDirectionResolver, type BlockDirectionSetting, type CaretMotionParam, type CellCaretFocus, type CellCaretWrite, type ClipboardHandler, type CommandDispatchContext, type CommandHandlerTable, type CommandRegistry, type ContentMove, type ConvertBlockParam, type CreateCommandRegistryOptions, type CreateFacetRegistryOptions, type CreateHeadlessEditorOptions, type DecorationSource, type DefaultKeymapBinding, type DefaultKeymapContext, type DefinedBlockSchema, type DeleteGranularity, type DeleteParam, type DocumentMigration, DocumentRangeImpl, type EditorA11yLabelAttrs, EventEmitter, ExtensionManagerImpl, type FacetRegistry, type FacetSettleInput, HOOK_PRIORITIES, type InsertTextParam, type Keymap, type KeymapPlatform, type MigrationReport, type NormalPositionSnapshot, PIPELINE_PHASES, PSEUDO_LOCALE_CLOSE, PSEUDO_LOCALE_OPEN, type PendingBlock, type PendingBlockImportPolicyViolation, type PendingBlockProfilePolicyViolation, type ProfilePolicyViolation, type SchemaA11yAttrs, type SchemaA11yKind, SchemaEngineImpl, type SchemaRegistryConfig, SchemaRegistryImpl, type SelectBlockParam, SelectionAuthorityImpl as SelectionAuthority, type StructureBlockParam, type ToggleMarkParam, type UrlContext, type UrlPolicy, type VerticalCaretDirection, type VerticalCaretMeasure, type VerticalCaretMeasureResult, type VerticalCaretPoint, type WordRange, a11yLabelFacet, affectedBlockIdsFromSummary, aiAutocompleteControllerFacet, aiControllerFacet, aiEgressExtension, aiEgressFacet, aiInlineCompletionFacet, aiInlineHistoryFacet, aiReviewControllerFacet, aiSuggestionsControllerFacet, announceEditorA11y, announcerFacet, applyDirectedBinding, applyMergeBlocks, applySplitBlock, ariaReadOnlyFacet, assetProviderFacet, beforeApplyFacet, blockDirectionFacet, blockLogicalText, blocksToOps, buildMergeBlocksRecipe, buildNormalPositionSnapshot, buildSplitBlockRecipe, buildTableChildren, builtinCommandHandlers, caretBlockEnd, caretBlockStart, caretDocEnd, caretDocStart, caretDown, caretLeft, caretLineEnd, caretLineStart, caretRight, caretUp, caretWordLeft, caretWordRight, clipboardFacet, collectEditorKeyBindings, collectToolExecutionOutput, commandHandler, commandsFacet, convertBlock, convertBlockOps, createAppHandle, createBlockHandle, createCommandRegistry, createDecorationSet, createDocumentSession, createEditor, createEmptySchema, createFacetRegistry, createHeadlessEditor, createImportResult, createMutationGroupMetadata, createPseudoLocaleCatalog, createTextSelection, decorationsFacet, deepEqual, defaultDirectionFacet, defaultKeymapBindings, defineBlock, defineCommand, defineExtension, defineFacet, delegatesToGridEditing, deleteAdjacentInlineAtom, deleteBackward, deleteBlock, deleteForward, deriveContentMoves, duplicateBlock, emptyDecorationSet, ensureInlineCompletionController, fieldEditorHostFacet, filterAIRequest, filterOpsForDocumentProfile, filterPendingBlocksForDocumentProfile, foldAndNormalize, getApplyOptionsGroupId, getBlockSelectionRoleFromSchema, getBlockSelectionRoleFromType, getCellCaretFocus, getCommandRegistry, getEditorSelectionRecord, getFlowCapabilityFromSchema, getFlowCapabilityFromType, getInlineCompletionController, getNumberedListItemValue, getOpOriginGroupId, getOpOriginType, getSelectionBlockRange, getVerticalCaretGoalX, getVerticalCaretMeasure, hasFieldEditorSurface, hasIndexedCellSelectionMetadata, historyRedo, historyUndo, hookPriorityToPrecedence, indent, inputRulesEngineFacet, inputRulesFacet, insertLineBreak, insertText, interpolateMessage, isBlockSelected, isCollapsed, isContinuousTextFlowCapability, isMultiBlock, isPseudoLocaleText, keyBindingPriorityToPrecedence, keymapFacet, localeFacet, mapOffsetThroughSplices, mergeDecorationSets, mergeSchemas, messagesFacet, moveBlockDown, moveBlockUp, multiplayerControllerFacet, nextGraphemeBoundary, nextWordBoundary, normalizePendingBlocksForImport, outdent, previousGraphemeBoundary, previousWordBoundary, priorityToPrecedence, prop, renderSelectionTargetBlockText, renderSelectionTargetText, repairAnchor, reportPendingBlockImportViolations, reportPendingBlockProfileViolations, resolveA11yBlockTypeLabel, resolveA11ySpec, resolveBlockDirection, resolveBlockFlowCapability, resolveCellSelectionCoord, resolveCellSelectionMatrix, resolveDefaultKeymap, resolveDirectedBinding, resolveDirectedCommand, resolveEditorA11yLabel, resolveEditorMessage, resolveEditorSchema, resolveFieldEditorBehavior, resolveFieldEditorInputMode, resolveFocusBlockDirection, resolveMessage, resolveSchema, resolveSchemaA11y, resolveSelectionTargetBlockIds, runMigrations, searchControllerFacet, selectAdjacentInlineAtom, selectAll, selectBlock, selectionToRange, setCellCaretFocus, setVerticalCaretGoalX, setVerticalCaretMeasure, shouldAllowDirectBlockPaste, shouldAllowFlowInsertionInSlashMenu, shouldExposeBlockInTooling, shouldForceBlockScopedSelectAll, shouldShowBlockInDefaultMenus, singleController, snapToNormalPosition, snapshotsControllerFacet, sortDeltaAttributes, spliceDeleteOp, spliceInsertOp, splitBlock, streamThroughEgress, streamingTargetFacet, suggestion, supportsInlineInputRules, supportsInlineMarks, tableCellDown, tableCellNext, tableCellPrev, tableEscapeGrid, toPseudoLocaleText, toggleMark, toolRuntimeFacet, undoManagerFacet, undoMetadataControllerFacet, undoRestoreControllerFacet, urlPolicy, urlPolicyFacet, usesInlineTextSelection, wordRangeAt };
1024
+ export { A11Y_MISSING_LABEL_CODE, APPLY_STORM_CODE, APPLY_STORM_QUEUE_LIMIT, type BeforeApplyHook, type BlockDirection, type BlockDirectionResolver, type BlockDirectionSetting, type CaretMotionParam, type CellCaretFocus, type CellCaretWrite, type ClipboardHandler, type CommandDispatchContext, type CommandHandlerTable, type CommandRegistry, type ContentMove, type ConvertBlockParam, type CreateCommandRegistryOptions, type CreateFacetRegistryOptions, type CreateHeadlessEditorOptions, type DecorationSource, type DefaultKeymapBinding, type DefaultKeymapContext, type DefinedBlockSchema, type DeleteGranularity, type DeleteParam, type DocumentMigration, DocumentRangeImpl, type EditorA11yLabelAttrs, EventEmitter, ExtensionManagerImpl, type FacetRegistry, type FacetSettleInput, HOOK_PRIORITIES, type InsertTextParam, type Keymap, type KeymapPlatform, type MigrationReport, type NormalPositionSnapshot, PIPELINE_PHASES, PSEUDO_LOCALE_CLOSE, PSEUDO_LOCALE_OPEN, type PendingBlock, type PendingBlockImportPolicyViolation, type PendingBlockProfilePolicyViolation, type ProfilePolicyViolation, type SchemaA11yAttrs, type SchemaA11yKind, SchemaEngineImpl, type SchemaRegistryConfig, SchemaRegistryImpl, type SelectBlockParam, SelectionAuthorityImpl as SelectionAuthority, type StructureBlockParam, type ToggleMarkParam, type UrlContext, type UrlPolicy, type VerticalCaretDirection, type VerticalCaretMeasure, type VerticalCaretMeasureResult, type VerticalCaretPoint, type WordRange, a11yLabelFacet, affectedBlockIdsFromSummary, aiAutocompleteControllerFacet, aiControllerFacet, aiEgressExtension, aiEgressFacet, aiInlineCompletionFacet, aiInlineHistoryFacet, aiReviewControllerFacet, aiSuggestionsControllerFacet, announceEditorA11y, announcerFacet, applyDirectedBinding, applyMergeBlocks, applySplitBlock, ariaReadOnlyFacet, assetProviderFacet, beforeApplyFacet, blockDirectionFacet, blockLogicalText, blocksToOps, buildMergeBlocksRecipe, buildNormalPositionSnapshot, buildSplitBlockRecipe, buildTableChildren, builtinCommandHandlers, caretBlockEnd, caretBlockStart, caretDocEnd, caretDocStart, caretDown, caretLeft, caretLineEnd, caretLineStart, caretRight, caretUp, caretWordLeft, caretWordRight, clipboardFacet, collectEditorKeyBindings, collectToolExecutionOutput, commandHandler, commandsFacet, convertBlock, convertBlockOps, createAppHandle, createBlockHandle, createCommandRegistry, createDecorationSet, createDocumentSession, createEditor, createEmptySchema, createFacetRegistry, createHeadlessEditor, createImportResult, createMutationGroupMetadata, createPseudoLocaleCatalog, createTextSelection, decorationsFacet, deepEqual, defaultDirectionFacet, defaultKeymapBindings, defineBlock, defineCommand, defineExtension, defineFacet, delegatesToGridEditing, deleteAdjacentInlineAtom, deleteBackward, deleteBlock, deleteForward, deriveContentMoves, duplicateBlock, emptyDecorationSet, ensureInlineCompletionController, fieldEditorHostFacet, filterAIRequest, filterOpsForDocumentProfile, filterPendingBlocksForDocumentProfile, foldAndNormalize, getApplyOptionsGroupId, getBlockSelectionRoleFromSchema, getBlockSelectionRoleFromType, getCellCaretFocus, getCommandRegistry, getEditorSelectionRecord, getFlowCapabilityFromSchema, getFlowCapabilityFromType, getInlineCompletionController, getNumberedListItemValue, getOpOriginGroupId, getOpOriginType, getSelectionBlockRange, getVerticalCaretGoalX, getVerticalCaretMeasure, hasFieldEditorSurface, hasIndexedCellSelectionMetadata, historyRedo, historyUndo, hookPriorityToPrecedence, indent, inputRulesEngineFacet, inputRulesFacet, insertLineBreak, insertText, interpolateMessage, isBlockSelected, isCollapsed, isContinuousTextFlowCapability, isMultiBlock, isPseudoLocaleText, keyBindingPriorityToPrecedence, keymapFacet, localeFacet, mapOffsetThroughSplices, mergeDecorationSets, mergeSchemas, messagesFacet, moveBlockDown, moveBlockUp, multiplayerControllerFacet, nextGraphemeBoundary, nextWordBoundary, normalizePendingBlocksForImport, orderSlashMenuItemsByGroup, outdent, previousGraphemeBoundary, previousWordBoundary, priorityToPrecedence, prop, renderSelectionTargetBlockText, renderSelectionTargetText, repairAnchor, reportPendingBlockImportViolations, reportPendingBlockProfileViolations, resolveA11yBlockTypeLabel, resolveA11ySpec, resolveBlockDirection, resolveBlockFlowCapability, resolveCellSelectionCoord, resolveCellSelectionMatrix, resolveDefaultKeymap, resolveDirectedBinding, resolveDirectedCommand, resolveEditorA11yLabel, resolveEditorMessage, resolveEditorSchema, resolveFieldEditorBehavior, resolveFieldEditorInputMode, resolveFocusBlockDirection, resolveMessage, resolveSchema, resolveSchemaA11y, resolveSelectionTargetBlockIds, runMigrations, searchControllerFacet, selectAdjacentInlineAtom, selectAll, selectBlock, selectionToRange, setCellCaretFocus, setVerticalCaretGoalX, setVerticalCaretMeasure, shouldAllowDirectBlockPaste, shouldAllowFlowInsertionInSlashMenu, shouldExposeBlockInTooling, shouldForceBlockScopedSelectAll, shouldShowBlockInDefaultMenus, singleController, slashMenuGroupOf, snapToNormalPosition, snapshotsControllerFacet, sortDeltaAttributes, spliceDeleteOp, spliceInsertOp, splitBlock, streamThroughEgress, streamingTargetFacet, suggestion, supportsInlineInputRules, supportsInlineMarks, tableCellDown, tableCellNext, tableCellPrev, tableEscapeGrid, toPseudoLocaleText, toggleMark, toolRuntimeFacet, undoManagerFacet, undoMetadataControllerFacet, undoRestoreControllerFacet, urlPolicy, urlPolicyFacet, usesInlineTextSelection, wordRangeAt };
package/dist/index.d.ts CHANGED
@@ -119,6 +119,25 @@ declare class SchemaRegistryImpl implements ComposableSchema {
119
119
  }
120
120
  declare function mergeSchemas(...registries: SchemaRegistry[]): ComposableSchema;
121
121
 
122
+ /** Display group key used to cluster slash-menu items, or `"other"` when unset. */
123
+ declare function slashMenuGroupOf(display: BlockDisplay): string;
124
+ /**
125
+ * Reorder items so blocks sharing a group sit together, groups keeping the
126
+ * order they first appear in and items their incoming order.
127
+ *
128
+ * A slash menu renders grouped and navigates by index into this same array, so
129
+ * the grouping belongs here rather than at render time: an item's position here
130
+ * is what the menu's `confirm(index)` resolves, and a list that regrouped on
131
+ * its own would hand back the index of a different block.
132
+ *
133
+ * `allBlockDisplays()` returns registration order, which interleaves. The
134
+ * default schema registers the three list blocks between `heading` and
135
+ * `codeBlock`, so `basic` resumes after `lists` has already started.
136
+ */
137
+ declare function orderSlashMenuItemsByGroup<T extends {
138
+ display: BlockDisplay;
139
+ }>(items: readonly T[]): T[];
140
+
122
141
  type DefineBlockConfig = Omit<Partial<BlockSchema<string, Record<string, PropSchema>, ContentType>>, "type" | "propSchema" | "validateProps"> & {
123
142
  props?: Record<string, unknown>;
124
143
  propSchema?: Record<string, unknown>;
@@ -184,6 +203,13 @@ declare class SchemaEngineImpl implements SchemaEngine {
184
203
  constructor(registry: SchemaRegistry, doc: PenDocument, crdtDoc: CRDTDocument, onDiagnostic?: DiagnosticSink);
185
204
  setOnDiagnostic(onDiagnostic: DiagnosticSink | undefined): void;
186
205
  markDirty(blockId: string): void;
206
+ /**
207
+ * Drop the cached pass index because `blockOrder` or a `children` array
208
+ * changed outside this engine — an executing op, or a remote/undo update.
209
+ * Every structural mutation the engine performs itself already invalidates
210
+ * at the mutation site, so those callers do not go through here.
211
+ */
212
+ notifyStructureChanged(): void;
187
213
  deferBlock(blockId: string): void;
188
214
  undeferBlock(blockId: string): void;
189
215
  normalizeDirty(): void;
@@ -995,4 +1021,4 @@ declare function affectedBlockIdsFromSummary(summary: Pick<ChangeSummary, "block
995
1021
  */
996
1022
  declare function mapOffsetThroughSplices(splices: readonly TextSplice[], offset: number, assoc: Assoc): number;
997
1023
 
998
- export { A11Y_MISSING_LABEL_CODE, APPLY_STORM_CODE, APPLY_STORM_QUEUE_LIMIT, type BeforeApplyHook, type BlockDirection, type BlockDirectionResolver, type BlockDirectionSetting, type CaretMotionParam, type CellCaretFocus, type CellCaretWrite, type ClipboardHandler, type CommandDispatchContext, type CommandHandlerTable, type CommandRegistry, type ContentMove, type ConvertBlockParam, type CreateCommandRegistryOptions, type CreateFacetRegistryOptions, type CreateHeadlessEditorOptions, type DecorationSource, type DefaultKeymapBinding, type DefaultKeymapContext, type DefinedBlockSchema, type DeleteGranularity, type DeleteParam, type DocumentMigration, DocumentRangeImpl, type EditorA11yLabelAttrs, EventEmitter, ExtensionManagerImpl, type FacetRegistry, type FacetSettleInput, HOOK_PRIORITIES, type InsertTextParam, type Keymap, type KeymapPlatform, type MigrationReport, type NormalPositionSnapshot, PIPELINE_PHASES, PSEUDO_LOCALE_CLOSE, PSEUDO_LOCALE_OPEN, type PendingBlock, type PendingBlockImportPolicyViolation, type PendingBlockProfilePolicyViolation, type ProfilePolicyViolation, type SchemaA11yAttrs, type SchemaA11yKind, SchemaEngineImpl, type SchemaRegistryConfig, SchemaRegistryImpl, type SelectBlockParam, SelectionAuthorityImpl as SelectionAuthority, type StructureBlockParam, type ToggleMarkParam, type UrlContext, type UrlPolicy, type VerticalCaretDirection, type VerticalCaretMeasure, type VerticalCaretMeasureResult, type VerticalCaretPoint, type WordRange, a11yLabelFacet, affectedBlockIdsFromSummary, aiAutocompleteControllerFacet, aiControllerFacet, aiEgressExtension, aiEgressFacet, aiInlineCompletionFacet, aiInlineHistoryFacet, aiReviewControllerFacet, aiSuggestionsControllerFacet, announceEditorA11y, announcerFacet, applyDirectedBinding, applyMergeBlocks, applySplitBlock, ariaReadOnlyFacet, assetProviderFacet, beforeApplyFacet, blockDirectionFacet, blockLogicalText, blocksToOps, buildMergeBlocksRecipe, buildNormalPositionSnapshot, buildSplitBlockRecipe, buildTableChildren, builtinCommandHandlers, caretBlockEnd, caretBlockStart, caretDocEnd, caretDocStart, caretDown, caretLeft, caretLineEnd, caretLineStart, caretRight, caretUp, caretWordLeft, caretWordRight, clipboardFacet, collectEditorKeyBindings, collectToolExecutionOutput, commandHandler, commandsFacet, convertBlock, convertBlockOps, createAppHandle, createBlockHandle, createCommandRegistry, createDecorationSet, createDocumentSession, createEditor, createEmptySchema, createFacetRegistry, createHeadlessEditor, createImportResult, createMutationGroupMetadata, createPseudoLocaleCatalog, createTextSelection, decorationsFacet, deepEqual, defaultDirectionFacet, defaultKeymapBindings, defineBlock, defineCommand, defineExtension, defineFacet, delegatesToGridEditing, deleteAdjacentInlineAtom, deleteBackward, deleteBlock, deleteForward, deriveContentMoves, duplicateBlock, emptyDecorationSet, ensureInlineCompletionController, fieldEditorHostFacet, filterAIRequest, filterOpsForDocumentProfile, filterPendingBlocksForDocumentProfile, foldAndNormalize, getApplyOptionsGroupId, getBlockSelectionRoleFromSchema, getBlockSelectionRoleFromType, getCellCaretFocus, getCommandRegistry, getEditorSelectionRecord, getFlowCapabilityFromSchema, getFlowCapabilityFromType, getInlineCompletionController, getNumberedListItemValue, getOpOriginGroupId, getOpOriginType, getSelectionBlockRange, getVerticalCaretGoalX, getVerticalCaretMeasure, hasFieldEditorSurface, hasIndexedCellSelectionMetadata, historyRedo, historyUndo, hookPriorityToPrecedence, indent, inputRulesEngineFacet, inputRulesFacet, insertLineBreak, insertText, interpolateMessage, isBlockSelected, isCollapsed, isContinuousTextFlowCapability, isMultiBlock, isPseudoLocaleText, keyBindingPriorityToPrecedence, keymapFacet, localeFacet, mapOffsetThroughSplices, mergeDecorationSets, mergeSchemas, messagesFacet, moveBlockDown, moveBlockUp, multiplayerControllerFacet, nextGraphemeBoundary, nextWordBoundary, normalizePendingBlocksForImport, outdent, previousGraphemeBoundary, previousWordBoundary, priorityToPrecedence, prop, renderSelectionTargetBlockText, renderSelectionTargetText, repairAnchor, reportPendingBlockImportViolations, reportPendingBlockProfileViolations, resolveA11yBlockTypeLabel, resolveA11ySpec, resolveBlockDirection, resolveBlockFlowCapability, resolveCellSelectionCoord, resolveCellSelectionMatrix, resolveDefaultKeymap, resolveDirectedBinding, resolveDirectedCommand, resolveEditorA11yLabel, resolveEditorMessage, resolveEditorSchema, resolveFieldEditorBehavior, resolveFieldEditorInputMode, resolveFocusBlockDirection, resolveMessage, resolveSchema, resolveSchemaA11y, resolveSelectionTargetBlockIds, runMigrations, searchControllerFacet, selectAdjacentInlineAtom, selectAll, selectBlock, selectionToRange, setCellCaretFocus, setVerticalCaretGoalX, setVerticalCaretMeasure, shouldAllowDirectBlockPaste, shouldAllowFlowInsertionInSlashMenu, shouldExposeBlockInTooling, shouldForceBlockScopedSelectAll, shouldShowBlockInDefaultMenus, singleController, snapToNormalPosition, snapshotsControllerFacet, sortDeltaAttributes, spliceDeleteOp, spliceInsertOp, splitBlock, streamThroughEgress, streamingTargetFacet, suggestion, supportsInlineInputRules, supportsInlineMarks, tableCellDown, tableCellNext, tableCellPrev, tableEscapeGrid, toPseudoLocaleText, toggleMark, toolRuntimeFacet, undoManagerFacet, undoMetadataControllerFacet, undoRestoreControllerFacet, urlPolicy, urlPolicyFacet, usesInlineTextSelection, wordRangeAt };
1024
+ export { A11Y_MISSING_LABEL_CODE, APPLY_STORM_CODE, APPLY_STORM_QUEUE_LIMIT, type BeforeApplyHook, type BlockDirection, type BlockDirectionResolver, type BlockDirectionSetting, type CaretMotionParam, type CellCaretFocus, type CellCaretWrite, type ClipboardHandler, type CommandDispatchContext, type CommandHandlerTable, type CommandRegistry, type ContentMove, type ConvertBlockParam, type CreateCommandRegistryOptions, type CreateFacetRegistryOptions, type CreateHeadlessEditorOptions, type DecorationSource, type DefaultKeymapBinding, type DefaultKeymapContext, type DefinedBlockSchema, type DeleteGranularity, type DeleteParam, type DocumentMigration, DocumentRangeImpl, type EditorA11yLabelAttrs, EventEmitter, ExtensionManagerImpl, type FacetRegistry, type FacetSettleInput, HOOK_PRIORITIES, type InsertTextParam, type Keymap, type KeymapPlatform, type MigrationReport, type NormalPositionSnapshot, PIPELINE_PHASES, PSEUDO_LOCALE_CLOSE, PSEUDO_LOCALE_OPEN, type PendingBlock, type PendingBlockImportPolicyViolation, type PendingBlockProfilePolicyViolation, type ProfilePolicyViolation, type SchemaA11yAttrs, type SchemaA11yKind, SchemaEngineImpl, type SchemaRegistryConfig, SchemaRegistryImpl, type SelectBlockParam, SelectionAuthorityImpl as SelectionAuthority, type StructureBlockParam, type ToggleMarkParam, type UrlContext, type UrlPolicy, type VerticalCaretDirection, type VerticalCaretMeasure, type VerticalCaretMeasureResult, type VerticalCaretPoint, type WordRange, a11yLabelFacet, affectedBlockIdsFromSummary, aiAutocompleteControllerFacet, aiControllerFacet, aiEgressExtension, aiEgressFacet, aiInlineCompletionFacet, aiInlineHistoryFacet, aiReviewControllerFacet, aiSuggestionsControllerFacet, announceEditorA11y, announcerFacet, applyDirectedBinding, applyMergeBlocks, applySplitBlock, ariaReadOnlyFacet, assetProviderFacet, beforeApplyFacet, blockDirectionFacet, blockLogicalText, blocksToOps, buildMergeBlocksRecipe, buildNormalPositionSnapshot, buildSplitBlockRecipe, buildTableChildren, builtinCommandHandlers, caretBlockEnd, caretBlockStart, caretDocEnd, caretDocStart, caretDown, caretLeft, caretLineEnd, caretLineStart, caretRight, caretUp, caretWordLeft, caretWordRight, clipboardFacet, collectEditorKeyBindings, collectToolExecutionOutput, commandHandler, commandsFacet, convertBlock, convertBlockOps, createAppHandle, createBlockHandle, createCommandRegistry, createDecorationSet, createDocumentSession, createEditor, createEmptySchema, createFacetRegistry, createHeadlessEditor, createImportResult, createMutationGroupMetadata, createPseudoLocaleCatalog, createTextSelection, decorationsFacet, deepEqual, defaultDirectionFacet, defaultKeymapBindings, defineBlock, defineCommand, defineExtension, defineFacet, delegatesToGridEditing, deleteAdjacentInlineAtom, deleteBackward, deleteBlock, deleteForward, deriveContentMoves, duplicateBlock, emptyDecorationSet, ensureInlineCompletionController, fieldEditorHostFacet, filterAIRequest, filterOpsForDocumentProfile, filterPendingBlocksForDocumentProfile, foldAndNormalize, getApplyOptionsGroupId, getBlockSelectionRoleFromSchema, getBlockSelectionRoleFromType, getCellCaretFocus, getCommandRegistry, getEditorSelectionRecord, getFlowCapabilityFromSchema, getFlowCapabilityFromType, getInlineCompletionController, getNumberedListItemValue, getOpOriginGroupId, getOpOriginType, getSelectionBlockRange, getVerticalCaretGoalX, getVerticalCaretMeasure, hasFieldEditorSurface, hasIndexedCellSelectionMetadata, historyRedo, historyUndo, hookPriorityToPrecedence, indent, inputRulesEngineFacet, inputRulesFacet, insertLineBreak, insertText, interpolateMessage, isBlockSelected, isCollapsed, isContinuousTextFlowCapability, isMultiBlock, isPseudoLocaleText, keyBindingPriorityToPrecedence, keymapFacet, localeFacet, mapOffsetThroughSplices, mergeDecorationSets, mergeSchemas, messagesFacet, moveBlockDown, moveBlockUp, multiplayerControllerFacet, nextGraphemeBoundary, nextWordBoundary, normalizePendingBlocksForImport, orderSlashMenuItemsByGroup, outdent, previousGraphemeBoundary, previousWordBoundary, priorityToPrecedence, prop, renderSelectionTargetBlockText, renderSelectionTargetText, repairAnchor, reportPendingBlockImportViolations, reportPendingBlockProfileViolations, resolveA11yBlockTypeLabel, resolveA11ySpec, resolveBlockDirection, resolveBlockFlowCapability, resolveCellSelectionCoord, resolveCellSelectionMatrix, resolveDefaultKeymap, resolveDirectedBinding, resolveDirectedCommand, resolveEditorA11yLabel, resolveEditorMessage, resolveEditorSchema, resolveFieldEditorBehavior, resolveFieldEditorInputMode, resolveFocusBlockDirection, resolveMessage, resolveSchema, resolveSchemaA11y, resolveSelectionTargetBlockIds, runMigrations, searchControllerFacet, selectAdjacentInlineAtom, selectAll, selectBlock, selectionToRange, setCellCaretFocus, setVerticalCaretGoalX, setVerticalCaretMeasure, shouldAllowDirectBlockPaste, shouldAllowFlowInsertionInSlashMenu, shouldExposeBlockInTooling, shouldForceBlockScopedSelectAll, shouldShowBlockInDefaultMenus, singleController, slashMenuGroupOf, snapToNormalPosition, snapshotsControllerFacet, sortDeltaAttributes, spliceDeleteOp, spliceInsertOp, splitBlock, streamThroughEgress, streamingTargetFacet, suggestion, supportsInlineInputRules, supportsInlineMarks, tableCellDown, tableCellNext, tableCellPrev, tableEscapeGrid, toPseudoLocaleText, toggleMark, toolRuntimeFacet, undoManagerFacet, undoMetadataControllerFacet, undoRestoreControllerFacet, urlPolicy, urlPolicyFacet, usesInlineTextSelection, wordRangeAt };
package/dist/index.mjs CHANGED
@@ -692,6 +692,25 @@ function mergeSchemas(...registries2) {
692
692
  });
693
693
  }
694
694
 
695
+ // src/schema/slashMenuOrder.ts
696
+ var FALLBACK_GROUP = "other";
697
+ function slashMenuGroupOf(display) {
698
+ return display.group ?? FALLBACK_GROUP;
699
+ }
700
+ function orderSlashMenuItemsByGroup(items) {
701
+ const grouped = /* @__PURE__ */ new Map();
702
+ for (const item of items) {
703
+ const group = slashMenuGroupOf(item.display);
704
+ const existing = grouped.get(group);
705
+ if (existing) {
706
+ existing.push(item);
707
+ } else {
708
+ grouped.set(group, [item]);
709
+ }
710
+ }
711
+ return [...grouped.values()].flat();
712
+ }
713
+
695
714
  // src/schema/defineBlock.ts
696
715
  function resolveProps2(config) {
697
716
  const raw = config.props ?? config.propSchema ?? {};
@@ -971,6 +990,15 @@ var SchemaEngineImpl = class {
971
990
  markDirty(blockId) {
972
991
  this.dirtyBlockIds.add(blockId);
973
992
  }
993
+ /**
994
+ * Drop the cached pass index because `blockOrder` or a `children` array
995
+ * changed outside this engine — an executing op, or a remote/undo update.
996
+ * Every structural mutation the engine performs itself already invalidates
997
+ * at the mutation site, so those callers do not go through here.
998
+ */
999
+ notifyStructureChanged() {
1000
+ this.invalidatePassIndex();
1001
+ }
974
1002
  deferBlock(blockId) {
975
1003
  this.deferredBlockIds.add(blockId);
976
1004
  }
@@ -990,7 +1018,6 @@ var SchemaEngineImpl = class {
990
1018
  }
991
1019
  iterations++;
992
1020
  this.dirtyBlockIds.clear();
993
- this.invalidatePassIndex();
994
1021
  this.doc.adapter.transact(this.crdtDoc, () => {
995
1022
  for (const blockId of snapshot) {
996
1023
  if (this.deferredBlockIds.has(blockId)) {
@@ -1001,7 +1028,6 @@ var SchemaEngineImpl = class {
1001
1028
  }
1002
1029
  });
1003
1030
  }
1004
- this.invalidatePassIndex();
1005
1031
  if (iterations >= MAX_ITERATIONS) {
1006
1032
  this.onDiagnostic?.({
1007
1033
  code: "normalize-cap",
@@ -1013,6 +1039,7 @@ var SchemaEngineImpl = class {
1013
1039
  }
1014
1040
  }
1015
1041
  normalizeAll() {
1042
+ this.invalidatePassIndex();
1016
1043
  for (const blockId of this.doc.blocks.keys()) {
1017
1044
  this.dirtyBlockIds.add(blockId);
1018
1045
  }
@@ -3227,6 +3254,11 @@ function emitSchemaUnknownBlock(pipeline, type) {
3227
3254
  });
3228
3255
  }
3229
3256
  function reportUnknownBlocksInDocument(pipeline) {
3257
+ const blockCount = pipeline._doc.blocks.size;
3258
+ if (blockCount === pipeline._unknownScanBlockCount) {
3259
+ return;
3260
+ }
3261
+ pipeline._unknownScanBlockCount = blockCount;
3230
3262
  for (const [, rawBlockMap] of pipeline._doc.blocks.entries()) {
3231
3263
  if (!isCRDTMap(rawBlockMap)) {
3232
3264
  continue;
@@ -3526,6 +3558,9 @@ function emitMalformedOpDiagnostic(pipeline, op, error) {
3526
3558
  ...error !== void 0 ? { error } : {}
3527
3559
  });
3528
3560
  }
3561
+ function isStructuralOp(op) {
3562
+ return op.type === "insert-block" || op.type === "delete-block" || op.type === "move-block";
3563
+ }
3529
3564
  function executeOps(pipeline, ops, origin, structural) {
3530
3565
  pipeline._commitDiagnostics = [];
3531
3566
  reportUnknownBlocksInDocument(pipeline);
@@ -3592,6 +3627,9 @@ function executeOps(pipeline, ops, origin, structural) {
3592
3627
  } catch (err) {
3593
3628
  emitMalformedOpDiagnostic(pipeline, op, err);
3594
3629
  }
3630
+ if (isStructuralOp(op)) {
3631
+ pipeline._engine.notifyStructureChanged();
3632
+ }
3595
3633
  }
3596
3634
  for (const blockId of affectedBlocks) {
3597
3635
  pipeline._engine.markDirty(blockId);
@@ -3763,6 +3801,7 @@ var ApplyPipeline = class {
3763
3801
  _applyStormEmitted = false;
3764
3802
  _suppressObserver = false;
3765
3803
  _unknownBlockTypesReported;
3804
+ _unknownScanBlockCount;
3766
3805
  _queue = [];
3767
3806
  _applyBoundaryHooks = [];
3768
3807
  _beforeApplyHooks = [];
@@ -3846,6 +3885,7 @@ var ApplyPipeline = class {
3846
3885
  this._doc = doc;
3847
3886
  this._crdtDoc = crdtDoc;
3848
3887
  this._engine = engine;
3888
+ this._unknownScanBlockCount = void 0;
3849
3889
  }
3850
3890
  };
3851
3891
 
@@ -10660,44 +10700,20 @@ function createBlockIndex(initial) {
10660
10700
  snapshot() {
10661
10701
  return current;
10662
10702
  },
10663
- apply(summary) {
10664
- current = applySummaryToSnapshot(current, summary);
10703
+ applyTextLengths(blockText) {
10704
+ for (const change of blockText) {
10705
+ const previous = current.lengthById.get(change.blockId) ?? 0;
10706
+ current.lengthById.set(
10707
+ change.blockId,
10708
+ lengthAfterSplices(previous, change.splices)
10709
+ );
10710
+ }
10665
10711
  },
10666
10712
  replace(snapshot) {
10667
10713
  current = cloneSnapshot(snapshot);
10668
10714
  }
10669
10715
  };
10670
10716
  }
10671
- function applySummaryToSnapshot(snapshot, summary) {
10672
- const lengthById = new Map(snapshot.lengthById);
10673
- const typeById = new Map(snapshot.typeById);
10674
- const childrenByParentId = cloneChildren(snapshot.childrenByParentId);
10675
- for (const change of summary.structural) {
10676
- applyStructural(change, lengthById, typeById, childrenByParentId);
10677
- }
10678
- for (const text of summary.blockText) {
10679
- const previous = lengthById.get(text.blockId) ?? 0;
10680
- lengthById.set(
10681
- text.blockId,
10682
- lengthAfterSplices(previous, text.splices)
10683
- );
10684
- }
10685
- const roots = [...childrenByParentId.get(null) ?? []];
10686
- const parentById = /* @__PURE__ */ new Map();
10687
- for (const [parentId, children] of childrenByParentId) {
10688
- for (const childId of children) {
10689
- parentById.set(childId, parentId);
10690
- }
10691
- }
10692
- return {
10693
- lengthById,
10694
- typeById,
10695
- parentById,
10696
- childrenByParentId,
10697
- order: flattenOrder(roots, childrenByParentId),
10698
- roots
10699
- };
10700
- }
10701
10717
  function lengthAfterSplices(length, splices) {
10702
10718
  let next = length;
10703
10719
  for (const splice of splices) {
@@ -10715,120 +10731,6 @@ function cloneSnapshot(snapshot) {
10715
10731
  roots: [...snapshot.roots]
10716
10732
  };
10717
10733
  }
10718
- function applyStructural(change, lengthById, typeById, childrenByParentId) {
10719
- switch (change.type) {
10720
- case "block-inserted": {
10721
- insertChild(
10722
- childrenByParentId,
10723
- change.parentId,
10724
- change.index,
10725
- change.blockId
10726
- );
10727
- if (!lengthById.has(change.blockId))
10728
- lengthById.set(change.blockId, 0);
10729
- break;
10730
- }
10731
- case "block-removed": {
10732
- removeChild(childrenByParentId, change.parentId, change.blockId);
10733
- lengthById.delete(change.blockId);
10734
- typeById.delete(change.blockId);
10735
- childrenByParentId.delete(change.blockId);
10736
- break;
10737
- }
10738
- case "block-moved": {
10739
- removeChild(
10740
- childrenByParentId,
10741
- change.fromParentId,
10742
- change.blockId
10743
- );
10744
- insertChild(
10745
- childrenByParentId,
10746
- change.toParentId,
10747
- change.toIndex,
10748
- change.blockId
10749
- );
10750
- break;
10751
- }
10752
- case "block-split": {
10753
- insertAfter(childrenByParentId, change.blockId, change.newBlockId);
10754
- const original = lengthById.get(change.blockId) ?? 0;
10755
- lengthById.set(change.blockId, Math.max(0, change.offset));
10756
- lengthById.set(
10757
- change.newBlockId,
10758
- Math.max(0, original - change.offset)
10759
- );
10760
- if (!typeById.has(change.newBlockId)) {
10761
- typeById.set(
10762
- change.newBlockId,
10763
- typeById.get(change.blockId) ?? ""
10764
- );
10765
- }
10766
- break;
10767
- }
10768
- case "blocks-merged": {
10769
- const parentId = parentOf(childrenByParentId, change.sourceBlockId);
10770
- const targetLength = lengthById.get(change.targetBlockId) ?? 0;
10771
- const sourceLength = lengthById.get(change.sourceBlockId) ?? 0;
10772
- lengthById.set(change.targetBlockId, targetLength + sourceLength);
10773
- removeChild(childrenByParentId, parentId, change.sourceBlockId);
10774
- lengthById.delete(change.sourceBlockId);
10775
- typeById.delete(change.sourceBlockId);
10776
- childrenByParentId.delete(change.sourceBlockId);
10777
- break;
10778
- }
10779
- case "block-props-changed":
10780
- case "table-changed":
10781
- case "apps-changed":
10782
- case "metadata-changed":
10783
- break;
10784
- default: {
10785
- const _exhaustive = change;
10786
- return _exhaustive;
10787
- }
10788
- }
10789
- }
10790
- function insertAfter(childrenByParentId, beforeId, newId) {
10791
- for (const [parentId, children] of childrenByParentId) {
10792
- const index = children.indexOf(beforeId);
10793
- if (index < 0) continue;
10794
- if (!children.includes(newId)) {
10795
- children.splice(index + 1, 0, newId);
10796
- }
10797
- childrenByParentId.set(parentId, children);
10798
- return;
10799
- }
10800
- insertChild(childrenByParentId, null, -1, newId);
10801
- }
10802
- function insertChild(childrenByParentId, parentId, index, blockId) {
10803
- const children = childrenByParentId.get(parentId) ?? [];
10804
- const next = children.filter((id) => id !== blockId);
10805
- const at = index < 0 || index > next.length ? next.length : index;
10806
- next.splice(at, 0, blockId);
10807
- childrenByParentId.set(parentId, next);
10808
- }
10809
- function removeChild(childrenByParentId, parentId, blockId) {
10810
- const children = childrenByParentId.get(parentId);
10811
- if (!children) {
10812
- for (const [id, list] of childrenByParentId) {
10813
- const index2 = list.indexOf(blockId);
10814
- if (index2 >= 0) {
10815
- list.splice(index2, 1);
10816
- childrenByParentId.set(id, list);
10817
- return;
10818
- }
10819
- }
10820
- return;
10821
- }
10822
- const index = children.indexOf(blockId);
10823
- if (index >= 0) children.splice(index, 1);
10824
- childrenByParentId.set(parentId, children);
10825
- }
10826
- function parentOf(childrenByParentId, blockId) {
10827
- for (const [parentId, children] of childrenByParentId) {
10828
- if (children.includes(blockId)) return parentId;
10829
- }
10830
- return null;
10831
- }
10832
10734
  function flattenOrder(roots, childrenByParentId) {
10833
10735
  const order = [];
10834
10736
  const visit = (id) => {
@@ -11418,7 +11320,7 @@ function affectedBlockIdsFromSummary(summary, documentOrder) {
11418
11320
  addStructuralBlockIds(ids, change);
11419
11321
  }
11420
11322
  const collected = [...ids];
11421
- if (!documentOrder || documentOrder.length === 0) {
11323
+ if (collected.length < 2 || !documentOrder || documentOrder.length === 0) {
11422
11324
  return collected;
11423
11325
  }
11424
11326
  const rank = /* @__PURE__ */ new Map();
@@ -11840,14 +11742,22 @@ function installChangeSummaries(host) {
11840
11742
  host._unsubSummary = createSummarySource(
11841
11743
  host._crdtDoc,
11842
11744
  (delta) => {
11843
- host._pendingSummary = buildChangeSummary(
11745
+ if (delta.blockOrderDelta.length > 0 || delta.childArrayDeltas.size > 0) {
11746
+ host._engine.notifyStructureChanged();
11747
+ }
11748
+ const summary = buildChangeSummary(
11844
11749
  delta,
11845
11750
  host._blockIndex.snapshot(),
11846
11751
  0
11847
11752
  );
11848
- host._blockIndex.replace(
11849
- createBlockIndexSnapshotFromDocument(host._doc)
11850
- );
11753
+ host._pendingSummary = summary;
11754
+ if (summary.structural.length === 0) {
11755
+ host._blockIndex.applyTextLengths(summary.blockText);
11756
+ } else {
11757
+ host._blockIndex.replace(
11758
+ createBlockIndexSnapshotFromDocument(host._doc)
11759
+ );
11760
+ }
11851
11761
  flushDeferredCRDTEvent(host);
11852
11762
  }
11853
11763
  );
@@ -14417,6 +14327,16 @@ var macosBindings = [
14417
14327
  key: "Alt-Delete",
14418
14328
  command: deleteForward,
14419
14329
  param: { granularity: "word" }
14330
+ },
14331
+ {
14332
+ key: "Meta-Backspace",
14333
+ command: deleteBackward,
14334
+ param: { granularity: "line" }
14335
+ },
14336
+ {
14337
+ key: "Ctrl-k",
14338
+ command: deleteForward,
14339
+ param: { granularity: "line" }
14420
14340
  }
14421
14341
  ];
14422
14342
  var windowsLinuxBindings = [
@@ -14926,6 +14846,7 @@ export {
14926
14846
  nextGraphemeBoundary,
14927
14847
  nextWordBoundary,
14928
14848
  normalizePendingBlocksForImport,
14849
+ orderSlashMenuItemsByGroup,
14929
14850
  outdent,
14930
14851
  previousGraphemeBoundary,
14931
14852
  previousWordBoundary,
@@ -14970,6 +14891,7 @@ export {
14970
14891
  shouldForceBlockScopedSelectAll,
14971
14892
  shouldShowBlockInDefaultMenus,
14972
14893
  singleController,
14894
+ slashMenuGroupOf,
14973
14895
  snapToNormalPosition,
14974
14896
  snapshotsControllerFacet,
14975
14897
  sortDeltaAttributes,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@input/pen-core",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Headless, extension-first editor engine for human-AI co-authoring",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/input-systems/pen#readme",
@@ -43,8 +43,8 @@
43
43
  },
44
44
  "sideEffects": false,
45
45
  "dependencies": {
46
- "@input/pen-yjs": "^0.1.0",
47
- "@input/pen-types": "^0.1.0"
46
+ "@input/pen-yjs": "^0.1.1",
47
+ "@input/pen-types": "^0.1.1"
48
48
  },
49
49
  "devDependencies": {
50
50
  "tsup": "^8.4.0",