@input/pen-core 0.1.6 → 0.1.8

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
@@ -134,6 +134,7 @@ __export(index_exports, {
134
134
  historyUndo: () => historyUndo,
135
135
  hookPriorityToPrecedence: () => hookPriorityToPrecedence,
136
136
  indent: () => indent,
137
+ inlineLogicalText: () => inlineLogicalText,
137
138
  inputRulesEngineFacet: () => inputRulesEngineFacet,
138
139
  inputRulesFacet: () => inputRulesFacet,
139
140
  insertLineBreak: () => insertLineBreak,
@@ -189,6 +190,7 @@ __export(index_exports, {
189
190
  resolveSchema: () => resolveSchema,
190
191
  resolveSchemaA11y: () => resolveSchemaA11y,
191
192
  resolveSelectionTargetBlockIds: () => resolveSelectionTargetBlockIds,
193
+ resolveSuggestionMenuTarget: () => resolveSuggestionMenuTarget,
192
194
  runMigrations: () => runMigrations,
193
195
  searchControllerFacet: () => searchControllerFacet,
194
196
  selectAdjacentInlineAtom: () => selectAdjacentInlineAtom,
@@ -589,6 +591,47 @@ function filterOpsForDocumentProfile(ops, documentProfile, registry) {
589
591
  };
590
592
  }
591
593
 
594
+ // src/schema/generateValidator.ts
595
+ function generateValidator(propSchemas) {
596
+ return (raw) => {
597
+ const result = {};
598
+ for (const [key, schema] of Object.entries(propSchemas)) {
599
+ let value = raw[key];
600
+ if (value === void 0 || value === null) {
601
+ result[key] = schema.default;
602
+ continue;
603
+ }
604
+ const schemaType = Array.isArray(schema.type) ? schema.type[0] : schema.type;
605
+ if (schemaType === "number" && typeof value === "string") {
606
+ const parsed = Number(value);
607
+ if (!Number.isNaN(parsed)) value = parsed;
608
+ }
609
+ if (schemaType === "boolean" && typeof value === "string") {
610
+ value = value === "true";
611
+ }
612
+ const matchesSchemaType = schemaType === "array" ? Array.isArray(value) : typeof value === schemaType;
613
+ if (schema.type && schemaType !== void 0 && !matchesSchemaType) {
614
+ result[key] = schema.default;
615
+ continue;
616
+ }
617
+ if (typeof value === "number") {
618
+ if (schema.minimum !== void 0 && value < schema.minimum) {
619
+ value = schema.minimum;
620
+ }
621
+ if (typeof value === "number" && schema.maximum !== void 0 && value > schema.maximum) {
622
+ value = schema.maximum;
623
+ }
624
+ }
625
+ if (schema.enum && !schema.enum.includes(value)) {
626
+ result[key] = schema.default;
627
+ continue;
628
+ }
629
+ result[key] = value;
630
+ }
631
+ return result;
632
+ };
633
+ }
634
+
592
635
  // src/schema/prop.ts
593
636
  var PropChainImpl = class {
594
637
  _schema;
@@ -895,6 +938,10 @@ var SchemaRegistryImpl = class _SchemaRegistryImpl {
895
938
  if (patch.serialize) {
896
939
  merged.serialize = { ...existing.serialize, ...patch.serialize };
897
940
  }
941
+ if ("propSchema" in patch && !("validateProps" in patch)) {
942
+ const propSchema = merged.propSchema ?? {};
943
+ merged.validateProps = Object.keys(propSchema).length > 0 ? generateValidator(propSchema) : void 0;
944
+ }
898
945
  const blocks = new Map(this._blocks);
899
946
  blocks.set(type, merged);
900
947
  return new _SchemaRegistryImpl({
@@ -989,45 +1036,6 @@ function generateAIDescription(type, props) {
989
1036
  }).join(", ");
990
1037
  return `${type}: ${propDescriptions}`;
991
1038
  }
992
- function generateValidator(propSchemas) {
993
- return (raw) => {
994
- const result = {};
995
- for (const [key, schema] of Object.entries(propSchemas)) {
996
- let value = raw[key];
997
- if (value === void 0 || value === null) {
998
- result[key] = schema.default;
999
- continue;
1000
- }
1001
- const schemaType = Array.isArray(schema.type) ? schema.type[0] : schema.type;
1002
- if (schemaType === "number" && typeof value === "string") {
1003
- const parsed = Number(value);
1004
- if (!Number.isNaN(parsed)) value = parsed;
1005
- }
1006
- if (schemaType === "boolean" && typeof value === "string") {
1007
- value = value === "true";
1008
- }
1009
- const matchesSchemaType = schemaType === "array" ? Array.isArray(value) : typeof value === schemaType;
1010
- if (schema.type && schemaType !== void 0 && !matchesSchemaType) {
1011
- result[key] = schema.default;
1012
- continue;
1013
- }
1014
- if (typeof value === "number") {
1015
- if (schema.minimum !== void 0 && value < schema.minimum) {
1016
- value = schema.minimum;
1017
- }
1018
- if (typeof value === "number" && schema.maximum !== void 0 && value > schema.maximum) {
1019
- value = schema.maximum;
1020
- }
1021
- }
1022
- if (schema.enum && !schema.enum.includes(value)) {
1023
- result[key] = schema.default;
1024
- continue;
1025
- }
1026
- result[key] = value;
1027
- }
1028
- return result;
1029
- };
1030
- }
1031
1039
  function defineBlock(typeOrConfig, maybeConfig) {
1032
1040
  const type = typeof typeOrConfig === "string" ? typeOrConfig : typeOrConfig.type;
1033
1041
  const config = typeof typeOrConfig === "string" ? maybeConfig : typeOrConfig;
@@ -3654,6 +3662,18 @@ function snapshotPlain(value) {
3654
3662
  writable: true
3655
3663
  });
3656
3664
  }
3665
+ for (const key of Object.getOwnPropertySymbols(value)) {
3666
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
3667
+ if (!descriptor?.enumerable) {
3668
+ continue;
3669
+ }
3670
+ Object.defineProperty(next, key, {
3671
+ value: snapshotPlain(descriptor.value),
3672
+ enumerable: true,
3673
+ configurable: true,
3674
+ writable: true
3675
+ });
3676
+ }
3657
3677
  return next;
3658
3678
  }
3659
3679
  function snapshotOps(ops) {
@@ -14475,6 +14495,77 @@ function emitMissingBlock(editor, blockId) {
14475
14495
  emit("diagnostic", event);
14476
14496
  }
14477
14497
 
14498
+ // src/suggestion/resolveSuggestionMenuTarget.ts
14499
+ var DEFAULT_LOOKBEHIND = 80;
14500
+ function inlineLogicalText(block) {
14501
+ return logicalInline(block).text;
14502
+ }
14503
+ function resolveSuggestionMenuTarget(editor, trigger) {
14504
+ if (trigger.char.length === 0) {
14505
+ return null;
14506
+ }
14507
+ const selection = editor.selection;
14508
+ if (selection?.type !== "text" || !isCollapsed(selection)) {
14509
+ return null;
14510
+ }
14511
+ if (selection.anchor.blockId !== selection.focus.blockId) {
14512
+ return null;
14513
+ }
14514
+ const block = editor.getBlock(selection.focus.blockId);
14515
+ if (!block) {
14516
+ return null;
14517
+ }
14518
+ const offset = selection.focus.offset;
14519
+ const lookbehind = trigger.lookbehind ?? DEFAULT_LOOKBEHIND;
14520
+ const prefixStartOffset = Math.max(0, offset - lookbehind);
14521
+ const { text, atoms } = logicalInline(block);
14522
+ const textBefore = text.slice(prefixStartOffset, offset);
14523
+ const triggerIndex = textBefore.lastIndexOf(trigger.char);
14524
+ if (triggerIndex < 0) {
14525
+ return null;
14526
+ }
14527
+ if (trigger.boundary === "whitespace") {
14528
+ const previousChar = textBefore[triggerIndex - 1];
14529
+ if (previousChar && !/\s/.test(previousChar)) {
14530
+ return null;
14531
+ }
14532
+ }
14533
+ const query = textBefore.slice(triggerIndex + trigger.char.length);
14534
+ const startOffset = prefixStartOffset + triggerIndex;
14535
+ const queryStartOffset = startOffset + trigger.char.length;
14536
+ if (queryRangeContainsAtom(atoms, queryStartOffset, offset)) {
14537
+ return null;
14538
+ }
14539
+ if (!trigger.allowSpaces && /\s/.test(query)) {
14540
+ return null;
14541
+ }
14542
+ if (trigger.closingChar && query.includes(trigger.closingChar)) {
14543
+ return null;
14544
+ }
14545
+ if (query.length < (trigger.minQueryLength ?? 0)) {
14546
+ return null;
14547
+ }
14548
+ if (trigger.maxQueryLength !== void 0 && query.length > trigger.maxQueryLength) {
14549
+ return null;
14550
+ }
14551
+ if (trigger.queryPattern) {
14552
+ trigger.queryPattern.lastIndex = 0;
14553
+ if (!trigger.queryPattern.test(query)) {
14554
+ return null;
14555
+ }
14556
+ }
14557
+ return {
14558
+ blockId: selection.focus.blockId,
14559
+ startOffset,
14560
+ endOffset: offset,
14561
+ query,
14562
+ trigger: trigger.char
14563
+ };
14564
+ }
14565
+ function queryRangeContainsAtom(atoms, queryStart, queryEnd) {
14566
+ return atoms.some((atom) => atom.start < queryEnd && atom.end > queryStart);
14567
+ }
14568
+
14478
14569
  // src/commands/resolveDirectedBinding.ts
14479
14570
  function resolveDirectedBinding(editor, binding) {
14480
14571
  const direction = resolveFocusBlockDirection(editor);
@@ -15091,6 +15182,7 @@ function mapOffsetThroughSplices(splices, offset, assoc) {
15091
15182
  historyUndo,
15092
15183
  hookPriorityToPrecedence,
15093
15184
  indent,
15185
+ inlineLogicalText,
15094
15186
  inputRulesEngineFacet,
15095
15187
  inputRulesFacet,
15096
15188
  insertLineBreak,
@@ -15146,6 +15238,7 @@ function mapOffsetThroughSplices(splices, offset, assoc) {
15146
15238
  resolveSchema,
15147
15239
  resolveSchemaA11y,
15148
15240
  resolveSelectionTargetBlockIds,
15241
+ resolveSuggestionMenuTarget,
15149
15242
  runMigrations,
15150
15243
  searchControllerFacet,
15151
15244
  selectAdjacentInlineAtom,
package/dist/index.d.cts CHANGED
@@ -722,6 +722,71 @@ declare function resolveBlockDirection(editor: Editor, block: BlockHandle): Bloc
722
722
  */
723
723
  declare function blockLogicalText(editor: Editor, blockId: string): string;
724
724
 
725
+ /** Whether a trigger may sit after a non-whitespace character. */
726
+ type SuggestionMenuBoundary = "any" | "whitespace";
727
+ /**
728
+ * Match constraints for {@link resolveSuggestionMenuTarget}.
729
+ *
730
+ * Offsets are logical (N6): each inline atom is one unit.
731
+ */
732
+ interface SuggestionMenuTrigger {
733
+ /** Trigger string to find; empty `char` never matches. */
734
+ char: string;
735
+ /** Minimum query length after the trigger. @default 0 */
736
+ minQueryLength?: number;
737
+ /** Maximum query length after the trigger. @default unlimited */
738
+ maxQueryLength?: number;
739
+ /** How many logical offsets before the caret to search. @default 80 */
740
+ lookbehind?: number;
741
+ /** When false, a query containing whitespace is refused. @default false */
742
+ allowSpaces?: boolean;
743
+ /**
744
+ * `"whitespace"` requires start-of-prefix or a whitespace character before
745
+ * the trigger (an atom is not whitespace). `"any"` does not.
746
+ * @default "any"
747
+ */
748
+ boundary?: SuggestionMenuBoundary;
749
+ /** When set, a query containing this character is refused. @default none */
750
+ closingChar?: string;
751
+ /** When set, the query must match; `lastIndex` is reset first. @default none */
752
+ queryPattern?: RegExp;
753
+ }
754
+ /** Resolved trigger range in the logical offset domain (N6). */
755
+ interface SuggestionMenuTarget {
756
+ blockId: string;
757
+ startOffset: number;
758
+ endOffset: number;
759
+ query: string;
760
+ trigger: string;
761
+ }
762
+ /**
763
+ * Logical inline text of a block: stored string text plus one U+FFFC per
764
+ * inline atom, in the same offset domain as caret offsets and
765
+ * `block.length()` (N6).
766
+ *
767
+ * @param block - Live block handle to read.
768
+ * @returns The logical string. Empty blocks return `""`.
769
+ * @throws Never.
770
+ */
771
+ declare function inlineLogicalText(block: BlockHandle): string;
772
+ /**
773
+ * Resolves a collapsed caret to a suggestion-menu trigger range.
774
+ *
775
+ * Matching uses the logical offset domain (N6), not `block.textContent()`.
776
+ * Each inline atom occupies one offset (U+FFFC in {@link inlineLogicalText}).
777
+ * A query range that contains an atom is refused. A trigger immediately after
778
+ * an atom starts at the offset after that atom; `boundary: "whitespace"` still
779
+ * rejects when the preceding unit is the atom.
780
+ *
781
+ * @param editor - Editor whose collapsed text caret is read.
782
+ * @param trigger - Trigger character and match constraints. See
783
+ * {@link SuggestionMenuTrigger} for field defaults.
784
+ * @returns The trigger range in logical offsets, or `null` when the caret is
785
+ * not a collapsed in-block text selection or the prefix does not match.
786
+ * @throws Never. Non-matches return `null`.
787
+ */
788
+ declare function resolveSuggestionMenuTarget(editor: Editor, trigger: SuggestionMenuTrigger): SuggestionMenuTarget | null;
789
+
725
790
  type DefaultKeymapContext = "text" | "cell" | "block" | "any";
726
791
  type KeymapPlatform = "macos" | "windows" | "linux";
727
792
  interface DefaultKeymapBinding {
@@ -1048,4 +1113,4 @@ declare function affectedBlockIdsFromSummary(summary: Pick<ChangeSummary, "block
1048
1113
  */
1049
1114
  declare function mapOffsetThroughSplices(splices: readonly TextSplice[], offset: number, assoc: Assoc): number;
1050
1115
 
1051
- 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, isContainerBlock, isContainerBlockType, 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, shouldRenderContainerChildren, 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 };
1116
+ 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 SuggestionMenuBoundary, type SuggestionMenuTarget, type SuggestionMenuTrigger, 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, inlineLogicalText, inputRulesEngineFacet, inputRulesFacet, insertLineBreak, insertText, interpolateMessage, isBlockSelected, isCollapsed, isContainerBlock, isContainerBlockType, 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, resolveSuggestionMenuTarget, runMigrations, searchControllerFacet, selectAdjacentInlineAtom, selectAll, selectBlock, selectionToRange, setCellCaretFocus, setVerticalCaretGoalX, setVerticalCaretMeasure, shouldAllowDirectBlockPaste, shouldAllowFlowInsertionInSlashMenu, shouldExposeBlockInTooling, shouldForceBlockScopedSelectAll, shouldRenderContainerChildren, 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
@@ -722,6 +722,71 @@ declare function resolveBlockDirection(editor: Editor, block: BlockHandle): Bloc
722
722
  */
723
723
  declare function blockLogicalText(editor: Editor, blockId: string): string;
724
724
 
725
+ /** Whether a trigger may sit after a non-whitespace character. */
726
+ type SuggestionMenuBoundary = "any" | "whitespace";
727
+ /**
728
+ * Match constraints for {@link resolveSuggestionMenuTarget}.
729
+ *
730
+ * Offsets are logical (N6): each inline atom is one unit.
731
+ */
732
+ interface SuggestionMenuTrigger {
733
+ /** Trigger string to find; empty `char` never matches. */
734
+ char: string;
735
+ /** Minimum query length after the trigger. @default 0 */
736
+ minQueryLength?: number;
737
+ /** Maximum query length after the trigger. @default unlimited */
738
+ maxQueryLength?: number;
739
+ /** How many logical offsets before the caret to search. @default 80 */
740
+ lookbehind?: number;
741
+ /** When false, a query containing whitespace is refused. @default false */
742
+ allowSpaces?: boolean;
743
+ /**
744
+ * `"whitespace"` requires start-of-prefix or a whitespace character before
745
+ * the trigger (an atom is not whitespace). `"any"` does not.
746
+ * @default "any"
747
+ */
748
+ boundary?: SuggestionMenuBoundary;
749
+ /** When set, a query containing this character is refused. @default none */
750
+ closingChar?: string;
751
+ /** When set, the query must match; `lastIndex` is reset first. @default none */
752
+ queryPattern?: RegExp;
753
+ }
754
+ /** Resolved trigger range in the logical offset domain (N6). */
755
+ interface SuggestionMenuTarget {
756
+ blockId: string;
757
+ startOffset: number;
758
+ endOffset: number;
759
+ query: string;
760
+ trigger: string;
761
+ }
762
+ /**
763
+ * Logical inline text of a block: stored string text plus one U+FFFC per
764
+ * inline atom, in the same offset domain as caret offsets and
765
+ * `block.length()` (N6).
766
+ *
767
+ * @param block - Live block handle to read.
768
+ * @returns The logical string. Empty blocks return `""`.
769
+ * @throws Never.
770
+ */
771
+ declare function inlineLogicalText(block: BlockHandle): string;
772
+ /**
773
+ * Resolves a collapsed caret to a suggestion-menu trigger range.
774
+ *
775
+ * Matching uses the logical offset domain (N6), not `block.textContent()`.
776
+ * Each inline atom occupies one offset (U+FFFC in {@link inlineLogicalText}).
777
+ * A query range that contains an atom is refused. A trigger immediately after
778
+ * an atom starts at the offset after that atom; `boundary: "whitespace"` still
779
+ * rejects when the preceding unit is the atom.
780
+ *
781
+ * @param editor - Editor whose collapsed text caret is read.
782
+ * @param trigger - Trigger character and match constraints. See
783
+ * {@link SuggestionMenuTrigger} for field defaults.
784
+ * @returns The trigger range in logical offsets, or `null` when the caret is
785
+ * not a collapsed in-block text selection or the prefix does not match.
786
+ * @throws Never. Non-matches return `null`.
787
+ */
788
+ declare function resolveSuggestionMenuTarget(editor: Editor, trigger: SuggestionMenuTrigger): SuggestionMenuTarget | null;
789
+
725
790
  type DefaultKeymapContext = "text" | "cell" | "block" | "any";
726
791
  type KeymapPlatform = "macos" | "windows" | "linux";
727
792
  interface DefaultKeymapBinding {
@@ -1048,4 +1113,4 @@ declare function affectedBlockIdsFromSummary(summary: Pick<ChangeSummary, "block
1048
1113
  */
1049
1114
  declare function mapOffsetThroughSplices(splices: readonly TextSplice[], offset: number, assoc: Assoc): number;
1050
1115
 
1051
- 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, isContainerBlock, isContainerBlockType, 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, shouldRenderContainerChildren, 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 };
1116
+ 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 SuggestionMenuBoundary, type SuggestionMenuTarget, type SuggestionMenuTrigger, 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, inlineLogicalText, inputRulesEngineFacet, inputRulesFacet, insertLineBreak, insertText, interpolateMessage, isBlockSelected, isCollapsed, isContainerBlock, isContainerBlockType, 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, resolveSuggestionMenuTarget, runMigrations, searchControllerFacet, selectAdjacentInlineAtom, selectAll, selectBlock, selectionToRange, setCellCaretFocus, setVerticalCaretGoalX, setVerticalCaretMeasure, shouldAllowDirectBlockPaste, shouldAllowFlowInsertionInSlashMenu, shouldExposeBlockInTooling, shouldForceBlockScopedSelectAll, shouldRenderContainerChildren, 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
@@ -353,6 +353,47 @@ function filterOpsForDocumentProfile(ops, documentProfile, registry) {
353
353
  };
354
354
  }
355
355
 
356
+ // src/schema/generateValidator.ts
357
+ function generateValidator(propSchemas) {
358
+ return (raw) => {
359
+ const result = {};
360
+ for (const [key, schema] of Object.entries(propSchemas)) {
361
+ let value = raw[key];
362
+ if (value === void 0 || value === null) {
363
+ result[key] = schema.default;
364
+ continue;
365
+ }
366
+ const schemaType = Array.isArray(schema.type) ? schema.type[0] : schema.type;
367
+ if (schemaType === "number" && typeof value === "string") {
368
+ const parsed = Number(value);
369
+ if (!Number.isNaN(parsed)) value = parsed;
370
+ }
371
+ if (schemaType === "boolean" && typeof value === "string") {
372
+ value = value === "true";
373
+ }
374
+ const matchesSchemaType = schemaType === "array" ? Array.isArray(value) : typeof value === schemaType;
375
+ if (schema.type && schemaType !== void 0 && !matchesSchemaType) {
376
+ result[key] = schema.default;
377
+ continue;
378
+ }
379
+ if (typeof value === "number") {
380
+ if (schema.minimum !== void 0 && value < schema.minimum) {
381
+ value = schema.minimum;
382
+ }
383
+ if (typeof value === "number" && schema.maximum !== void 0 && value > schema.maximum) {
384
+ value = schema.maximum;
385
+ }
386
+ }
387
+ if (schema.enum && !schema.enum.includes(value)) {
388
+ result[key] = schema.default;
389
+ continue;
390
+ }
391
+ result[key] = value;
392
+ }
393
+ return result;
394
+ };
395
+ }
396
+
356
397
  // src/schema/prop.ts
357
398
  var PropChainImpl = class {
358
399
  _schema;
@@ -659,6 +700,10 @@ var SchemaRegistryImpl = class _SchemaRegistryImpl {
659
700
  if (patch.serialize) {
660
701
  merged.serialize = { ...existing.serialize, ...patch.serialize };
661
702
  }
703
+ if ("propSchema" in patch && !("validateProps" in patch)) {
704
+ const propSchema = merged.propSchema ?? {};
705
+ merged.validateProps = Object.keys(propSchema).length > 0 ? generateValidator(propSchema) : void 0;
706
+ }
662
707
  const blocks = new Map(this._blocks);
663
708
  blocks.set(type, merged);
664
709
  return new _SchemaRegistryImpl({
@@ -753,45 +798,6 @@ function generateAIDescription(type, props) {
753
798
  }).join(", ");
754
799
  return `${type}: ${propDescriptions}`;
755
800
  }
756
- function generateValidator(propSchemas) {
757
- return (raw) => {
758
- const result = {};
759
- for (const [key, schema] of Object.entries(propSchemas)) {
760
- let value = raw[key];
761
- if (value === void 0 || value === null) {
762
- result[key] = schema.default;
763
- continue;
764
- }
765
- const schemaType = Array.isArray(schema.type) ? schema.type[0] : schema.type;
766
- if (schemaType === "number" && typeof value === "string") {
767
- const parsed = Number(value);
768
- if (!Number.isNaN(parsed)) value = parsed;
769
- }
770
- if (schemaType === "boolean" && typeof value === "string") {
771
- value = value === "true";
772
- }
773
- const matchesSchemaType = schemaType === "array" ? Array.isArray(value) : typeof value === schemaType;
774
- if (schema.type && schemaType !== void 0 && !matchesSchemaType) {
775
- result[key] = schema.default;
776
- continue;
777
- }
778
- if (typeof value === "number") {
779
- if (schema.minimum !== void 0 && value < schema.minimum) {
780
- value = schema.minimum;
781
- }
782
- if (typeof value === "number" && schema.maximum !== void 0 && value > schema.maximum) {
783
- value = schema.maximum;
784
- }
785
- }
786
- if (schema.enum && !schema.enum.includes(value)) {
787
- result[key] = schema.default;
788
- continue;
789
- }
790
- result[key] = value;
791
- }
792
- return result;
793
- };
794
- }
795
801
  function defineBlock(typeOrConfig, maybeConfig) {
796
802
  const type = typeof typeOrConfig === "string" ? typeOrConfig : typeOrConfig.type;
797
803
  const config = typeof typeOrConfig === "string" ? maybeConfig : typeOrConfig;
@@ -3422,6 +3428,18 @@ function snapshotPlain(value) {
3422
3428
  writable: true
3423
3429
  });
3424
3430
  }
3431
+ for (const key of Object.getOwnPropertySymbols(value)) {
3432
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
3433
+ if (!descriptor?.enumerable) {
3434
+ continue;
3435
+ }
3436
+ Object.defineProperty(next, key, {
3437
+ value: snapshotPlain(descriptor.value),
3438
+ enumerable: true,
3439
+ configurable: true,
3440
+ writable: true
3441
+ });
3442
+ }
3425
3443
  return next;
3426
3444
  }
3427
3445
  function snapshotOps(ops) {
@@ -14288,6 +14306,77 @@ function emitMissingBlock(editor, blockId) {
14288
14306
  emit("diagnostic", event);
14289
14307
  }
14290
14308
 
14309
+ // src/suggestion/resolveSuggestionMenuTarget.ts
14310
+ var DEFAULT_LOOKBEHIND = 80;
14311
+ function inlineLogicalText(block) {
14312
+ return logicalInline(block).text;
14313
+ }
14314
+ function resolveSuggestionMenuTarget(editor, trigger) {
14315
+ if (trigger.char.length === 0) {
14316
+ return null;
14317
+ }
14318
+ const selection = editor.selection;
14319
+ if (selection?.type !== "text" || !isCollapsed(selection)) {
14320
+ return null;
14321
+ }
14322
+ if (selection.anchor.blockId !== selection.focus.blockId) {
14323
+ return null;
14324
+ }
14325
+ const block = editor.getBlock(selection.focus.blockId);
14326
+ if (!block) {
14327
+ return null;
14328
+ }
14329
+ const offset = selection.focus.offset;
14330
+ const lookbehind = trigger.lookbehind ?? DEFAULT_LOOKBEHIND;
14331
+ const prefixStartOffset = Math.max(0, offset - lookbehind);
14332
+ const { text, atoms } = logicalInline(block);
14333
+ const textBefore = text.slice(prefixStartOffset, offset);
14334
+ const triggerIndex = textBefore.lastIndexOf(trigger.char);
14335
+ if (triggerIndex < 0) {
14336
+ return null;
14337
+ }
14338
+ if (trigger.boundary === "whitespace") {
14339
+ const previousChar = textBefore[triggerIndex - 1];
14340
+ if (previousChar && !/\s/.test(previousChar)) {
14341
+ return null;
14342
+ }
14343
+ }
14344
+ const query = textBefore.slice(triggerIndex + trigger.char.length);
14345
+ const startOffset = prefixStartOffset + triggerIndex;
14346
+ const queryStartOffset = startOffset + trigger.char.length;
14347
+ if (queryRangeContainsAtom(atoms, queryStartOffset, offset)) {
14348
+ return null;
14349
+ }
14350
+ if (!trigger.allowSpaces && /\s/.test(query)) {
14351
+ return null;
14352
+ }
14353
+ if (trigger.closingChar && query.includes(trigger.closingChar)) {
14354
+ return null;
14355
+ }
14356
+ if (query.length < (trigger.minQueryLength ?? 0)) {
14357
+ return null;
14358
+ }
14359
+ if (trigger.maxQueryLength !== void 0 && query.length > trigger.maxQueryLength) {
14360
+ return null;
14361
+ }
14362
+ if (trigger.queryPattern) {
14363
+ trigger.queryPattern.lastIndex = 0;
14364
+ if (!trigger.queryPattern.test(query)) {
14365
+ return null;
14366
+ }
14367
+ }
14368
+ return {
14369
+ blockId: selection.focus.blockId,
14370
+ startOffset,
14371
+ endOffset: offset,
14372
+ query,
14373
+ trigger: trigger.char
14374
+ };
14375
+ }
14376
+ function queryRangeContainsAtom(atoms, queryStart, queryEnd) {
14377
+ return atoms.some((atom) => atom.start < queryEnd && atom.end > queryStart);
14378
+ }
14379
+
14291
14380
  // src/commands/resolveDirectedBinding.ts
14292
14381
  function resolveDirectedBinding(editor, binding) {
14293
14382
  const direction = resolveFocusBlockDirection(editor);
@@ -14913,6 +15002,7 @@ export {
14913
15002
  historyUndo,
14914
15003
  hookPriorityToPrecedence,
14915
15004
  indent,
15005
+ inlineLogicalText,
14916
15006
  inputRulesEngineFacet,
14917
15007
  inputRulesFacet,
14918
15008
  insertLineBreak,
@@ -14968,6 +15058,7 @@ export {
14968
15058
  resolveSchema,
14969
15059
  resolveSchemaA11y,
14970
15060
  resolveSelectionTargetBlockIds,
15061
+ resolveSuggestionMenuTarget,
14971
15062
  runMigrations,
14972
15063
  searchControllerFacet,
14973
15064
  selectAdjacentInlineAtom,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@input/pen-core",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
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.6",
47
- "@input/pen-types": "^0.1.6"
46
+ "@input/pen-yjs": "^0.1.8",
47
+ "@input/pen-types": "^0.1.8"
48
48
  },
49
49
  "devDependencies": {
50
50
  "tsup": "^8.4.0",