@refraction-ui/react 0.19.1 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-533MCMSU.js +2355 -0
- package/dist/chunk-533MCMSU.js.map +7 -0
- package/dist/chunk-AMK4VCBG.js +250 -0
- package/dist/chunk-AMK4VCBG.js.map +7 -0
- package/dist/chunk-BOLFYVI6.js +90 -0
- package/dist/chunk-BOLFYVI6.js.map +7 -0
- package/dist/chunk-F7B64KT3.js +187 -0
- package/dist/chunk-F7B64KT3.js.map +7 -0
- package/dist/chunk-FD37DFCC.js +77 -0
- package/dist/chunk-FD37DFCC.js.map +7 -0
- package/dist/chunk-H54FLNAF.js +132 -0
- package/dist/chunk-H54FLNAF.js.map +7 -0
- package/dist/chunk-INP46354.js +664 -0
- package/dist/chunk-INP46354.js.map +7 -0
- package/dist/chunk-ONACRMG4.js +116 -0
- package/dist/chunk-ONACRMG4.js.map +7 -0
- package/dist/chunk-SDZ3LROZ.js +7 -0
- package/dist/chunk-SDZ3LROZ.js.map +7 -0
- package/dist/chunk-TOZBR4GL.js +154 -0
- package/dist/chunk-TOZBR4GL.js.map +7 -0
- package/dist/chunk-UNEM2TRI.js +225 -0
- package/dist/chunk-UNEM2TRI.js.map +7 -0
- package/dist/chunk-VK3EQ34F.js +92 -0
- package/dist/chunk-VK3EQ34F.js.map +7 -0
- package/dist/chunk-WK2IIUFO.js +316 -0
- package/dist/chunk-WK2IIUFO.js.map +7 -0
- package/dist/chunk-WSPJTQAV.js +59 -0
- package/dist/chunk-WSPJTQAV.js.map +7 -0
- package/dist/chunk-XWP763SH.js +3 -2
- package/dist/chunk-XWP763SH.js.map +7 -1
- package/dist/chunk-Y2FNNAIL.js +80 -0
- package/dist/chunk-Y2FNNAIL.js.map +1 -0
- package/dist/{chunk-4A4BW3GR.js → chunk-ZF3OHCGV.js} +28 -5
- package/dist/chunk-ZF3OHCGV.js.map +7 -0
- package/dist/form.cjs +5 -11
- package/dist/form.cjs.map +1 -1
- package/dist/form.js +6 -12
- package/dist/form.js.map +1 -1
- package/dist/index.cjs +2879 -33936
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +126 -28780
- package/dist/internal/react-app-shell/index.d.cts +2 -22
- package/dist/internal/react-app-shell/index.d.ts +2 -22
- package/dist/internal/react-badge/index.d.cts +5 -0
- package/dist/internal/react-badge/index.d.ts +5 -0
- package/dist/internal/react-callout/index.d.cts +4 -0
- package/dist/internal/react-callout/index.d.ts +4 -0
- package/dist/internal/react-card/index.d.cts +8 -1
- package/dist/internal/react-card/index.d.ts +8 -1
- package/dist/internal/react-collapsible/index.d.cts +0 -3
- package/dist/internal/react-collapsible/index.d.ts +0 -3
- package/dist/internal/react-combobox/index.d.cts +0 -3
- package/dist/internal/react-combobox/index.d.ts +0 -3
- package/dist/internal/react-command/index.d.cts +0 -3
- package/dist/internal/react-command/index.d.ts +0 -3
- package/dist/internal/react-data-table/index.d.cts +0 -3
- package/dist/internal/react-data-table/index.d.ts +0 -3
- package/dist/internal/react-dialog/index.d.cts +0 -3
- package/dist/internal/react-dialog/index.d.ts +0 -3
- package/dist/internal/react-dropdown-menu/index.d.cts +0 -3
- package/dist/internal/react-dropdown-menu/index.d.ts +0 -3
- package/dist/internal/react-feedback-dialog/index.d.cts +0 -3
- package/dist/internal/react-feedback-dialog/index.d.ts +0 -3
- package/dist/internal/react-form/index.d.cts +2 -21
- package/dist/internal/react-form/index.d.ts +2 -21
- package/dist/internal/react-keyboard-shortcut/index.d.cts +0 -6
- package/dist/internal/react-keyboard-shortcut/index.d.ts +0 -6
- package/dist/internal/react-link-card/index.d.cts +8 -2
- package/dist/internal/react-link-card/index.d.ts +8 -2
- package/dist/internal/react-popover/index.d.cts +0 -3
- package/dist/internal/react-popover/index.d.ts +0 -3
- package/dist/internal/react-sheet/index.d.cts +0 -3
- package/dist/internal/react-sheet/index.d.ts +0 -3
- package/dist/internal/react-slot/index.d.cts +23 -0
- package/dist/internal/react-slot/index.d.ts +23 -0
- package/dist/internal/react-toast/index.d.cts +0 -3
- package/dist/internal/react-toast/index.d.ts +0 -3
- package/dist/internal/react-tooltip/index.d.cts +0 -3
- package/dist/internal/react-tooltip/index.d.ts +0 -3
- package/dist/react-accordion.js +125 -0
- package/dist/react-accordion.js.map +7 -0
- package/dist/react-ai.js +257 -0
- package/dist/react-ai.js.map +7 -0
- package/dist/react-analytics.js +1698 -0
- package/dist/react-analytics.js.map +7 -0
- package/dist/react-animated-text.js +164 -0
- package/dist/react-animated-text.js.map +7 -0
- package/dist/react-app-shell.js +631 -0
- package/dist/react-app-shell.js.map +7 -0
- package/dist/react-audience-feature-card.js +53 -0
- package/dist/react-audience-feature-card.js.map +7 -0
- package/dist/react-audio-room.js +166 -0
- package/dist/react-audio-room.js.map +7 -0
- package/dist/react-auth.js +211 -0
- package/dist/react-auth.js.map +7 -0
- package/dist/react-avatar-group.js +154 -0
- package/dist/react-avatar-group.js.map +7 -0
- package/dist/react-avatar.js +168 -0
- package/dist/react-avatar.js.map +7 -0
- package/dist/react-badge.js +86 -0
- package/dist/react-badge.js.map +7 -0
- package/dist/react-bottom-nav.js +67 -0
- package/dist/react-bottom-nav.js.map +7 -0
- package/dist/react-brand-network-cell.js +92 -0
- package/dist/react-brand-network-cell.js.map +7 -0
- package/dist/react-breadcrumbs.js +104 -0
- package/dist/react-breadcrumbs.js.map +7 -0
- package/dist/react-browser-chrome-mock.js +89 -0
- package/dist/react-browser-chrome-mock.js.map +7 -0
- package/dist/react-button.js +13 -0
- package/dist/react-button.js.map +7 -0
- package/dist/react-calendar.js +301 -0
- package/dist/react-calendar.js.map +7 -0
- package/dist/react-call-controls.js +113 -0
- package/dist/react-call-controls.js.map +7 -0
- package/dist/react-callout.js +152 -0
- package/dist/react-callout.js.map +7 -0
- package/dist/react-card-grid.js +34 -0
- package/dist/react-card-grid.js.map +7 -0
- package/dist/react-card.js +200 -0
- package/dist/react-card.js.map +7 -0
- package/dist/react-carousel.js +132 -0
- package/dist/react-carousel.js.map +7 -0
- package/dist/react-charts.js +509 -0
- package/dist/react-charts.js.map +7 -0
- package/dist/react-checkbox.js +156 -0
- package/dist/react-checkbox.js.map +7 -0
- package/dist/react-checklist.js +168 -0
- package/dist/react-checklist.js.map +7 -0
- package/dist/react-code-block.js +78 -0
- package/dist/react-code-block.js.map +7 -0
- package/dist/react-code-editor.js +184 -0
- package/dist/react-code-editor.js.map +7 -0
- package/dist/react-collapsible.js +192 -0
- package/dist/react-collapsible.js.map +7 -0
- package/dist/react-combobox.js +609 -0
- package/dist/react-combobox.js.map +7 -0
- package/dist/react-command-input.js +202 -0
- package/dist/react-command-input.js.map +7 -0
- package/dist/react-command.js +364 -0
- package/dist/react-command.js.map +7 -0
- package/dist/react-composer.js +2036 -0
- package/dist/react-composer.js.map +7 -0
- package/dist/react-content-protection.js +133 -0
- package/dist/react-content-protection.js.map +7 -0
- package/dist/react-conversation.js +1226 -0
- package/dist/react-conversation.js.map +7 -0
- package/dist/react-cookie-consent.js +291 -0
- package/dist/react-cookie-consent.js.map +7 -0
- package/dist/react-data-table.js +310 -0
- package/dist/react-data-table.js.map +7 -0
- package/dist/react-date-picker.js +95 -0
- package/dist/react-date-picker.js.map +7 -0
- package/dist/react-device-frame.js +137 -0
- package/dist/react-device-frame.js.map +7 -0
- package/dist/react-dialog.js +218 -0
- package/dist/react-dialog.js.map +7 -0
- package/dist/react-diff-viewer.js +460 -0
- package/dist/react-diff-viewer.js.map +7 -0
- package/dist/react-dropdown-menu.js +292 -0
- package/dist/react-dropdown-menu.js.map +7 -0
- package/dist/react-editor-status-bar.js +148 -0
- package/dist/react-editor-status-bar.js.map +7 -0
- package/dist/react-editor-tabs.js +159 -0
- package/dist/react-editor-tabs.js.map +7 -0
- package/dist/react-emoji-picker.js +238 -0
- package/dist/react-emoji-picker.js.map +7 -0
- package/dist/react-empty-state.js +90 -0
- package/dist/react-empty-state.js.map +7 -0
- package/dist/react-feedback-dialog.js +275 -0
- package/dist/react-feedback-dialog.js.map +7 -0
- package/dist/react-file-tree.js +376 -0
- package/dist/react-file-tree.js.map +7 -0
- package/dist/react-file-upload.js +360 -0
- package/dist/react-file-upload.js.map +7 -0
- package/dist/react-floating-reactions.js +124 -0
- package/dist/react-floating-reactions.js.map +7 -0
- package/dist/react-flow-editor.js +198 -0
- package/dist/react-flow-editor.js.map +7 -0
- package/dist/react-footer.js +67 -0
- package/dist/react-footer.js.map +7 -0
- package/dist/react-graph-view.js +197 -0
- package/dist/react-graph-view.js.map +7 -0
- package/dist/react-icon-system.js +9 -0
- package/dist/react-icon-system.js.map +7 -0
- package/dist/react-infinite-canvas.js +8 -0
- package/dist/react-infinite-canvas.js.map +7 -0
- package/dist/react-inline-editor.js +216 -0
- package/dist/react-inline-editor.js.map +7 -0
- package/dist/react-input-group.js +150 -0
- package/dist/react-input-group.js.map +7 -0
- package/dist/react-input.js +10 -0
- package/dist/react-input.js.map +7 -0
- package/dist/react-install-prompt.js +132 -0
- package/dist/react-install-prompt.js.map +7 -0
- package/dist/react-kanban-board.js +198 -0
- package/dist/react-kanban-board.js.map +7 -0
- package/dist/react-keyboard-shortcut.js +36 -0
- package/dist/react-keyboard-shortcut.js.map +7 -0
- package/dist/react-language-selector.js +311 -0
- package/dist/react-language-selector.js.map +7 -0
- package/dist/react-link-card.js +48 -0
- package/dist/react-link-card.js.map +7 -0
- package/dist/react-live-captions.js +92 -0
- package/dist/react-live-captions.js.map +7 -0
- package/dist/react-live-cursors.js +145 -0
- package/dist/react-live-cursors.js.map +7 -0
- package/dist/react-live-transcript.js +117 -0
- package/dist/react-live-transcript.js.map +7 -0
- package/dist/react-location-selector.js +252 -0
- package/dist/react-location-selector.js.map +7 -0
- package/dist/react-logger.js +415 -0
- package/dist/react-logger.js.map +7 -0
- package/dist/react-markdown-renderer.js +12 -0
- package/dist/react-markdown-renderer.js.map +7 -0
- package/dist/react-marquee-strip.js +62 -0
- package/dist/react-marquee-strip.js.map +7 -0
- package/dist/react-mascot.js +367 -0
- package/dist/react-mascot.js.map +7 -0
- package/dist/react-mastery-bar.js +97 -0
- package/dist/react-mastery-bar.js.map +7 -0
- package/dist/react-mini-map.js +207 -0
- package/dist/react-mini-map.js.map +7 -0
- package/dist/react-mobile-nav.js +216 -0
- package/dist/react-mobile-nav.js.map +7 -0
- package/dist/react-navbar.js +96 -0
- package/dist/react-navbar.js.map +7 -0
- package/dist/react-numbered-steps.js +67 -0
- package/dist/react-numbered-steps.js.map +7 -0
- package/dist/react-otp-input.js +318 -0
- package/dist/react-otp-input.js.map +7 -0
- package/dist/react-pagination.js +199 -0
- package/dist/react-pagination.js.map +7 -0
- package/dist/react-password-input.js +104 -0
- package/dist/react-password-input.js.map +7 -0
- package/dist/react-payment.js +81 -0
- package/dist/react-payment.js.map +7 -0
- package/dist/react-popover.js +176 -0
- package/dist/react-popover.js.map +7 -0
- package/dist/react-pre-call-lobby.js +309 -0
- package/dist/react-pre-call-lobby.js.map +7 -0
- package/dist/react-presence-indicator.js +86 -0
- package/dist/react-presence-indicator.js.map +7 -0
- package/dist/react-pricing-card.js +142 -0
- package/dist/react-pricing-card.js.map +7 -0
- package/dist/react-progress-display.js +188 -0
- package/dist/react-progress-display.js.map +7 -0
- package/dist/react-radial-gauge.js +193 -0
- package/dist/react-radial-gauge.js.map +7 -0
- package/dist/react-radio.js +169 -0
- package/dist/react-radio.js.map +7 -0
- package/dist/react-rating-scale.js +171 -0
- package/dist/react-rating-scale.js.map +7 -0
- package/dist/react-reaction-bar.js +111 -0
- package/dist/react-reaction-bar.js.map +7 -0
- package/dist/react-resizable-layout.js +298 -0
- package/dist/react-resizable-layout.js.map +7 -0
- package/dist/react-rich-editor.js +1 -0
- package/dist/react-rich-editor.js.map +7 -0
- package/dist/react-search-bar.js +285 -0
- package/dist/react-search-bar.js.map +7 -0
- package/dist/react-section-head.js +65 -0
- package/dist/react-section-head.js.map +7 -0
- package/dist/react-segmented-control.js +215 -0
- package/dist/react-segmented-control.js.map +7 -0
- package/dist/react-select.js +389 -0
- package/dist/react-select.js.map +7 -0
- package/dist/react-separator.js +86 -0
- package/dist/react-separator.js.map +7 -0
- package/dist/react-sheet.js +310 -0
- package/dist/react-sheet.js.map +7 -0
- package/dist/react-sidebar.js +100 -0
- package/dist/react-sidebar.js.map +7 -0
- package/dist/react-skeleton.js +91 -0
- package/dist/react-skeleton.js.map +7 -0
- package/dist/react-skip-to-content.js +38 -0
- package/dist/react-skip-to-content.js.map +7 -0
- package/dist/react-slide-viewer.js +264 -0
- package/dist/react-slide-viewer.js.map +7 -0
- package/dist/react-slider.js +105 -0
- package/dist/react-slider.js.map +7 -0
- package/dist/react-slot-picker.js +184 -0
- package/dist/react-slot-picker.js.map +7 -0
- package/dist/react-slot.js +7 -0
- package/dist/react-slot.js.map +7 -0
- package/dist/react-social-auth-button.js +169 -0
- package/dist/react-social-auth-button.js.map +7 -0
- package/dist/react-sortable-list.js +204 -0
- package/dist/react-sortable-list.js.map +7 -0
- package/dist/react-stat-grid.js +69 -0
- package/dist/react-stat-grid.js.map +7 -0
- package/dist/react-status-indicator.js +117 -0
- package/dist/react-status-indicator.js.map +7 -0
- package/dist/react-steps.js +167 -0
- package/dist/react-steps.js.map +7 -0
- package/dist/react-sticky-note.js +131 -0
- package/dist/react-sticky-note.js.map +7 -0
- package/dist/react-switch.js +139 -0
- package/dist/react-switch.js.map +7 -0
- package/dist/react-table-of-contents.js +80 -0
- package/dist/react-table-of-contents.js.map +7 -0
- package/dist/react-tabs.js +230 -0
- package/dist/react-tabs.js.map +7 -0
- package/dist/react-terminal.js +101 -0
- package/dist/react-terminal.js.map +7 -0
- package/dist/react-test-results.js +158 -0
- package/dist/react-test-results.js.map +7 -0
- package/dist/react-textarea.js +89 -0
- package/dist/react-textarea.js.map +7 -0
- package/dist/react-thread-view.js +139 -0
- package/dist/react-thread-view.js.map +7 -0
- package/dist/react-timeline.js +153 -0
- package/dist/react-timeline.js.map +7 -0
- package/dist/react-toast.js +253 -0
- package/dist/react-toast.js.map +7 -0
- package/dist/react-tooltip.js +221 -0
- package/dist/react-tooltip.js.map +7 -0
- package/dist/react-version-selector.js +270 -0
- package/dist/react-version-selector.js.map +7 -0
- package/dist/react-video-grid.js +129 -0
- package/dist/react-video-grid.js.map +7 -0
- package/dist/react-video-player.js +206 -0
- package/dist/react-video-player.js.map +7 -0
- package/dist/react-video-tile.js +8 -0
- package/dist/react-video-tile.js.map +7 -0
- package/dist/react-voice-pill.js +314 -0
- package/dist/react-voice-pill.js.map +7 -0
- package/dist/react-waveform.js +558 -0
- package/dist/react-waveform.js.map +7 -0
- package/dist/react-wizard.js +337 -0
- package/dist/react-wizard.js.map +7 -0
- package/dist/styles.css +7 -0
- package/dist/theme.js +1 -1
- package/package.json +3 -2
- package/dist/applicationinsights-web-H7OBLIVJ.js +0 -23393
- package/dist/applicationinsights-web-H7OBLIVJ.js.map +0 -1
- package/dist/chunk-4A4BW3GR.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/module-X3HTTRQK.js +0 -6438
- package/dist/module-X3HTTRQK.js.map +0 -1
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../composer/src/types.ts", "../../composer/src/graphemes.ts", "../../composer/src/rules.ts", "../../composer/src/trigger-engine.ts", "../../composer/src/suggestion.ts", "../../composer/src/tokens.ts", "../../composer/src/composer.ts", "../../composer/src/styles.ts", "../../composer/src/emoji-trigger.ts", "../../react-composer/src/use-composer.ts", "../../react-composer/src/composer.tsx"],
|
|
4
|
+
"sourcesContent": ["/**\n * Public types for the headless chat composer core.\n *\n * The composer models a single growing message buffer (flat string + selection)\n * with inline triggers (@mention, /command, :emoji:, #tag, custom), committed\n * atomic tokens, staged attachments, and one optimistic submit. Framework\n * adapters (React/Astro/Flutter) wrap this contract without re-implementing\n * any behavior.\n */\n\nimport type { MessageAttachment } from '@refraction-ui/conversation'\n\n// ---------------------------------------------------------------------------\n// Triggers & suggestions\n// ---------------------------------------------------------------------------\n\n/** Where a trigger symbol is allowed to arm. */\nexport type ComposerTriggerScope = 'anywhere' | 'startOfLine' | 'startOfMessage'\n\n/** A candidate shown in the suggestion menu for an armed trigger. */\nexport interface ComposerCandidate {\n id: string\n display: string\n subtitle?: string\n metadata?: Record<string, unknown>\n}\n\nexport type ComposerTriggerResolver = (\n query: string,\n) => ComposerCandidate[] | Promise<ComposerCandidate[]>\n\n/**\n * A trigger is configuration, not code — the engine is symbol-agnostic and\n * never special-cases '@' / '/' / ':' in control flow.\n */\nexport interface ComposerTrigger {\n id: string\n /** Trigger symbol; any length ('@', '/', '#', '!!'). */\n symbol: string\n /** Default 'anywhere'. Slash-style commands use 'startOfMessage'. */\n scope?: ComposerTriggerScope\n /** Extra validation for the query; a violating query closes the trigger. */\n queryPattern?: RegExp\n /** Queries longer than this silently cancel the trigger. Default 40. */\n maxQueryLength?: number\n /** Default true. '#'-style triggers set false so '#weekend trip' stays armed. */\n closeOnSpace?: boolean\n /** Default false. Trades away email/URL protection — must be justified. */\n allowMidWord?: boolean\n /** Additional boundary characters allowed before the symbol (e.g. '(' or '\"'). */\n extraBoundaryChars?: string[]\n /** Resolver debounce in ms. Default 0 (sync/local resolvers). */\n debounceMs?: number\n /** Visible slice size for the adapter's menu. Default 6. */\n maxVisibleResults?: number\n /** Whether ArrowUp/Down wrap around the ends. Default true. */\n wrapNavigation?: boolean\n /**\n * Builds the committed token's display text. Defaults to `symbol + display`\n * ('@Jordan Lee'); the emoji recipe returns the unicode itself.\n */\n toDisplay?: (candidate: ComposerCandidate) => string\n resolve: ComposerTriggerResolver\n}\n\n/** A committed, atomic inline unit (identity + frozen display). */\nexport interface ComposerToken {\n triggerId: string\n symbol: string\n id: string\n label: string\n display: string\n metadata?: Record<string, unknown>\n}\n\n/** A token placed in the value, with its live UTF-16 range. */\nexport interface PlacedToken extends ComposerToken {\n start: number\n end: number\n}\n\n/** Structured output token with derived UTF-16 offsets into plainText. */\nexport interface ResolvedToken {\n type: string\n id: string\n display: string\n start: number\n end: number\n}\n\n// ---------------------------------------------------------------------------\n// Attachments\n// ---------------------------------------------------------------------------\n\nexport type ComposerAttachmentKind = 'text' | 'image' | 'video' | 'audio' | 'file'\nexport type ComposerAttachmentStatus = 'pending' | 'uploading' | 'ready' | 'error'\n\nexport interface ComposerAttachment {\n id: string\n kind: ComposerAttachmentKind\n name: string\n mimeType?: string\n sizeBytes?: number\n previewUrl?: string\n status: ComposerAttachmentStatus\n /** 0..1 while uploading. */\n progress?: number\n errorMessage?: string\n metadata?: Record<string, unknown>\n}\n\n/** Input to `addAttachment` — id/status are core-owned defaults. */\nexport type ComposerAttachmentDraft = Omit<ComposerAttachment, 'id' | 'status'> & {\n id?: string\n status?: ComposerAttachmentStatus\n}\n\n// ---------------------------------------------------------------------------\n// Submission & serialization\n// ---------------------------------------------------------------------------\n\nexport interface ComposerSubmission {\n /** Trimmed message text with token displays inlined. */\n plainText: string\n tokens: ResolvedToken[]\n attachments: ComposerAttachment[]\n replyToMessageId?: string\n /** Present when submitting from edit mode. */\n editingMessageId?: string\n}\n\nexport interface ComposerOutput {\n plainText: string\n tokens: ResolvedToken[]\n}\n\n// ---------------------------------------------------------------------------\n// State\n// ---------------------------------------------------------------------------\n\nexport interface ComposerSelection {\n start: number\n end: number\n}\n\nexport interface ActiveTriggerState {\n triggerId: string\n symbol: string\n symbolStart: number\n caret: number\n query: string\n}\n\nexport interface SuggestionState {\n isOpen: boolean\n /** Full result list (overflow intact). */\n items: ComposerCandidate[]\n /** `items` sliced to the trigger's maxVisibleResults for the adapter. */\n visibleItems: ComposerCandidate[]\n activeIndex: number\n loading: boolean\n error: string | null\n /** Monotonic id of the latest resolve request (staleness guard). */\n requestToken: number\n}\n\nexport interface CounterState {\n /** Visible once remaining budget is within 20% of maxLength. */\n visible: boolean\n /** Remaining grapheme budget; null without a maxLength. */\n remaining: number | null\n overLimit: boolean\n}\n\nexport type ComposerMode = 'compose' | 'edit'\n\nexport interface ComposerState {\n value: string\n selection: ComposerSelection\n isComposing: boolean\n isBusy: boolean\n disabled: boolean\n readOnly: boolean\n isEmpty: boolean\n canSend: boolean\n error: string | null\n attachments: ComposerAttachment[]\n tokens: PlacedToken[]\n activeTrigger: ActiveTriggerState | null\n suggestion: SuggestionState\n counter: CounterState\n mode: ComposerMode\n editingMessageId?: string\n}\n\n// ---------------------------------------------------------------------------\n// Validation, drafts, events, config\n// ---------------------------------------------------------------------------\n\nexport interface ComposerValidationResult {\n isValid: boolean\n reason?: string\n}\n\nexport type ComposerValidator = (\n plainText: string,\n tokens: ResolvedToken[],\n) => ComposerValidationResult\n\n/** Persisted draft snapshot (attachments by id only — blobs are host-owned). */\nexport interface ComposerDraft {\n value: string\n tokens: PlacedToken[]\n attachmentIds: string[]\n updatedAt: number\n}\n\n/** Injected persistence seam; the core ships only an in-memory default. */\nexport interface ComposerDraftStore {\n read(key: string): ComposerDraft | null\n write(key: string, draft: ComposerDraft): void\n clear(key: string): void\n}\n\nexport type ComposerEvent =\n | { type: 'paste-trimmed' }\n | { type: 'insert-rejected'; reason: 'max-length' | 'composing' | 'disabled' | 'read-only' }\n | { type: 'edit-rejected'; reason: 'inside-token' }\n | { type: 'attachment-rejected'; reason: 'max-attachments' | 'max-size' | 'not-accepted'; name: string; detail?: string }\n | { type: 'typing' }\n\nexport interface ComposerConfig {\n initialValue?: string\n /** Tokens present in initialValue (e.g. when re-opening a draft the host owns). */\n initialTokens?: PlacedToken[]\n /** Grapheme-cluster budget (not UTF-16 units). */\n maxLength?: number\n maxAttachments?: number\n /** Per-attachment size gate; larger drafts are rejected with an event. */\n maxAttachmentSizeBytes?: number\n /** Predicate gate; return false or a reason string to reject a draft. */\n acceptAttachment?: (draft: ComposerAttachmentDraft) => boolean | string\n minLines?: number\n maxLines?: number\n triggers?: ComposerTrigger[]\n validator?: ComposerValidator\n draftStore?: ComposerDraftStore\n draftKey?: string\n /** Draft autosave debounce. Default 400ms. */\n draftDebounceMs?: number\n /** Leading-edge throttle for the 'typing' event. Default 3000ms. */\n typingSignalIntervalMs?: number\n replyToMessageId?: string\n /** Injected clock — the core never calls Date.now() in logic paths. */\n now?: () => number\n /** Injected id factory — the core never calls Math.random(). */\n generateId?: (prefix?: string) => string\n /** Notice channel ('paste-trimmed', 'attachment-rejected', 'typing', …). */\n onEvent?: (event: ComposerEvent) => void\n}\n\n// ---------------------------------------------------------------------------\n// API\n// ---------------------------------------------------------------------------\n\n/** Outcome of a physical Enter press routed through the core. */\nexport type EnterResult = 'submitted' | 'newline' | 'committed-suggestion' | 'noop'\n\nexport interface ComposerAPI {\n getState(): ComposerState\n subscribe(listener: (state: ComposerState) => void): () => void\n\n /**\n * The adapter's input path: full new text + selection after a user edit.\n * Pass `{ programmatic: true }` for host-driven writes (suppresses the\n * typing signal).\n */\n setValue(text: string, selection?: ComposerSelection, opts?: { programmatic?: boolean }): void\n /** Caret/selection move without a text change (allowed while readOnly). */\n setSelection(selection: ComposerSelection): void\n insertTextAtCursor(text: string): void\n setComposing(isComposing: boolean): void\n\n /** Routes a physical Enter; the return value tells the adapter whether to preventDefault. */\n applyEnter(opts: { shiftPressed: boolean }): EnterResult\n\n moveSuggestionNext(): void\n moveSuggestionPrevious(): void\n /** Pointer hover — last-input-wins with the keyboard. */\n setSuggestionActiveIndex(index: number): void\n applySuggestion(index?: number): void\n /** Escape-style dismissal: closes now and marks the occurrence dismissed. */\n dismissSuggestion(): void\n /** Blur-style dismissal: closes after a short grace so a click can land. */\n dismissSuggestionDeferred(): void\n retrySuggestions(): void\n\n addAttachment(draft: ComposerAttachmentDraft): string | null\n updateAttachment(id: string, patch: Partial<Omit<ComposerAttachment, 'id'>>): void\n removeAttachment(id: string): void\n\n setBusy(busy: boolean): void\n setError(error: string | null): void\n setDisabled(disabled: boolean): void\n setReadOnly(readOnly: boolean): void\n\n beginEdit(args: { value: string; tokens?: PlacedToken[]; messageId: string }): void\n cancelEdit(): void\n\n /** Clipboard seam: returns the selected display text. */\n copySelection(): string\n /** Clipboard seam: removes the selection, retaining token identity for same-instance paste. */\n cutSelection(): string\n /** Clipboard seam: plain-text insert at caret (clamped); restores tokens only for a same-instance cut. */\n pasteText(text: string): void\n\n submit(): ComposerSubmission | null\n reset(): void\n serialize(): ComposerOutput\n destroy(): void\n}\n\n/**\n * Bridge to the conversation layer: a ready composer attachment mapped onto\n * `@refraction-ui/conversation`'s wire shape.\n */\nexport function toMessageAttachment(attachment: ComposerAttachment): MessageAttachment {\n return {\n id: attachment.id,\n name: attachment.name,\n url: attachment.previewUrl ?? '',\n type: attachment.mimeType ?? attachment.kind,\n size: attachment.sizeBytes,\n }\n}\n\nexport type { MessageAttachment }\n", "/**\n * Grapheme-cluster utilities.\n *\n * Length limits and clamping must never bisect a user-perceived character —\n * a 👨👩👧👦 ZWJ family emoji is 11 UTF-16 units but one cluster. We use\n * `Intl.Segmenter` when available; the fallback treats each code point as a\n * cluster (coarser, but it never splits a surrogate pair).\n */\n\ntype GraphemeSegmenter = { segment(input: string): Iterable<{ segment: string; index: number }> }\n\nlet cachedSegmenter: GraphemeSegmenter | null | undefined\n\nfunction getSegmenter(): GraphemeSegmenter | null {\n if (cachedSegmenter === undefined) {\n // Feature-detect once; Intl is the only global the module touches.\n const IntlAny = typeof Intl !== 'undefined' ? (Intl as unknown as Record<string, unknown>) : undefined\n const Segmenter = IntlAny?.Segmenter as\n | (new (locale: undefined, opts: { granularity: 'grapheme' }) => GraphemeSegmenter)\n | undefined\n cachedSegmenter = Segmenter ? new Segmenter(undefined, { granularity: 'grapheme' }) : null\n }\n return cachedSegmenter\n}\n\nfunction isHighSurrogate(code: number): boolean {\n return code >= 0xd800 && code <= 0xdbff\n}\n\nfunction isLowSurrogate(code: number): boolean {\n return code >= 0xdc00 && code <= 0xdfff\n}\n\n/** Count of grapheme clusters (code points in the fallback), never UTF-16 units. */\nexport function graphemeLength(text: string): number {\n if (text === '') return 0\n const segmenter = getSegmenter()\n if (segmenter) {\n let count = 0\n for (const _ of segmenter.segment(text)) count++\n return count\n }\n // Fallback: code points (surrogate pairs count once).\n let count = 0\n for (const _ of text) count++\n return count\n}\n\n/**\n * Clamp `text` to at most `max` grapheme clusters. A cluster that would cross\n * the limit is rejected whole — the result is always a valid cluster boundary.\n */\nexport function clampGraphemes(text: string, max: number): string {\n if (max <= 0) return ''\n const segmenter = getSegmenter()\n if (segmenter) {\n let count = 0\n let endIndex = text.length\n for (const part of segmenter.segment(text)) {\n count++\n if (count > max) {\n endIndex = part.index\n break\n }\n }\n return text.slice(0, endIndex)\n }\n // Fallback: walk code points, never splitting a surrogate pair.\n let count = 0\n let i = 0\n while (i < text.length && count < max) {\n const code = text.charCodeAt(i)\n i += isHighSurrogate(code) && i + 1 < text.length && isLowSurrogate(text.charCodeAt(i + 1)) ? 2 : 1\n count++\n }\n return text.slice(0, i)\n}\n\n/** Whether `offset` sits on a grapheme-cluster boundary of `text`. */\nexport function isGraphemeBoundary(text: string, offset: number): boolean {\n if (offset <= 0 || offset >= text.length) return true\n const segmenter = getSegmenter()\n if (segmenter) {\n for (const part of segmenter.segment(text)) {\n if (part.index === offset) return true\n if (part.index > offset) return false\n }\n return false\n }\n // Fallback: only reject offsets that split a surrogate pair.\n return !(isLowSurrogate(text.charCodeAt(offset)) && isHighSurrogate(text.charCodeAt(offset - 1)))\n}\n", "/**\n * Pure composer rules — the single source of truth shared by the enable-check,\n * the Enter handler, and the submitted payload (so they can never disagree).\n */\n\nimport { graphemeLength } from './graphemes.js'\nimport type { CounterState } from './types.js'\n\n/** One trim shared by the enable-check AND the sent payload. */\nexport function payloadFor(raw: string): string {\n return raw.trim()\n}\n\nexport interface CanSendArgs {\n text: string\n attachments?: readonly unknown[]\n disabled?: boolean\n readOnly?: boolean\n busy?: boolean\n}\n\n/**\n * Send is derived from BOTH trimmed text and attachments — an attachments-only\n * message is valid; a whitespace-only message never is.\n */\nexport function canSend({ text, attachments = [], disabled = false, readOnly = false, busy = false }: CanSendArgs): boolean {\n if (disabled || readOnly || busy) return false\n return payloadFor(text).length > 0 || attachments.length > 0\n}\n\n/**\n * The platform-matrix resolver: physical Enter sends unless Shift is held or\n * an IME composition is active (composing always wins — Enter confirms the\n * candidate, never submits).\n */\nexport function shouldSubmitOnEnter({\n shiftPressed,\n isComposing,\n}: {\n shiftPressed: boolean\n isComposing: boolean\n}): boolean {\n return !shiftPressed && !isComposing\n}\n\n/** The counter surfaces once remaining budget is within this fraction of maxLength. */\nexport const COUNTER_VISIBLE_FRACTION = 0.2\n\n/**\n * Counter math over grapheme budget. The counter is confirmatory, not the\n * enforcement mechanism (clamping at the insert boundary prevents overflow).\n */\nexport function computeCounter(text: string, maxLength: number | undefined): CounterState {\n if (maxLength === undefined) {\n return { visible: false, remaining: null, overLimit: false }\n }\n const remaining = maxLength - graphemeLength(text)\n return {\n visible: remaining <= maxLength * COUNTER_VISIBLE_FRACTION,\n remaining,\n overLimit: remaining <= 0,\n }\n}\n", "/**\n * Symbol-agnostic trigger detection (R10–R12).\n *\n * Detection runs once per mutation, scanning backward from the caret within a\n * bounded window — cost is independent of how much text precedes the trigger.\n * The simple single-char/anywhere case delegates to rich-editor's shared\n * `detectTriggerInText` so the boundary rules live in exactly one place; the\n * general path (multi-char symbols, scopes, extra boundary chars,\n * closeOnSpace=false) layers on top of the same candidate/validation split.\n */\n\nimport { detectTriggerInText } from '@refraction-ui/rich-editor'\nimport { graphemeLength } from './graphemes.js'\nimport type { ComposerTrigger, ComposerTriggerScope, ComposerCandidate } from './types.js'\n\n// ---------------------------------------------------------------------------\n// Resolved config (defaults applied once)\n// ---------------------------------------------------------------------------\n\nexport const DEFAULT_MAX_QUERY_LENGTH = 40\nexport const DEFAULT_MAX_VISIBLE_RESULTS = 6\n/**\n * UTF-16 budget per grapheme when sizing the backward-scan window. The widest\n * common cluster (ZWJ family emoji) is 11 units; 16 leaves headroom without\n * unbounding the scan.\n */\nexport const SCAN_UNITS_PER_GRAPHEME = 16\n\nexport interface ResolvedTrigger {\n id: string\n symbol: string\n scope: ComposerTriggerScope\n queryPattern: RegExp | null\n maxQueryLength: number\n closeOnSpace: boolean\n allowMidWord: boolean\n extraBoundaryChars: readonly string[]\n debounceMs: number\n maxVisibleResults: number\n wrapNavigation: boolean\n toDisplay: (candidate: ComposerCandidate) => string\n resolve: ComposerTrigger['resolve']\n}\n\nexport function resolveTriggerConfig(trigger: ComposerTrigger): ResolvedTrigger {\n const symbol = trigger.symbol\n return {\n id: trigger.id,\n symbol,\n scope: trigger.scope ?? 'anywhere',\n queryPattern: trigger.queryPattern ?? null,\n maxQueryLength: trigger.maxQueryLength ?? DEFAULT_MAX_QUERY_LENGTH,\n closeOnSpace: trigger.closeOnSpace ?? true,\n allowMidWord: trigger.allowMidWord ?? false,\n extraBoundaryChars: trigger.extraBoundaryChars ?? [],\n debounceMs: trigger.debounceMs ?? 0,\n maxVisibleResults: trigger.maxVisibleResults ?? DEFAULT_MAX_VISIBLE_RESULTS,\n wrapNavigation: trigger.wrapNavigation ?? true,\n toDisplay: trigger.toDisplay ?? ((candidate) => `${symbol}${candidate.display}`),\n resolve: trigger.resolve,\n }\n}\n\n/** UTF-16 size of the backward-scan window for a trigger (exported for tests). */\nexport function scanWindowFor(trigger: ResolvedTrigger): number {\n return trigger.maxQueryLength * SCAN_UNITS_PER_GRAPHEME + trigger.symbol.length\n}\n\n// ---------------------------------------------------------------------------\n// Detection\n// ---------------------------------------------------------------------------\n\nexport interface TriggerMatch {\n trigger: ResolvedTrigger\n symbolStart: number\n caret: number\n query: string\n}\n\n/** An occurrence dismissed via Escape — never re-arms until the symbol is retyped. */\nexport interface DismissedOccurrence {\n triggerId: string\n symbolStart: number\n}\n\nexport interface TokenRange {\n start: number\n end: number\n}\n\nexport interface DetectTriggerArgs {\n text: string\n caret: number\n triggers: readonly ResolvedTrigger[]\n isComposing?: boolean\n dismissed?: readonly DismissedOccurrence[]\n tokenRanges?: readonly TokenRange[]\n}\n\nfunction isWhitespace(ch: string): boolean {\n return /\\s/.test(ch)\n}\n\nfunction boundaryOk(text: string, symbolStart: number, trigger: ResolvedTrigger): boolean {\n if (trigger.allowMidWord) return true\n if (symbolStart === 0) return true\n const prev = text[symbolStart - 1]\n return isWhitespace(prev) || trigger.extraBoundaryChars.includes(prev)\n}\n\nfunction scopeOk(text: string, symbolStart: number, scope: ComposerTriggerScope): boolean {\n if (scope === 'anywhere') return true\n if (scope === 'startOfMessage') return symbolStart === 0\n // startOfLine\n return symbolStart === 0 || text[symbolStart - 1] === '\\n'\n}\n\n/** Nearest-occurrence candidate within the bounded scan window, or null. */\nfunction findCandidate(\n text: string,\n caret: number,\n trigger: ResolvedTrigger,\n): { symbolStart: number; query: string } | null {\n const window = scanWindowFor(trigger)\n const isSimple =\n trigger.symbol.length === 1 &&\n trigger.scope === 'anywhere' &&\n trigger.closeOnSpace &&\n !trigger.allowMidWord &&\n trigger.extraBoundaryChars.length === 0\n\n if (isSimple) {\n // Include one context char so the shared detector sees the boundary; a\n // symbol landing exactly on the slice edge is re-validated below anyway.\n const sliceStart = Math.max(0, caret - window - 1)\n const slice = text.slice(sliceStart, caret)\n const hit = detectTriggerInText(slice, slice.length, trigger.symbol)\n if (!hit) return null\n return { symbolStart: sliceStart + hit.start, query: hit.query }\n }\n\n const sliceStart = Math.max(0, caret - window)\n const rel = text.slice(sliceStart, caret).lastIndexOf(trigger.symbol)\n if (rel === -1) return null\n const symbolStart = sliceStart + rel\n // The symbol must be fully typed before the caret (multi-char symbols arm\n // only once complete — C13).\n if (symbolStart + trigger.symbol.length > caret) return null\n return { symbolStart, query: text.slice(symbolStart + trigger.symbol.length, caret) }\n}\n\nfunction validateCandidate(\n text: string,\n caret: number,\n trigger: ResolvedTrigger,\n candidate: { symbolStart: number; query: string },\n dismissed: readonly DismissedOccurrence[],\n tokenRanges: readonly TokenRange[],\n): TriggerMatch | null {\n const { symbolStart, query } = candidate\n if (!boundaryOk(text, symbolStart, trigger)) return null\n if (!scopeOk(text, symbolStart, trigger.scope)) return null\n // Never arm inside a committed token's range (its display may contain the symbol).\n for (const range of tokenRanges) {\n if (symbolStart >= range.start && symbolStart < range.end) return null\n }\n if (dismissed.some((d) => d.triggerId === trigger.id && d.symbolStart === symbolStart)) return null\n if (trigger.closeOnSpace && /\\s/.test(query)) return null\n // Queries are line-local even when spaces are allowed ('#weekend trip').\n if (!trigger.closeOnSpace && query.includes('\\n')) return null\n if (graphemeLength(query) > trigger.maxQueryLength) return null\n if (trigger.queryPattern && !trigger.queryPattern.test(query)) return null\n return { trigger, symbolStart, caret, query }\n}\n\n/**\n * Backward-scan detection from the caret. Returns the nearest live trigger\n * (only the last unescaped occurrence before the caret can be live), or null.\n * Suspended entirely while an IME composition is active.\n */\nexport function detectActiveTrigger({\n text,\n caret,\n triggers,\n isComposing = false,\n dismissed = [],\n tokenRanges = [],\n}: DetectTriggerArgs): TriggerMatch | null {\n if (isComposing) return null\n let best: TriggerMatch | null = null\n for (const trigger of triggers) {\n const candidate = findCandidate(text, caret, trigger)\n if (!candidate) continue\n const match = validateCandidate(text, caret, trigger, candidate, dismissed, tokenRanges)\n if (!match) continue\n if (\n !best ||\n match.symbolStart > best.symbolStart ||\n (match.symbolStart === best.symbolStart && match.trigger.symbol.length > best.trigger.symbol.length)\n ) {\n best = match\n }\n }\n return best\n}\n", "/**\n * Suggestion menu state (R13, R14): debounced async resolution with a\n * monotonic requestToken staleness guard, delayed loading state, empty/error\n * states with retry, a per-trigger circuit breaker, and keyboard navigation.\n *\n * Timers are plain setTimeout/clearTimeout — cancelable and deterministic\n * under vitest fake timers; the controller never touches DOM globals.\n */\n\nimport type { ComposerCandidate, SuggestionState } from './types.js'\nimport type { TriggerMatch } from './trigger-engine.js'\n\n/** Loading UI appears only if the resolver is still pending after this long. */\nexport const LOADING_DELAY_MS = 120\n/** Deferred (blur) dismissal grace so a pointer tap on a row can land first. */\nexport const DISMISS_GRACE_MS = 120\n/** Consecutive resolver throws for one trigger before remote calls stop for the session. */\nexport const CIRCUIT_BREAKER_THRESHOLD = 2\n\nexport const CLOSED_SUGGESTION_STATE: SuggestionState = {\n isOpen: false,\n items: [],\n visibleItems: [],\n activeIndex: 0,\n loading: false,\n error: null,\n requestToken: 0,\n}\n\nexport interface SuggestionController {\n readonly state: SuggestionState\n /** Reconcile with the current trigger match after any mutation/caret move. */\n sync(match: TriggerMatch | null): void\n moveNext(): void\n movePrevious(): void\n setActiveIndex(index: number): void\n retry(): void\n /** Close after a grace period (blur); a commit during the grace cancels it. */\n dismissDeferred(onClosed: () => void): void\n cancelDeferredDismiss(): void\n /** Close immediately and invalidate any in-flight resolution. */\n close(): void\n isTripped(triggerId: string): boolean\n destroy(): void\n}\n\nexport function createSuggestionController({ emit }: { emit: () => void }): SuggestionController {\n let state: SuggestionState = CLOSED_SUGGESTION_STATE\n let match: TriggerMatch | null = null\n let requestCounter = 0\n let debounceTimer: ReturnType<typeof setTimeout> | null = null\n let loadingTimer: ReturnType<typeof setTimeout> | null = null\n let dismissTimer: ReturnType<typeof setTimeout> | null = null\n const consecutiveFailures = new Map<string, number>()\n const tripped = new Set<string>()\n\n function clearTimer(timer: ReturnType<typeof setTimeout> | null): null {\n if (timer !== null) clearTimeout(timer)\n return null\n }\n\n function setState(patch: Partial<SuggestionState>): void {\n state = { ...state, ...patch }\n }\n\n function sameOccurrence(a: TriggerMatch | null, b: TriggerMatch | null): boolean {\n return (\n a !== null &&\n b !== null &&\n a.trigger.id === b.trigger.id &&\n a.symbolStart === b.symbolStart\n )\n }\n\n function closeInternal(): void {\n debounceTimer = clearTimer(debounceTimer)\n loadingTimer = clearTimer(loadingTimer)\n // Bumping the counter invalidates any in-flight resolution (D13).\n requestCounter++\n match = null\n state = CLOSED_SUGGESTION_STATE\n }\n\n function applyResults(forMatch: TriggerMatch, items: ComposerCandidate[]): void {\n loadingTimer = clearTimer(loadingTimer)\n consecutiveFailures.set(forMatch.trigger.id, 0)\n setState({\n isOpen: true,\n items,\n visibleItems: items.slice(0, forMatch.trigger.maxVisibleResults),\n activeIndex: 0,\n loading: false,\n error: null,\n })\n }\n\n function applyFailure(forMatch: TriggerMatch, error: unknown): void {\n loadingTimer = clearTimer(loadingTimer)\n const triggerId = forMatch.trigger.id\n const failures = (consecutiveFailures.get(triggerId) ?? 0) + 1\n consecutiveFailures.set(triggerId, failures)\n if (failures >= CIRCUIT_BREAKER_THRESHOLD) {\n // Session fallback: stop resolving remotely, close silently (D6).\n tripped.add(triggerId)\n closeInternal()\n return\n }\n setState({\n isOpen: true,\n items: [],\n visibleItems: [],\n loading: false,\n error: error instanceof Error ? error.message : 'Failed to load suggestions',\n })\n }\n\n /**\n * `notifySync` — emit() after a synchronously-settled resolve. False when a\n * composer mutation is already in flight (it emits once at the end); true\n * when invoked from a timer, where nobody else will notify.\n */\n function dispatch(forMatch: TriggerMatch, notifySync: boolean): void {\n const token = ++requestCounter\n setState({ requestToken: token })\n let result: ComposerCandidate[] | Promise<ComposerCandidate[]>\n try {\n result = forMatch.trigger.resolve(forMatch.query)\n } catch (error) {\n applyFailure(forMatch, error)\n if (notifySync) emit()\n return\n }\n if (Array.isArray(result)) {\n applyResults(forMatch, result)\n if (notifySync) emit()\n return\n }\n // Async: show the loading state only if it is still pending after 120ms.\n loadingTimer = clearTimer(loadingTimer)\n loadingTimer = setTimeout(() => {\n if (token !== requestCounter) return\n setState({ isOpen: true, loading: true })\n emit()\n }, LOADING_DELAY_MS)\n result.then(\n (items) => {\n // Staleness guard: a slow response must never overwrite a newer one,\n // and results arriving after the trigger closed are ignored.\n if (token !== requestCounter || !sameOccurrence(match, forMatch)) return\n applyResults(forMatch, items)\n emit()\n },\n (error) => {\n if (token !== requestCounter || !sameOccurrence(match, forMatch)) return\n applyFailure(forMatch, error)\n emit()\n },\n )\n }\n\n function schedule(forMatch: TriggerMatch): void {\n debounceTimer = clearTimer(debounceTimer)\n if (forMatch.trigger.debounceMs <= 0) {\n dispatch(forMatch, false)\n return\n }\n debounceTimer = setTimeout(() => {\n debounceTimer = null\n if (sameOccurrence(match, forMatch) && match?.query === forMatch.query) {\n dispatch(forMatch, true)\n }\n }, forMatch.trigger.debounceMs)\n }\n\n return {\n get state() {\n return state\n },\n\n sync(next: TriggerMatch | null): void {\n if (next === null) {\n if (match !== null || state.isOpen) closeInternal()\n return\n }\n if (tripped.has(next.trigger.id)) {\n if (match !== null || state.isOpen) closeInternal()\n return\n }\n const isSame = sameOccurrence(match, next)\n const queryChanged = !isSame || match?.query !== next.query\n match = next\n if (!isSame) {\n // Fresh occurrence: start from a clean open menu.\n state = { ...CLOSED_SUGGESTION_STATE, isOpen: true, requestToken: state.requestToken }\n }\n if (queryChanged) schedule(next)\n },\n\n moveNext(): void {\n const count = state.items.length\n if (!state.isOpen || count === 0) return\n const wrap = match?.trigger.wrapNavigation ?? true\n const next = wrap\n ? (state.activeIndex + 1) % count\n : Math.min(state.activeIndex + 1, count - 1)\n setState({ activeIndex: next })\n },\n\n movePrevious(): void {\n const count = state.items.length\n if (!state.isOpen || count === 0) return\n const wrap = match?.trigger.wrapNavigation ?? true\n const previous = wrap\n ? (state.activeIndex - 1 + count) % count\n : Math.max(state.activeIndex - 1, 0)\n setState({ activeIndex: previous })\n },\n\n setActiveIndex(index: number): void {\n if (!state.isOpen || state.items.length === 0) return\n setState({ activeIndex: Math.max(0, Math.min(index, state.items.length - 1)) })\n },\n\n retry(): void {\n if (match === null) return\n if (tripped.has(match.trigger.id)) return\n setState({ error: null })\n dispatch(match, false)\n },\n\n dismissDeferred(onClosed: () => void): void {\n dismissTimer = clearTimer(dismissTimer)\n dismissTimer = setTimeout(() => {\n dismissTimer = null\n closeInternal()\n onClosed()\n }, DISMISS_GRACE_MS)\n },\n\n cancelDeferredDismiss(): void {\n dismissTimer = clearTimer(dismissTimer)\n },\n\n close(): void {\n dismissTimer = clearTimer(dismissTimer)\n closeInternal()\n },\n\n isTripped(triggerId: string): boolean {\n return tripped.has(triggerId)\n },\n\n destroy(): void {\n debounceTimer = clearTimer(debounceTimer)\n loadingTimer = clearTimer(loadingTimer)\n dismissTimer = clearTimer(dismissTimer)\n match = null\n state = CLOSED_SUGGESTION_STATE\n },\n }\n}\n", "/**\n * Token bookkeeping over the flat string (R15–R18).\n *\n * Committed tokens are atomic: an edit touching part of a token removes the\n * whole token; an insertion strictly inside a token is rejected; the caret\n * never rests strictly inside a token. Every function here is pure — the\n * composer owns the state and feeds it through these rules on each mutation.\n */\n\nimport { detectEmojiShortcode } from '@refraction-ui/rich-editor'\nimport { clampGraphemes, graphemeLength } from './graphemes.js'\nimport type {\n ComposerOutput,\n ComposerSelection,\n ComposerToken,\n PlacedToken,\n ResolvedToken,\n} from './types.js'\n\n// ---------------------------------------------------------------------------\n// Diffing\n// ---------------------------------------------------------------------------\n\nexport interface TextEdit {\n /** Offset where old and new text diverge. */\n start: number\n /** End of the replaced range in the OLD text. */\n oldEnd: number\n /** End of the inserted range in the NEW text. */\n newEnd: number\n}\n\n/** Minimal single-span diff via common prefix/suffix. Null when texts are equal. */\nexport function diffEdit(oldText: string, newText: string): TextEdit | null {\n if (oldText === newText) return null\n const minLen = Math.min(oldText.length, newText.length)\n let prefix = 0\n while (prefix < minLen && oldText[prefix] === newText[prefix]) prefix++\n let suffix = 0\n while (\n suffix < minLen - prefix &&\n oldText[oldText.length - 1 - suffix] === newText[newText.length - 1 - suffix]\n ) {\n suffix++\n }\n return { start: prefix, oldEnd: oldText.length - suffix, newEnd: newText.length - suffix }\n}\n\n// ---------------------------------------------------------------------------\n// Edit application with token atomicity\n// ---------------------------------------------------------------------------\n\nexport interface AppliedEdit {\n value: string\n tokens: PlacedToken[]\n selection: ComposerSelection\n /** Set when an insertion strictly inside a token was rejected (value restored). */\n rejected: boolean\n /** True when maxLength clamping dropped part of the inserted text. */\n trimmed: boolean\n removedTokens: PlacedToken[]\n /** The edit as actually applied to the old value (post token expansion), or null. */\n edit: TextEdit | null\n}\n\nexport interface ApplyValueEditArgs {\n oldValue: string\n newValue: string\n tokens: readonly PlacedToken[]\n newSelection: ComposerSelection\n /** Grapheme budget; the INSERTED slice is clamped to fit, never bisecting a cluster. */\n maxLength?: number\n}\n\nfunction nearestBoundary(offset: number, token: PlacedToken): number {\n return offset - token.start <= token.end - offset ? token.start : token.end\n}\n\n/**\n * Reconcile a raw text edit (as reported by the adapter's input) with the\n * committed token list. Deletions touching part of a token expand to the whole\n * token; insertions strictly inside a token are rejected outright.\n */\nexport function applyValueEdit({\n oldValue,\n newValue,\n tokens,\n newSelection,\n maxLength,\n}: ApplyValueEditArgs): AppliedEdit {\n const noChange: AppliedEdit = {\n value: oldValue,\n tokens: [...tokens],\n selection: newSelection,\n rejected: false,\n trimmed: false,\n removedTokens: [],\n edit: null,\n }\n const diff = diffEdit(oldValue, newValue)\n if (!diff) return noChange\n\n const insertedRaw = newValue.slice(diff.start, diff.newEnd)\n\n // Pure insertion strictly inside a token → reject, snap caret to a boundary.\n if (diff.oldEnd === diff.start) {\n const host = tokens.find((t) => t.start < diff.start && diff.start < t.end)\n if (host) {\n const caret = nearestBoundary(diff.start, host)\n return { ...noChange, rejected: true, selection: { start: caret, end: caret } }\n }\n }\n\n // Expand the replaced range over any partially/fully covered token (atomic delete).\n let expStart = diff.start\n let expOldEnd = diff.oldEnd\n const removedTokens: PlacedToken[] = []\n for (const token of tokens) {\n const overlaps = token.start < expOldEnd && token.end > expStart\n if (!overlaps) continue\n removedTokens.push(token)\n expStart = Math.min(expStart, token.start)\n expOldEnd = Math.max(expOldEnd, token.end)\n }\n\n // Clamp the inserted slice against the grapheme budget of what remains.\n let inserted = insertedRaw\n let trimmed = false\n if (maxLength !== undefined && inserted.length > 0) {\n const baseLength =\n graphemeLength(oldValue.slice(0, expStart)) + graphemeLength(oldValue.slice(expOldEnd))\n const budget = Math.max(0, maxLength - baseLength)\n if (graphemeLength(inserted) > budget) {\n inserted = clampGraphemes(inserted, budget)\n trimmed = true\n }\n }\n\n const value = oldValue.slice(0, expStart) + inserted + oldValue.slice(expOldEnd)\n const delta = expStart + inserted.length - expOldEnd\n const nextTokens = tokens\n .filter((t) => !removedTokens.includes(t))\n .map((t) => (t.start >= expOldEnd ? { ...t, start: t.start + delta, end: t.end + delta } : t))\n\n // When the applied edit differs from the raw input (expansion/clamp), the\n // reported selection can point past reality — collapse to end of insertion.\n const appliedMatchesRaw = expStart === diff.start && expOldEnd === diff.oldEnd && !trimmed\n const caret = expStart + inserted.length\n const selection = appliedMatchesRaw ? newSelection : { start: caret, end: caret }\n\n return {\n value,\n tokens: nextTokens,\n selection: clampSelectionToValue(selection, value),\n rejected: false,\n trimmed,\n removedTokens,\n edit: { start: expStart, oldEnd: expOldEnd, newEnd: expStart + inserted.length },\n }\n}\n\nfunction clampSelectionToValue(selection: ComposerSelection, value: string): ComposerSelection {\n const clamp = (n: number) => Math.max(0, Math.min(n, value.length))\n return { start: clamp(selection.start), end: clamp(selection.end) }\n}\n\n// ---------------------------------------------------------------------------\n// Selection snapping\n// ---------------------------------------------------------------------------\n\n/**\n * Snap a selection so no endpoint rests strictly inside a token. A collapsed\n * caret snaps in the direction of travel (so arrow keys skip a token as one\n * unit); a range expands outward to the full token bounds.\n */\nexport function snapSelectionToTokens(\n tokens: readonly PlacedToken[],\n selection: ComposerSelection,\n previous?: ComposerSelection,\n): ComposerSelection {\n const inside = (offset: number) =>\n tokens.find((t) => t.start < offset && offset < t.end)\n\n if (selection.start === selection.end) {\n const host = inside(selection.start)\n if (!host) return selection\n let caret: number\n if (previous && previous.start === previous.end && previous.start !== selection.start) {\n caret = selection.start > previous.start ? host.end : host.start\n } else {\n caret = nearestBoundary(selection.start, host)\n }\n return { start: caret, end: caret }\n }\n\n const startHost = inside(selection.start)\n const endHost = inside(selection.end)\n return {\n start: startHost ? startHost.start : selection.start,\n end: endHost ? endHost.end : selection.end,\n }\n}\n\n/** Expand a range outward so it covers any partially-included token whole. */\nexport function expandRangeOverTokens(\n tokens: readonly PlacedToken[],\n range: ComposerSelection,\n): ComposerSelection {\n let { start, end } = range\n for (const token of tokens) {\n if (token.start < end && token.end > start) {\n start = Math.min(start, token.start)\n end = Math.max(end, token.end)\n }\n }\n return { start, end }\n}\n\n// ---------------------------------------------------------------------------\n// Commit / serialize\n// ---------------------------------------------------------------------------\n\nexport interface CommitTokenArgs {\n value: string\n tokens: readonly PlacedToken[]\n /** Replaced range: `[start, end)` — symbol + query for a trigger commit. */\n start: number\n end: number\n token: ComposerToken\n}\n\nexport interface CommitTokenResult {\n value: string\n tokens: PlacedToken[]\n selection: ComposerSelection\n}\n\n/** Replace `[start, end)` with the token's display in one atomic transaction. */\nexport function commitTokenAt({ value, tokens, start, end, token }: CommitTokenArgs): CommitTokenResult {\n const display = token.display\n const nextValue = value.slice(0, start) + display + value.slice(end)\n const delta = display.length - (end - start)\n const placed: PlacedToken = { ...token, start, end: start + display.length }\n const shifted = tokens.map((t) =>\n t.start >= end ? { ...t, start: t.start + delta, end: t.end + delta } : t,\n )\n const nextTokens = [...shifted, placed].sort((a, b) => a.start - b.start)\n const caret = placed.end\n return { value: nextValue, tokens: nextTokens, selection: { start: caret, end: caret } }\n}\n\n/**\n * Structured output: plainText inlines every display; ranges are derived and\n * guaranteed in sync (`plainText.substring(start, end) === display`).\n */\nexport function serializeTokens(value: string, tokens: readonly PlacedToken[]): ComposerOutput {\n const resolved: ResolvedToken[] = tokens.map((t) => ({\n type: t.triggerId,\n id: t.id,\n display: t.display,\n start: t.start,\n end: t.end,\n }))\n return { plainText: value, tokens: resolved }\n}\n\n// ---------------------------------------------------------------------------\n// Direct-typed emoji commit (R18)\n// ---------------------------------------------------------------------------\n\nexport interface TypedEmojiHit {\n shortcode: string\n unicode: string\n /** Offset of the opening colon. */\n start: number\n}\n\n/**\n * Detect a just-completed `:shortcode:` immediately before the caret using\n * rich-editor's EMOJI_MAP (via detectEmojiShortcode). Unknown shortcodes stay\n * literal; the composer converts a hit into a committed token without the\n * menu ever opening.\n */\nexport function detectTypedEmoji(value: string, caret: number): TypedEmojiHit | null {\n const hit = detectEmojiShortcode(value.slice(0, caret))\n if (!hit) return null\n return { shortcode: hit.shortcode, unicode: hit.unicode, start: caret - hit.shortcode.length }\n}\n", "/**\n * `createComposer(config)` — the headless composer core.\n *\n * Owns the flat value + selection, committed tokens, staged attachments,\n * trigger/suggestion lifecycle, drafts, and the optimistic submit. Pure of\n * DOM/browser globals: `now`/`generateId` are injected (defaults live only at\n * this boundary, matching the other cores in the repo), so the same instance\n * is hydration-deterministic and portable to any framework adapter.\n */\n\nimport { generateId as sharedGenerateId } from '@refraction-ui/shared'\nimport { clampGraphemes, graphemeLength } from './graphemes.js'\nimport { canSend as computeCanSend, computeCounter, shouldSubmitOnEnter, payloadFor } from './rules.js'\nimport {\n detectActiveTrigger,\n resolveTriggerConfig,\n type DismissedOccurrence,\n type ResolvedTrigger,\n type TriggerMatch,\n} from './trigger-engine.js'\nimport { createSuggestionController, CLOSED_SUGGESTION_STATE } from './suggestion.js'\nimport {\n applyValueEdit,\n commitTokenAt,\n detectTypedEmoji,\n expandRangeOverTokens,\n serializeTokens,\n snapSelectionToTokens,\n} from './tokens.js'\nimport type {\n ActiveTriggerState,\n ComposerAPI,\n ComposerAttachment,\n ComposerConfig,\n ComposerDraft,\n ComposerOutput,\n ComposerSelection,\n ComposerState,\n ComposerSubmission,\n ComposerToken,\n EnterResult,\n PlacedToken,\n ResolvedToken,\n} from './types.js'\n\nexport const DEFAULT_DRAFT_DEBOUNCE_MS = 400\nexport const DEFAULT_TYPING_SIGNAL_INTERVAL_MS = 3000\nexport const DEFAULT_MIN_LINES = 1\nexport const DEFAULT_MAX_LINES = 6\n\ninterface ClipShadow {\n text: string\n /** Tokens rebased to the start of the cut range. */\n tokens: PlacedToken[]\n}\n\ninterface EditModeSnapshot {\n value: string\n tokens: PlacedToken[]\n selection: ComposerSelection\n attachments: ComposerAttachment[]\n}\n\nfunction validateDraft(raw: unknown): ComposerDraft | null {\n if (typeof raw !== 'object' || raw === null) return null\n const draft = raw as Partial<ComposerDraft>\n if (typeof draft.value !== 'string') return null\n if (!Array.isArray(draft.tokens)) return null\n if (!Array.isArray(draft.attachmentIds)) return null\n for (const token of draft.tokens) {\n if (\n typeof token !== 'object' ||\n token === null ||\n typeof token.start !== 'number' ||\n typeof token.end !== 'number' ||\n typeof token.display !== 'string' ||\n // The one invariant that matters: ranges must project the display.\n draft.value.substring(token.start, token.end) !== token.display\n ) {\n return null\n }\n }\n return {\n value: draft.value,\n tokens: draft.tokens as PlacedToken[],\n attachmentIds: draft.attachmentIds as string[],\n updatedAt: typeof draft.updatedAt === 'number' ? draft.updatedAt : 0,\n }\n}\n\nexport function createComposer(config: ComposerConfig = {}): ComposerAPI {\n // Injection boundary: defaults using ambient time/counters live ONLY here.\n const now = config.now ?? (() => Date.now())\n const generateId = config.generateId ?? sharedGenerateId\n const onEvent = config.onEvent ?? (() => undefined)\n const triggers: ResolvedTrigger[] = (config.triggers ?? []).map(resolveTriggerConfig)\n const draftDebounceMs = config.draftDebounceMs ?? DEFAULT_DRAFT_DEBOUNCE_MS\n const typingSignalIntervalMs = config.typingSignalIntervalMs ?? DEFAULT_TYPING_SIGNAL_INTERVAL_MS\n const emojiTrigger = triggers.find((t) => t.symbol === ':') ?? null\n\n // --- mutable internal state -------------------------------------------------\n let value = config.initialValue ?? ''\n let tokens: PlacedToken[] = [...(config.initialTokens ?? [])]\n let selection: ComposerSelection = { start: value.length, end: value.length }\n let isComposing = false\n let isBusy = false\n let disabled = false\n let readOnly = false\n let error: string | null = null\n let attachments: ComposerAttachment[] = []\n let mode: 'compose' | 'edit' = 'compose'\n let editingMessageId: string | undefined\n let preEditSnapshot: EditModeSnapshot | null = null\n let activeMatch: TriggerMatch | null = null\n let dismissed: DismissedOccurrence[] = []\n let clipShadow: ClipShadow | null = null\n let lastTypingAt = Number.NEGATIVE_INFINITY\n let draftTimer: ReturnType<typeof setTimeout> | null = null\n let destroyed = false\n\n const listeners = new Set<(state: ComposerState) => void>()\n\n // Draft restore on create (fail closed: a corrupt draft yields a clean state).\n if (config.draftStore && config.draftKey) {\n try {\n const draft = validateDraft(config.draftStore.read(config.draftKey))\n if (draft) {\n value = draft.value\n tokens = draft.tokens\n selection = { start: value.length, end: value.length }\n }\n } catch {\n // fail closed — start empty\n }\n }\n\n const suggestions = createSuggestionController({ emit: () => emit() })\n\n // --- state assembly ----------------------------------------------------------\n\n function toActiveTriggerState(match: TriggerMatch | null): ActiveTriggerState | null {\n if (!match) return null\n return {\n triggerId: match.trigger.id,\n symbol: match.trigger.symbol,\n symbolStart: match.symbolStart,\n caret: match.caret,\n query: match.query,\n }\n }\n\n function buildState(): ComposerState {\n const counter = computeCounter(value, config.maxLength)\n const overBudget = counter.remaining !== null && counter.remaining < 0\n return {\n value,\n selection,\n isComposing,\n isBusy,\n disabled,\n readOnly,\n isEmpty: payloadFor(value).length === 0 && attachments.length === 0,\n canSend:\n computeCanSend({ text: value, attachments, disabled, readOnly, busy: isBusy }) &&\n !overBudget,\n error,\n attachments: [...attachments],\n tokens: [...tokens],\n activeTrigger: toActiveTriggerState(activeMatch),\n suggestion: destroyed ? CLOSED_SUGGESTION_STATE : suggestions.state,\n counter,\n mode,\n editingMessageId,\n }\n }\n\n function emit(): void {\n if (destroyed) return\n const snapshot = buildState()\n for (const listener of listeners) listener(snapshot)\n }\n\n // --- cross-cutting helpers ----------------------------------------------------\n\n function shiftDismissedForEdit(editStart: number, editOldEnd: number, delta: number): void {\n dismissed = dismissed\n .map((d) => (d.symbolStart >= editOldEnd ? { ...d, symbolStart: d.symbolStart + delta } : d))\n // An occurrence only stays dismissed while its symbol still sits at that\n // offset — deleting and retyping the symbol re-arms (C9).\n .filter((d) => {\n if (d.symbolStart >= editStart && d.symbolStart < editOldEnd) return false\n const trigger = triggers.find((t) => t.id === d.triggerId)\n return trigger !== undefined && value.startsWith(trigger.symbol, d.symbolStart)\n })\n }\n\n function recomputeTriggers(): void {\n const collapsed = selection.start === selection.end\n activeMatch =\n collapsed && !isComposing && !disabled && !readOnly\n ? detectActiveTrigger({\n text: value,\n caret: selection.start,\n triggers,\n isComposing,\n dismissed,\n tokenRanges: tokens,\n })\n : null\n suggestions.sync(activeMatch)\n }\n\n function fireTypingSignal(): void {\n // Leading edge + max one per interval; trailing edge deliberately dropped.\n const at = now()\n if (at - lastTypingAt >= typingSignalIntervalMs) {\n lastTypingAt = at\n onEvent({ type: 'typing' })\n }\n }\n\n function scheduleDraftWrite(): void {\n if (!config.draftStore || !config.draftKey) return\n if (draftTimer !== null) clearTimeout(draftTimer)\n draftTimer = setTimeout(() => {\n draftTimer = null\n // Never persist mid-IME text (F7); the post-composition mutation reschedules.\n if (isComposing || destroyed) return\n config.draftStore?.write(config.draftKey as string, {\n value,\n tokens: [...tokens],\n attachmentIds: attachments.map((a) => a.id),\n updatedAt: now(),\n })\n }, draftDebounceMs)\n }\n\n function cancelDraftWrite(): void {\n if (draftTimer !== null) {\n clearTimeout(draftTimer)\n draftTimer = null\n }\n }\n\n function clearPersistedDraft(): void {\n cancelDraftWrite()\n if (config.draftStore && config.draftKey) config.draftStore.clear(config.draftKey)\n }\n\n function maybeCommitTypedEmoji(): void {\n if (!emojiTrigger || selection.start !== selection.end) return\n const caretBefore = selection.start\n const hit = detectTypedEmoji(value, caretBefore)\n if (!hit) return\n // Never re-tokenize inside an existing token's range.\n if (tokens.some((t) => t.start < caretBefore && hit.start < t.end)) return\n const token: ComposerToken = {\n triggerId: emojiTrigger.id,\n symbol: emojiTrigger.symbol,\n id: hit.shortcode,\n label: hit.shortcode.slice(1, -1),\n display: hit.unicode,\n }\n const committed = commitTokenAt({\n value,\n tokens,\n start: hit.start,\n end: caretBefore,\n token,\n })\n const delta = committed.value.length - value.length\n value = committed.value\n tokens = committed.tokens\n selection = committed.selection\n shiftDismissedForEdit(hit.start, caretBefore, delta)\n }\n\n interface MutateOptions {\n programmatic?: boolean\n /** Emit 'paste-trimmed' (vs silent clamp) when the budget trims the insert. */\n trimEventOnClamp?: boolean\n }\n\n /**\n * The one write path for text: diff → token atomicity → clamp → snap →\n * emoji commit → trigger detection → typing signal → draft write → emit.\n * Returns the applied edit start (for clip-shadow restores) or null.\n */\n function mutateValue(\n newText: string,\n newSelection: ComposerSelection,\n opts: MutateOptions = {},\n ): { applied: boolean; editStart: number } {\n const previousSelection = selection\n\n if (isComposing) {\n // IME owns mid-composition text: track it verbatim (token ranges still\n // shift via the diff) but suspend clamping, triggers, drafts, signals.\n const result = applyValueEdit({ oldValue: value, newValue: newText, tokens, newSelection })\n value = result.value\n tokens = result.tokens\n selection = result.selection\n emit()\n return { applied: true, editStart: result.edit?.start ?? newSelection.start }\n }\n\n const result = applyValueEdit({\n oldValue: value,\n newValue: newText,\n tokens,\n newSelection,\n maxLength: config.maxLength,\n })\n\n if (result.rejected) {\n onEvent({ type: 'edit-rejected', reason: 'inside-token' })\n selection = result.selection\n recomputeTriggers()\n emit()\n return { applied: false, editStart: 0 }\n }\n\n if (result.trimmed && opts.trimEventOnClamp !== false) {\n onEvent({ type: 'paste-trimmed' })\n }\n\n const editStart = result.edit?.start ?? newSelection.start\n value = result.value\n tokens = result.tokens\n selection = snapSelectionToTokens(tokens, result.selection, previousSelection)\n\n if (result.edit) {\n shiftDismissedForEdit(\n result.edit.start,\n result.edit.oldEnd,\n result.edit.newEnd - result.edit.oldEnd,\n )\n }\n\n maybeCommitTypedEmoji()\n recomputeTriggers()\n if (!opts.programmatic) fireTypingSignal()\n scheduleDraftWrite()\n emit()\n return { applied: true, editStart }\n }\n\n function buildSubmissionTokens(): { plainText: string; tokens: ResolvedToken[] } {\n const plainText = payloadFor(value)\n const leading = value.length - value.trimStart().length\n const serialized = serializeTokens(value, tokens)\n return {\n plainText,\n tokens: serialized.tokens.map((t) => ({ ...t, start: t.start - leading, end: t.end - leading })),\n }\n }\n\n function commitSuggestion(index?: number): boolean {\n const match = activeMatch\n const menu = suggestions.state\n if (!match || !menu.isOpen) return false\n const item = menu.items[index ?? menu.activeIndex]\n if (!item) return false\n\n const display = match.trigger.toDisplay(item)\n if (config.maxLength !== undefined) {\n const replacedSpan = value.slice(match.symbolStart, match.caret)\n const nextLength =\n graphemeLength(value) - graphemeLength(replacedSpan) + graphemeLength(display)\n // A token commits whole or not at all — never a clamped half-token (E16).\n if (nextLength > config.maxLength) {\n onEvent({ type: 'insert-rejected', reason: 'max-length' })\n emit()\n return false\n }\n }\n\n const token: ComposerToken = {\n triggerId: match.trigger.id,\n symbol: match.trigger.symbol,\n id: item.id,\n label: item.display,\n display,\n metadata: item.metadata,\n }\n const committed = commitTokenAt({\n value,\n tokens,\n start: match.symbolStart,\n end: match.caret,\n token,\n })\n const delta = committed.value.length - value.length\n value = committed.value\n tokens = committed.tokens\n selection = committed.selection\n shiftDismissedForEdit(match.symbolStart, match.caret, delta)\n suggestions.cancelDeferredDismiss()\n activeMatch = null\n suggestions.sync(null)\n scheduleDraftWrite()\n emit()\n return true\n }\n\n function performSubmit(): ComposerSubmission | null {\n const counter = computeCounter(value, config.maxLength)\n const overBudget = counter.remaining !== null && counter.remaining < 0\n if (!computeCanSend({ text: value, attachments, disabled, readOnly, busy: isBusy }) || overBudget) {\n return null\n }\n\n const { plainText, tokens: submissionTokens } = buildSubmissionTokens()\n if (config.validator) {\n const verdict = config.validator(plainText, submissionTokens)\n if (!verdict.isValid) {\n // Draft-preserving: surface the reason, leave the value untouched.\n error = verdict.reason ?? 'Message failed validation'\n emit()\n return null\n }\n }\n\n const submission: ComposerSubmission = {\n plainText,\n tokens: submissionTokens,\n attachments: [...attachments],\n replyToMessageId: config.replyToMessageId,\n ...(mode === 'edit' ? { editingMessageId } : {}),\n }\n\n // Optimistic: snapshot then clear synchronously; transport is host-owned.\n value = ''\n tokens = []\n attachments = []\n selection = { start: 0, end: 0 }\n error = null\n activeMatch = null\n dismissed = []\n clipShadow = null\n mode = 'compose'\n editingMessageId = undefined\n preEditSnapshot = null\n suggestions.close()\n clearPersistedDraft()\n emit()\n return submission\n }\n\n // --- public API ----------------------------------------------------------------\n\n const api: ComposerAPI = {\n getState(): ComposerState {\n return buildState()\n },\n\n subscribe(listener: (state: ComposerState) => void): () => void {\n listeners.add(listener)\n return () => listeners.delete(listener)\n },\n\n setValue(text, sel, opts): void {\n if (destroyed || disabled) return\n if (readOnly) {\n // Value is immutable via the user path; selection moves are allowed.\n if (text === value && sel) api.setSelection(sel)\n return\n }\n const nextSelection = sel ?? { start: text.length, end: text.length }\n mutateValue(text, nextSelection, { programmatic: opts?.programmatic })\n },\n\n setSelection(sel): void {\n if (destroyed || disabled) return\n const previous = selection\n selection = snapSelectionToTokens(tokens, {\n start: Math.max(0, Math.min(sel.start, value.length)),\n end: Math.max(0, Math.min(sel.end, value.length)),\n }, previous)\n // Any caret move re-evaluates the trigger (closes when leaving its range).\n recomputeTriggers()\n emit()\n },\n\n insertTextAtCursor(text): void {\n if (destroyed) return\n if (disabled) {\n onEvent({ type: 'insert-rejected', reason: 'disabled' })\n return\n }\n if (readOnly) {\n onEvent({ type: 'insert-rejected', reason: 'read-only' })\n return\n }\n if (isComposing) {\n // Splicing into an active composition corrupts the candidate — no-op.\n onEvent({ type: 'insert-rejected', reason: 'composing' })\n return\n }\n if (config.maxLength !== undefined) {\n const range = expandRangeOverTokens(tokens, selection)\n const base =\n graphemeLength(value.slice(0, range.start)) + graphemeLength(value.slice(range.end))\n // All-or-nothing: a programmatic insert is rejected, never truncated.\n if (base + graphemeLength(text) > config.maxLength) {\n onEvent({ type: 'insert-rejected', reason: 'max-length' })\n return\n }\n }\n const nextText = value.slice(0, selection.start) + text + value.slice(selection.end)\n const caret = selection.start + text.length\n mutateValue(nextText, { start: caret, end: caret })\n },\n\n setComposing(composing): void {\n if (destroyed || isComposing === composing) return\n isComposing = composing\n if (!composing) {\n // Re-run everything suspended during composition (R5/C12): clamp the\n // now-committed text, re-detect triggers, resume drafts.\n if (config.maxLength !== undefined && graphemeLength(value) > config.maxLength) {\n value = clampGraphemes(value, config.maxLength)\n tokens = tokens.filter((t) => t.end <= value.length)\n selection = {\n start: Math.min(selection.start, value.length),\n end: Math.min(selection.end, value.length),\n }\n }\n maybeCommitTypedEmoji()\n recomputeTriggers()\n scheduleDraftWrite()\n } else {\n activeMatch = null\n suggestions.sync(null)\n }\n emit()\n },\n\n applyEnter({ shiftPressed }): EnterResult {\n if (destroyed || disabled || readOnly) return 'noop'\n const menu = suggestions.state\n if (menu.isOpen && !isComposing) {\n // While the menu is open Enter always commits, never submits (D11).\n if (menu.items.length > 0) {\n return commitSuggestion() ? 'committed-suggestion' : 'noop'\n }\n return 'noop'\n }\n if (!shouldSubmitOnEnter({ shiftPressed, isComposing })) {\n // 'newline' asks the adapter to let the native key insert; while\n // composing the IME owns Enter entirely, so report 'noop'.\n return isComposing ? 'noop' : 'newline'\n }\n return performSubmit() ? 'submitted' : 'noop'\n },\n\n moveSuggestionNext(): void {\n suggestions.moveNext()\n emit()\n },\n\n moveSuggestionPrevious(): void {\n suggestions.movePrevious()\n emit()\n },\n\n setSuggestionActiveIndex(index): void {\n suggestions.setActiveIndex(index)\n emit()\n },\n\n applySuggestion(index): void {\n commitSuggestion(index)\n },\n\n dismissSuggestion(): void {\n if (activeMatch) {\n // Escape marks this occurrence dismissed — it will not re-arm until\n // the symbol is deleted and retyped (C9).\n dismissed.push({ triggerId: activeMatch.trigger.id, symbolStart: activeMatch.symbolStart })\n activeMatch = null\n }\n suggestions.close()\n emit()\n },\n\n dismissSuggestionDeferred(): void {\n suggestions.dismissDeferred(() => {\n // Blur-close: no dismissal marking — the next mutation may re-arm.\n activeMatch = null\n emit()\n })\n },\n\n retrySuggestions(): void {\n suggestions.retry()\n emit()\n },\n\n addAttachment(draft): string | null {\n if (destroyed) return null\n if (config.maxAttachments !== undefined && attachments.length >= config.maxAttachments) {\n onEvent({ type: 'attachment-rejected', reason: 'max-attachments', name: draft.name })\n return null\n }\n if (\n config.maxAttachmentSizeBytes !== undefined &&\n draft.sizeBytes !== undefined &&\n draft.sizeBytes > config.maxAttachmentSizeBytes\n ) {\n onEvent({ type: 'attachment-rejected', reason: 'max-size', name: draft.name })\n return null\n }\n if (config.acceptAttachment) {\n const verdict = config.acceptAttachment(draft)\n if (verdict !== true) {\n onEvent({\n type: 'attachment-rejected',\n reason: 'not-accepted',\n name: draft.name,\n ...(typeof verdict === 'string' ? { detail: verdict } : {}),\n })\n return null\n }\n }\n const id = draft.id ?? generateId('rfr-attachment')\n attachments = [...attachments, { ...draft, id, status: draft.status ?? 'pending' }]\n scheduleDraftWrite()\n emit()\n return id\n },\n\n updateAttachment(id, patch): void {\n if (destroyed) return\n attachments = attachments.map((a) => (a.id === id ? { ...a, ...patch, id } : a))\n emit()\n },\n\n removeAttachment(id): void {\n if (destroyed) return\n attachments = attachments.filter((a) => a.id !== id)\n scheduleDraftWrite()\n emit()\n },\n\n setBusy(busy): void {\n if (destroyed || isBusy === busy) return\n isBusy = busy\n emit()\n },\n\n setError(nextError): void {\n if (destroyed) return\n error = nextError\n emit()\n },\n\n setDisabled(nextDisabled): void {\n if (destroyed || disabled === nextDisabled) return\n disabled = nextDisabled\n if (disabled) {\n activeMatch = null\n suggestions.close()\n }\n emit()\n },\n\n setReadOnly(nextReadOnly): void {\n if (destroyed || readOnly === nextReadOnly) return\n readOnly = nextReadOnly\n if (readOnly) {\n activeMatch = null\n suggestions.close()\n }\n emit()\n },\n\n beginEdit({ value: editValue, tokens: editTokens, messageId }): void {\n if (destroyed || disabled) return\n preEditSnapshot = { value, tokens: [...tokens], selection, attachments: [...attachments] }\n mode = 'edit'\n editingMessageId = messageId\n value = editValue\n tokens = [...(editTokens ?? [])]\n selection = { start: value.length, end: value.length }\n activeMatch = null\n dismissed = []\n suggestions.close()\n emit()\n },\n\n cancelEdit(): void {\n if (destroyed || mode !== 'edit') return\n const snapshot = preEditSnapshot\n mode = 'compose'\n editingMessageId = undefined\n preEditSnapshot = null\n if (snapshot) {\n value = snapshot.value\n tokens = snapshot.tokens\n selection = snapshot.selection\n attachments = snapshot.attachments\n }\n activeMatch = null\n suggestions.close()\n emit()\n },\n\n copySelection(): string {\n // Tokens copy as their display text — the value already inlines it.\n return value.slice(selection.start, selection.end)\n },\n\n cutSelection(): string {\n if (destroyed || disabled || readOnly) return ''\n if (selection.start === selection.end) return ''\n const range = expandRangeOverTokens(tokens, selection)\n const text = value.slice(range.start, range.end)\n // Clip shadow: same-instance paste can restore live tokens (E12).\n clipShadow = {\n text,\n tokens: tokens\n .filter((t) => t.start >= range.start && t.end <= range.end)\n .map((t) => ({ ...t, start: t.start - range.start, end: t.end - range.start })),\n }\n const nextText = value.slice(0, range.start) + value.slice(range.end)\n mutateValue(nextText, { start: range.start, end: range.start })\n return text\n },\n\n pasteText(text): void {\n if (destroyed || disabled || readOnly) return\n const shadow = clipShadow\n const range = expandRangeOverTokens(tokens, selection)\n const nextText = value.slice(0, range.start) + text + value.slice(range.end)\n const caret = range.start + text.length\n const before = value\n mutateValue(nextText, { start: caret, end: caret }, { trimEventOnClamp: true })\n // Restore cut tokens only for an exact, untrimmed same-instance paste;\n // pasted look-alike text is never auto-tokenized (E11).\n if (\n shadow &&\n text === shadow.text &&\n value === before.slice(0, range.start) + text + before.slice(range.end)\n ) {\n const restored = shadow.tokens.map((t) => ({\n ...t,\n start: t.start + range.start,\n end: t.end + range.start,\n }))\n tokens = [...tokens, ...restored].sort((a, b) => a.start - b.start)\n emit()\n }\n },\n\n submit(): ComposerSubmission | null {\n if (destroyed) return null\n return performSubmit()\n },\n\n reset(): void {\n if (destroyed) return\n cancelDraftWrite()\n suggestions.close()\n value = config.initialValue ?? ''\n tokens = [...(config.initialTokens ?? [])]\n selection = { start: value.length, end: value.length }\n isComposing = false\n isBusy = false\n error = null\n attachments = []\n mode = 'compose'\n editingMessageId = undefined\n preEditSnapshot = null\n activeMatch = null\n dismissed = []\n clipShadow = null\n lastTypingAt = Number.NEGATIVE_INFINITY\n recomputeTriggers()\n emit()\n },\n\n serialize(): ComposerOutput {\n return serializeTokens(value, tokens)\n },\n\n destroy(): void {\n if (destroyed) return\n cancelDraftWrite()\n suggestions.destroy()\n listeners.clear()\n destroyed = true\n },\n }\n\n // Hydration determinism (§6.7): suggestion.isOpen starts false — the menu\n // only opens on the first user interaction, never at create time.\n return api\n}\n", "/**\n * cva style variants for the composer surface, pill, tray, and menu.\n * Boolean-ish variants use string 'true'/'false' keys per repo convention\n * (raw booleans fail the dts build).\n */\n\nimport { cva } from '@refraction-ui/shared'\n\n/**\n * Outer surface (the rounded card wrapping tray + field + action row).\n * `surface` picks the resting fill: `outlined` (transparent-on-page card) or\n * `filled` (a calm muted fill distinct from the page). Both keep a tasteful\n * focus-visible ring on the pill — a hairline border plus a single-px ring, not\n * a slammed saturated full-perimeter brand border.\n */\nexport const composerSurfaceVariants = cva({\n base: [\n 'overflow-hidden rounded-2xl border shadow-sm',\n 'transition-[box-shadow,border-color,background-color] duration-150 ease-out',\n 'focus-within:ring-1 focus-within:ring-ring focus-within:shadow-md',\n 'motion-reduce:transition-none',\n ].join(' '),\n variants: {\n surface: {\n outlined: 'border-border bg-background focus-within:border-ring',\n filled: 'border-border/50 bg-muted/50 focus-within:border-ring/70 focus-within:bg-background',\n },\n disabled: {\n true: 'pointer-events-none opacity-60',\n false: '',\n },\n error: {\n true: 'border-destructive/50',\n false: '',\n },\n },\n defaultVariants: {\n surface: 'outlined',\n disabled: 'false',\n error: 'false',\n },\n})\n\n/** The text field itself (borderless inside the surface). */\nexport const composerFieldClass =\n 'block w-full resize-none bg-transparent px-3.5 py-3 text-sm placeholder:text-muted-foreground focus:outline-none'\n\n/** Attachment tray above the field, inside the dock. */\nexport const composerTrayClass = 'flex flex-wrap gap-2 px-3 pt-3'\n\n/**\n * A single attachment chip. Chips animate in on add (scale + fade) and, when\n * the adapter marks one `exiting`, animate out faster before unmounting. Both\n * degrade to opacity-only / instant under reduced motion.\n */\nexport const composerAttachmentChipVariants = cva({\n base: [\n 'inline-flex items-center gap-1 rounded-md bg-muted px-2 py-0.5 text-xs',\n 'origin-left transition-all duration-150 ease-out motion-reduce:transition-none',\n ].join(' '),\n variants: {\n status: {\n pending: 'opacity-70',\n uploading: 'opacity-70',\n ready: '',\n error: 'text-destructive',\n },\n exiting: {\n true: 'scale-90 opacity-0 motion-reduce:scale-100',\n false: 'motion-safe:animate-fade-in-scale',\n },\n },\n defaultVariants: {\n status: 'pending',\n exiting: 'false',\n },\n})\n\n/** A committed inline token pill rendered by adapters over the flat string. */\nexport const composerTokenPillClass =\n 'rounded bg-accent/40 px-0.5 text-accent-foreground'\n\n/**\n * The caret-anchored suggestion menu. Enters with an eased fade+scale from its\n * bottom edge (it sits above the field); reduced motion drops the movement.\n */\nexport const composerMenuClass =\n 'z-20 w-72 overflow-hidden rounded-xl border border-border bg-popover shadow-lg origin-bottom motion-safe:animate-fade-in-scale'\n\n/**\n * The inline expression/accessory panel docked below the field. It is part of\n * the composer's own stack (never a floating portal), so the textarea and the\n * message in progress stay fully visible while the panel is open. Opens with an\n * eased fade; reduced motion shows it instantly.\n */\nexport const composerAccessoryPanelClass =\n 'border-t border-border motion-safe:animate-fade-in'\n\n/** The built-in expression-panel toggle button. */\nexport const composerAccessoryToggleVariants = cva({\n base: 'flex h-8 w-8 items-center justify-center rounded-lg transition-colors hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',\n variants: {\n active: {\n true: 'bg-accent text-foreground',\n false: 'text-muted-foreground',\n },\n },\n defaultVariants: {\n active: 'false',\n },\n})\n\n/** A suggestion row; `active` follows keyboard/pointer last-input-wins. */\nexport const composerMenuItemVariants = cva({\n base: 'flex w-full items-center gap-2 px-3 py-2 text-left text-sm',\n variants: {\n active: {\n true: 'bg-accent',\n false: 'hover:bg-accent/50',\n },\n },\n defaultVariants: {\n active: 'false',\n },\n})\n\n/** Character counter; switches to the attention treatment at/over the limit. */\nexport const composerCounterVariants = cva({\n base: 'text-xs tabular-nums',\n variants: {\n overLimit: {\n true: 'font-medium text-destructive',\n false: 'text-muted-foreground',\n },\n },\n defaultVariants: {\n overLimit: 'false',\n },\n})\n\n/**\n * Primary action (send ⇄ stop swap driven by `{hasText, canSend, isBusy}`).\n * The colour/affordance change eases; the adapter keys the button on busy so\n * the swap pops in (scale) rather than snapping. Reduced motion keeps it calm.\n */\nexport const composerPrimaryActionVariants = cva({\n base: 'flex h-9 w-9 items-center justify-center rounded-full transition-all duration-150 ease-out active:scale-95 motion-reduce:transition-none motion-reduce:active:scale-100 motion-safe:animate-scale-in',\n variants: {\n enabled: {\n true: 'bg-primary text-primary-foreground hover:opacity-90',\n false: 'bg-muted text-muted-foreground',\n },\n },\n defaultVariants: {\n enabled: 'false',\n },\n})\n", "/**\n * `createEmojiTrigger` — a ready-made `:` shortcode trigger backed by the shared\n * emoji dataset (`@refraction-ui/emoji-picker`, ~1900 base emoji), so the\n * composer's `:` menu is complete and matches the picker exactly (one source of\n * truth, no divergence). The committed token is the native glyph.\n *\n * This module is side-effect free and imports the dataset lazily via the emoji\n * search API, so it is only pulled into a bundle when `createEmojiTrigger` is\n * actually referenced.\n */\nimport { searchEmojis } from '@refraction-ui/emoji-picker'\nimport type { ComposerCandidate, ComposerTrigger } from './types.js'\n\nexport interface EmojiTriggerOptions {\n /** Trigger id. Default 'emoji'. */\n id?: string\n /** Symbol. Default ':'. */\n symbol?: string\n /** Max results surfaced to the menu. Default 8. */\n maxResults?: number\n /**\n * Minimum query length before resolving (avoids flooding the menu with the\n * whole set on a lone ':'). Default 1.\n */\n minQueryLength?: number\n}\n\n/**\n * Build a `:` emoji trigger. Typing `:fir` filters the full set; committing\n * inserts the glyph (🔥). Pair it with the picker for the same coverage in both\n * surfaces.\n */\nexport function createEmojiTrigger(options: EmojiTriggerOptions = {}): ComposerTrigger {\n const { id = 'emoji', symbol = ':', maxResults = 8, minQueryLength = 1 } = options\n return {\n id,\n symbol,\n // The committed token is the glyph itself, not `:name:`.\n toDisplay: (candidate: ComposerCandidate) =>\n (candidate.metadata?.emoji as string | undefined) ?? candidate.display,\n resolve: (query: string): ComposerCandidate[] => {\n if (query.length < minQueryLength) return []\n return searchEmojis(query, maxResults).map((entry) => ({\n id: `${entry.shortcode}`,\n display: `${entry.emoji} :${entry.shortcode}:`,\n subtitle: entry.name,\n metadata: { emoji: entry.emoji, shortcode: entry.shortcode },\n }))\n },\n }\n}\n", "import * as React from 'react'\nimport { createComposer } from '@refraction-ui/composer'\nimport type { ComposerAPI, ComposerConfig, ComposerState } from '@refraction-ui/composer'\n\nexport interface UseComposerResult {\n /** Live core snapshot (re-renders on change). */\n state: ComposerState\n /** The underlying composer core, for imperative use (beginEdit, addAttachment, …). */\n api: ComposerAPI\n}\n\ninterface ComposerCoreRef {\n api: ComposerAPI\n /** Cached snapshot — `getState()` builds a fresh object per call, which\n * `useSyncExternalStore` would treat as an endless change loop. */\n snapshot: ComposerState\n}\n\n/**\n * useComposer — binds the headless `createComposer` core to React via\n * `useSyncExternalStore`. The core is created once with the config of the\n * first render (the core reads `maxLength`/`maxAttachments`/`validator`/\n * `replyToMessageId`/draft fields lazily, so callers may mutate a stable\n * config object to keep those live). The core is destroyed on unmount and\n * lazily recreated if React StrictMode re-subscribes after that dev-only\n * unmount/remount cycle.\n *\n * @param init Optional one-time initializer invoked right after creation and\n * before the first snapshot (e.g. to seed attachments SSR-deterministically).\n */\nexport function useComposer(\n config: ComposerConfig = {},\n init?: (api: ComposerAPI) => void,\n): UseComposerResult {\n const configRef = React.useRef(config)\n const initRef = React.useRef(init)\n const coreRef = React.useRef<ComposerCoreRef | null>(null)\n\n const getCore = React.useCallback((): ComposerCoreRef => {\n if (coreRef.current === null) {\n const api = createComposer(configRef.current)\n initRef.current?.(api)\n coreRef.current = { api, snapshot: api.getState() }\n }\n return coreRef.current\n }, [])\n\n const subscribe = React.useCallback(\n (onStoreChange: () => void) => {\n const core = getCore()\n return core.api.subscribe((next) => {\n core.snapshot = next\n onStoreChange()\n })\n },\n [getCore],\n )\n\n const getSnapshot = React.useCallback(() => getCore().snapshot, [getCore])\n\n const state = React.useSyncExternalStore(subscribe, getSnapshot, getSnapshot)\n\n React.useEffect(\n () => () => {\n // Cancel core timers on unmount. Nulling the ref lets a StrictMode\n // remount (which re-runs `subscribe`) recreate a live core instead of\n // talking to a destroyed one.\n coreRef.current?.api.destroy()\n coreRef.current = null\n },\n [],\n )\n\n return { state, api: getCore().api }\n}\n", "import * as React from 'react'\nimport {\n composerAccessoryPanelClass,\n composerAccessoryToggleVariants,\n composerAttachmentChipVariants,\n composerCounterVariants,\n composerFieldClass,\n composerMenuClass,\n composerMenuItemVariants,\n composerPrimaryActionVariants,\n composerSurfaceVariants,\n composerTokenPillClass,\n composerTrayClass,\n} from '@refraction-ui/composer'\nimport type {\n ComposerAPI,\n ComposerAttachment,\n ComposerAttachmentDraft,\n ComposerAttachmentKind,\n ComposerCandidate,\n ComposerConfig,\n ComposerDraftStore,\n ComposerEvent,\n ComposerSubmission,\n ComposerTrigger,\n ComposerValidator,\n PlacedToken,\n} from '@refraction-ui/composer'\nimport { cn, generateId, prefersReducedMotion } from '@refraction-ui/shared'\nimport { useComposer } from './use-composer.js'\n\n// ---------------------------------------------------------------------------\n// Layout constants (single source for the measure-free auto-grow math)\n// ---------------------------------------------------------------------------\n\n/** Unitless line-height applied to both the textarea and its mirror. */\nconst FIELD_LINE_HEIGHT = 1.5\n/** Total vertical padding of `composerFieldClass` (`py-3` = 0.75rem × 2). */\nconst FIELD_VERTICAL_PADDING_REM = 1.5\n/** How long the \"paste was trimmed\" notice stays visible. */\nconst TRIMMED_NOTICE_HIDE_MS = 4000\n/** Chip exit animation window before the attachment is actually removed. */\nconst CHIP_EXIT_MS = 150\n\nfunction fieldHeightFor(lines: number): string {\n return `calc(${lines * FIELD_LINE_HEIGHT}em + ${FIELD_VERTICAL_PADDING_REM}rem)`\n}\n\n// ---------------------------------------------------------------------------\n// Strings\n// ---------------------------------------------------------------------------\n\n/** All user-facing text; every entry is overridable via the `strings` prop. */\nexport interface RefractionComposerStrings {\n /** Accessible name of the whole composer landmark. */\n composerLabel: string\n /** Accessible name of the message textarea (independent of the placeholder). */\n messageLabel: string\n sendLabel: string\n stopLabel: string\n attachLabel: string\n removeAttachmentLabel: (name: string) => string\n /** Accessible name of the suggestion listbox. */\n suggestionsLabel: string\n noMatchesLabel: (query: string) => string\n loadingLabel: string\n retryLabel: string\n trimmedNotice: string\n counterLabel: (remaining: number) => string\n editingLabel: string\n cancelEditLabel: string\n /** Accessible name of the expression-panel toggle button. */\n accessoryPanelLabel: string\n /** Live-region announcement while the suggestion menu shows results. */\n resultsAnnouncement: (count: number) => string\n}\n\nexport const DEFAULT_COMPOSER_STRINGS: RefractionComposerStrings = {\n composerLabel: 'Message composer',\n messageLabel: 'Message',\n sendLabel: 'Send',\n stopLabel: 'Stop',\n attachLabel: 'Attach a file',\n removeAttachmentLabel: (name) => `Remove attachment ${name}`,\n suggestionsLabel: 'Suggestions',\n noMatchesLabel: (query) => `No matches for \"${query}\"`,\n loadingLabel: 'Loading suggestions…',\n retryLabel: 'Retry',\n trimmedNotice: 'Pasted text was trimmed to fit the length limit',\n counterLabel: (remaining) => `${remaining} characters remaining`,\n editingLabel: 'Editing message',\n cancelEditLabel: 'Cancel edit',\n accessoryPanelLabel: 'Emoji and stickers',\n resultsAnnouncement: (count) => `${count} suggestions available`,\n}\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface PrimaryActionContext {\n hasText: boolean\n canSend: boolean\n busy: boolean\n}\n\nexport interface SuggestionRenderContext {\n active: boolean\n index: number\n}\n\nexport interface RefractionComposerProps\n extends Omit<\n React.HTMLAttributes<HTMLDivElement>,\n 'onChange' | 'defaultValue' | 'onSubmit' | 'onError' | 'content' | 'placeholder' | 'dir'\n > {\n /** Controlled value. When set, the prop wins over internal edits. */\n value?: string\n /** Initial value for uncontrolled usage. */\n defaultValue?: string\n /** Fired with the new text after every user-driven change (incl. submit clearing). */\n onChange?: (value: string) => void\n placeholder?: string\n /** Distinct placeholder shown while disabled. Falls back to `placeholder`. */\n disabledPlaceholder?: string\n /** Rows shown when empty. Default 1. */\n minLines?: number\n /** Auto-grow ceiling; the field scrolls internally beyond it. Default 6. */\n maxLines?: number\n /** Grapheme-cluster budget (not UTF-16 units). Fixed at mount. */\n maxLength?: number\n maxAttachments?: number\n disabled?: boolean\n readOnly?: boolean\n /** Busy/streaming: swaps the default send button for a stop button. */\n busy?: boolean\n onStop?: () => void\n /**\n * Resting fill of the composer surface. `outlined` (default) is a\n * transparent-on-page card; `filled` uses a calm muted fill distinct from the\n * page. Both keep a tasteful focus-visible ring.\n */\n surface?: 'outlined' | 'filled'\n /**\n * Content for the inline expression panel (e.g. an emoji picker). When set, a\n * toggle button appears in the action row and the panel docks BELOW the field\n * inside the composer's own stack — it never floats over or covers the text\n * being typed. Toggle open/close is animated and reduced-motion aware.\n */\n accessoryPanel?: React.ReactNode\n /** Controlled open state for the accessory panel. */\n accessoryPanelOpen?: boolean\n /** Initial open state for the accessory panel (uncontrolled). Default false. */\n defaultAccessoryPanelOpen?: boolean\n /** Fired when the accessory-panel toggle changes the open state. */\n onAccessoryPanelToggle?: (open: boolean) => void\n autoFocus?: boolean\n dir?: 'ltr' | 'rtl' | 'auto'\n /** Trigger configs (mention '@', slash '/', emoji ':', …). Fixed at mount. */\n triggers?: ComposerTrigger[]\n /**\n * Whether a plain Enter submits. Defaults to true (SSR-deterministic);\n * after mount a coarse pointer (`(pointer: coarse)`) flips the default to\n * false. An explicit prop always wins.\n */\n submitOnEnter?: boolean\n strings?: Partial<RefractionComposerStrings>\n /** Rendered at the start of the action row (e.g. an attach button). */\n leading?: React.ReactNode\n /** Rendered at the end of the action row, before the primary action. */\n trailing?: React.ReactNode\n /** Replaces the built-in send/stop primary action. */\n primaryAction?: (context: PrimaryActionContext) => React.ReactNode\n /** Custom row content for suggestion menu items. */\n renderSuggestion?: (candidate: ComposerCandidate, context: SuggestionRenderContext) => React.ReactNode\n /** Attachments staged at creation (SSR-deterministic, e.g. restored drafts). */\n initialAttachments?: ComposerAttachmentDraft[]\n draftStore?: ComposerDraftStore\n draftKey?: string\n validator?: ComposerValidator\n replyToMessageId?: string\n onSubmit?: (submission: ComposerSubmission) => void\n /** ArrowUp on an empty field (desktop edit-last affordance). */\n onEditLastRequested?: () => void\n /** Escape pressed while in edit mode (after the core restores the draft). */\n onEditCancel?: () => void\n /** Throttled typing signal (core-owned leading-edge throttle). */\n onTypingActivity?: () => void\n onAttachmentAdd?: (attachment: ComposerAttachment) => void\n onAttachmentRejected?: (event: Extract<ComposerEvent, { type: 'attachment-rejected' }>) => void\n /** Raw core notice channel ('paste-trimmed', 'insert-rejected', …). */\n onEvent?: (event: ComposerEvent) => void\n /** Receives the composer core for imperative use (beginEdit, addAttachment, …). */\n apiRef?: React.Ref<ComposerAPI>\n id?: string\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nfunction useLatestRef<T>(value: T): React.RefObject<T> {\n const ref = React.useRef(value)\n ref.current = value\n return ref\n}\n\nfunction kindFromMime(mimeType: string): ComposerAttachmentKind {\n if (mimeType.startsWith('image/')) return 'image'\n if (mimeType.startsWith('video/')) return 'video'\n if (mimeType.startsWith('audio/')) return 'audio'\n return 'file'\n}\n\ninterface ValueSegment {\n key: string\n text: string\n isToken: boolean\n}\n\n/** Split the flat value at committed token ranges for the highlight mirror. */\nfunction segmentValue(value: string, tokens: readonly PlacedToken[]): ValueSegment[] {\n const segments: ValueSegment[] = []\n let cursor = 0\n for (const token of tokens) {\n if (token.start > cursor) {\n segments.push({ key: `p${cursor}`, text: value.slice(cursor, token.start), isToken: false })\n }\n segments.push({ key: `t${token.start}`, text: value.slice(token.start, token.end), isToken: true })\n cursor = token.end\n }\n if (cursor < value.length) {\n segments.push({ key: `p${cursor}`, text: value.slice(cursor), isToken: false })\n }\n return segments\n}\n\nconst iconSvgProps = {\n xmlns: 'http://www.w3.org/2000/svg',\n viewBox: '0 0 24 24',\n width: 16,\n height: 16,\n fill: 'none',\n stroke: 'currentColor',\n strokeWidth: 2,\n strokeLinecap: 'round',\n strokeLinejoin: 'round',\n 'aria-hidden': true,\n} as const\n\nfunction IconArrowUp() {\n return (\n <svg {...iconSvgProps}>\n <path d=\"M12 19V5\" />\n <path d=\"m5 12 7-7 7 7\" />\n </svg>\n )\n}\n\nfunction IconStop() {\n return (\n <svg {...iconSvgProps}>\n <rect x={7} y={7} width={10} height={10} rx={1.5} />\n </svg>\n )\n}\n\nfunction IconSmiley() {\n return (\n <svg {...iconSvgProps}>\n <circle cx={12} cy={12} r={9} />\n <path d=\"M8 14s1.5 2 4 2 4-2 4-2\" />\n <path d=\"M9 9h.01M15 9h.01\" />\n </svg>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\n/**\n * RefractionComposer — the chat message composer.\n *\n * Wraps the headless `@refraction-ui/composer` core: flat value + selection,\n * atomic inline tokens (mention/slash/emoji triggers), attachments, drafts,\n * busy/stop, edit-in-place, and one optimistic submit. `ref` targets the\n * textarea; the core is reachable through `apiRef`.\n */\nexport const RefractionComposer = React.forwardRef<HTMLTextAreaElement, RefractionComposerProps>(\n function RefractionComposer(\n {\n value: valueProp,\n defaultValue,\n onChange,\n placeholder,\n disabledPlaceholder,\n minLines = 1,\n maxLines = 6,\n maxLength,\n maxAttachments,\n disabled = false,\n readOnly = false,\n busy = false,\n onStop,\n surface = 'outlined',\n accessoryPanel,\n accessoryPanelOpen,\n defaultAccessoryPanelOpen = false,\n onAccessoryPanelToggle,\n autoFocus,\n dir,\n triggers,\n submitOnEnter,\n strings: stringsProp,\n leading,\n trailing,\n primaryAction,\n renderSuggestion,\n initialAttachments,\n draftStore,\n draftKey,\n validator,\n replyToMessageId,\n onSubmit,\n onEditLastRequested,\n onEditCancel,\n onTypingActivity,\n onAttachmentAdd,\n onAttachmentRejected,\n onEvent,\n apiRef,\n id,\n className,\n ...rest\n },\n forwardedRef,\n ) {\n const strings = React.useMemo(\n () => ({ ...DEFAULT_COMPOSER_STRINGS, ...stringsProp }),\n [stringsProp],\n )\n\n // Latest-prop refs so the once-captured core config/events see fresh handlers.\n const onSubmitRef = useLatestRef(onSubmit)\n const onChangeRef = useLatestRef(onChange)\n const onStopRef = useLatestRef(onStop)\n const onEventRef = useLatestRef(onEvent)\n const onTypingActivityRef = useLatestRef(onTypingActivity)\n const onAttachmentAddRef = useLatestRef(onAttachmentAdd)\n const onAttachmentRejectedRef = useLatestRef(onAttachmentRejected)\n const onEditLastRequestedRef = useLatestRef(onEditLastRequested)\n const onEditCancelRef = useLatestRef(onEditCancel)\n const onAccessoryPanelToggleRef = useLatestRef(onAccessoryPanelToggle)\n\n const [trimmedNoticeVisible, setTrimmedNoticeVisible] = React.useState(false)\n const trimmedTimerRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)\n\n // Inline expression panel open state (controlled or uncontrolled).\n const [panelOpenInternal, setPanelOpenInternal] = React.useState(defaultAccessoryPanelOpen)\n const panelControlled = accessoryPanelOpen !== undefined\n const panelOpen = panelControlled ? accessoryPanelOpen : panelOpenInternal\n const togglePanel = React.useCallback(() => {\n const next = !(panelControlled ? accessoryPanelOpen : panelOpenInternal)\n if (!panelControlled) setPanelOpenInternal(next)\n onAccessoryPanelToggleRef.current?.(next)\n }, [panelControlled, accessoryPanelOpen, panelOpenInternal, onAccessoryPanelToggleRef])\n\n // Attachment chips animate out before they leave the DOM; track which ids\n // are mid-exit and the timers so we can clean up on unmount.\n const [exitingAttachments, setExitingAttachments] = React.useState<Set<string>>(\n () => new Set(),\n )\n const exitTimersRef = React.useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map())\n\n // One stable config object. The core reads maxLength/maxAttachments/\n // validator/replyToMessageId/draft fields lazily on each operation, so\n // updating the same object keeps them live; triggers and initialValue are\n // fixed at mount by design (the core resolves them once).\n const configRef = React.useRef<ComposerConfig | null>(null)\n if (configRef.current === null) {\n configRef.current = {\n initialValue: valueProp ?? defaultValue ?? '',\n minLines,\n maxLines,\n triggers,\n onEvent: (event) => {\n if (event.type === 'typing') onTypingActivityRef.current?.()\n if (event.type === 'attachment-rejected') onAttachmentRejectedRef.current?.(event)\n if (event.type === 'paste-trimmed') {\n setTrimmedNoticeVisible(true)\n if (trimmedTimerRef.current !== null) clearTimeout(trimmedTimerRef.current)\n trimmedTimerRef.current = setTimeout(() => {\n trimmedTimerRef.current = null\n setTrimmedNoticeVisible(false)\n }, TRIMMED_NOTICE_HIDE_MS)\n }\n onEventRef.current?.(event)\n },\n }\n }\n configRef.current.maxLength = maxLength\n configRef.current.maxAttachments = maxAttachments\n configRef.current.validator = validator\n configRef.current.replyToMessageId = replyToMessageId\n configRef.current.draftStore = draftStore\n configRef.current.draftKey = draftKey\n\n // Seed creation-time state (runs before the first snapshot, so SSR markup\n // already reflects attachments and the disabled/readOnly/busy props).\n const { state, api } = useComposer(configRef.current, (core) => {\n for (const draft of initialAttachments ?? []) core.addAttachment(draft)\n if (disabled) core.setDisabled(true)\n if (readOnly) core.setReadOnly(true)\n if (busy) core.setBusy(true)\n })\n\n React.useEffect(() => {\n const exitTimers = exitTimersRef.current\n return () => {\n if (trimmedTimerRef.current !== null) clearTimeout(trimmedTimerRef.current)\n for (const timer of exitTimers.values()) clearTimeout(timer)\n exitTimers.clear()\n }\n }, [])\n\n // ---- refs / ids -------------------------------------------------------\n\n const textareaRef = React.useRef<HTMLTextAreaElement | null>(null)\n const highlightRef = React.useRef<HTMLDivElement | null>(null)\n const rootRef = React.useRef<HTMLDivElement | null>(null)\n const mergedTextareaRef = React.useCallback(\n (node: HTMLTextAreaElement | null) => {\n textareaRef.current = node\n if (typeof forwardedRef === 'function') forwardedRef(node)\n else if (forwardedRef) forwardedRef.current = node\n },\n [forwardedRef],\n )\n\n const idBase = React.useRef(id ?? generateId('composer')).current\n const listId = `${idBase}-listbox`\n const optionId = (index: number) => `${idBase}-option-${index}`\n\n React.useEffect(() => {\n if (!apiRef) return\n if (typeof apiRef === 'function') {\n apiRef(api)\n return () => {\n apiRef(null)\n }\n }\n const mutableRef = apiRef as React.MutableRefObject<ComposerAPI | null>\n mutableRef.current = api\n return () => {\n mutableRef.current = null\n }\n }, [apiRef, api])\n\n // ---- prop → core sync -------------------------------------------------\n\n React.useEffect(() => {\n api.setDisabled(disabled)\n }, [api, disabled])\n React.useEffect(() => {\n api.setReadOnly(readOnly)\n }, [api, readOnly])\n React.useEffect(() => {\n api.setBusy(busy)\n }, [api, busy])\n\n // Controlled value: the prop wins. Re-runs on internal state changes too,\n // so an edit the parent ignored snaps back to the prop (like a native\n // controlled input, the snap-back itself does not re-fire onChange).\n React.useEffect(() => {\n if (valueProp === undefined) return\n const current = api.getState()\n if (current.disabled || current.readOnly || current.value === valueProp) return\n api.setValue(\n valueProp,\n { start: valueProp.length, end: valueProp.length },\n { programmatic: true },\n )\n }, [api, valueProp, state.value])\n\n // onChange fires synchronously inside the user-event paths (not from an\n // effect) so an echoing controlled parent updates in the same React batch\n // as the edit — an effect-based echo arrives after the controlled\n // snap-back and ping-pongs forever.\n const withValueChange = React.useCallback(\n <T,>(mutate: () => T): T => {\n const before = api.getState().value\n const result = mutate()\n const after = api.getState().value\n if (after !== before) onChangeRef.current?.(after)\n return result\n },\n [api, onChangeRef],\n )\n\n // submitOnEnter default: SSR-deterministic true; coarse pointers flip the\n // default after mount. An explicit prop always wins.\n const [coarsePointer, setCoarsePointer] = React.useState(false)\n React.useEffect(() => {\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return\n const query = window.matchMedia('(pointer: coarse)')\n const update = () => setCoarsePointer(query.matches)\n update()\n query.addEventListener?.('change', update)\n return () => query.removeEventListener?.('change', update)\n }, [])\n const submitEnabled = submitOnEnter ?? !coarsePointer\n\n // The core may correct value/selection after any notify (atomic token\n // delete, clamp, snap): write the corrected selection back to the DOM.\n React.useEffect(() => {\n const el = textareaRef.current\n if (!el) return\n if (el.value !== state.value) el.value = state.value\n if (\n document.activeElement === el &&\n (el.selectionStart !== state.selection.start || el.selectionEnd !== state.selection.end)\n ) {\n el.setSelectionRange(state.selection.start, state.selection.end)\n }\n }, [state.value, state.selection.start, state.selection.end])\n\n // ---- event handlers ----------------------------------------------------\n\n const readSelection = (el: HTMLTextAreaElement) => ({\n start: el.selectionStart ?? el.value.length,\n end: el.selectionEnd ?? el.value.length,\n })\n\n const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {\n const el = event.currentTarget\n withValueChange(() => api.setValue(el.value, readSelection(el)))\n }\n\n const handleSelect = (event: React.SyntheticEvent<HTMLTextAreaElement>) => {\n const el = event.currentTarget\n const next = readSelection(el)\n const current = api.getState().selection\n if (next.start !== current.start || next.end !== current.end) {\n api.setSelection(next)\n }\n }\n\n const submitNow = React.useCallback(() => {\n const submission = withValueChange(() => api.submit())\n if (submission) onSubmitRef.current?.(submission)\n return submission !== null\n }, [api, onSubmitRef, withValueChange])\n\n const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {\n const snapshot = api.getState()\n const menuOpen = snapshot.suggestion.isOpen && !snapshot.isComposing\n const hasMenuItems = snapshot.suggestion.items.length > 0\n\n if (menuOpen) {\n if (event.key === 'ArrowDown' && hasMenuItems) {\n event.preventDefault()\n api.moveSuggestionNext()\n return\n }\n if (event.key === 'ArrowUp' && hasMenuItems) {\n event.preventDefault()\n api.moveSuggestionPrevious()\n return\n }\n if (event.key === 'Tab' && hasMenuItems) {\n event.preventDefault()\n withValueChange(() => api.applySuggestion())\n return\n }\n if (event.key === 'Escape') {\n event.preventDefault()\n api.dismissSuggestion()\n return\n }\n }\n\n if (event.key === 'Escape' && !menuOpen && snapshot.mode === 'edit') {\n event.preventDefault()\n withValueChange(() => api.cancelEdit())\n onEditCancelRef.current?.()\n return\n }\n\n if (\n event.key === 'ArrowUp' &&\n !menuOpen &&\n !snapshot.isComposing &&\n snapshot.value === '' &&\n onEditLastRequestedRef.current\n ) {\n event.preventDefault()\n onEditLastRequestedRef.current()\n return\n }\n\n if (event.key === 'Enter') {\n if (menuOpen) {\n // While the menu is open Enter always commits, never submits —\n // and never inserts a native newline.\n event.preventDefault()\n withValueChange(() => api.applyEnter({ shiftPressed: event.shiftKey }))\n return\n }\n // While composing the IME owns Enter entirely (core reports 'noop').\n if (snapshot.isComposing) return\n // Shift+Enter — and any Enter on touch modality — is a native newline.\n if (event.shiftKey || !submitEnabled) return\n // Guarded submit: whitespace-only/busy/disabled Enter is swallowed\n // (never a newline), matching the core's applyEnter contract.\n event.preventDefault()\n if (snapshot.disabled || snapshot.readOnly) return\n submitNow()\n }\n }\n\n const handleCompositionStart = () => api.setComposing(true)\n const handleCompositionEnd = () => withValueChange(() => api.setComposing(false))\n\n const handleCopy = (event: React.ClipboardEvent<HTMLTextAreaElement>) => {\n const text = api.copySelection()\n if (text === '') return\n event.preventDefault()\n event.clipboardData.setData('text/plain', text)\n }\n\n const handleCut = (event: React.ClipboardEvent<HTMLTextAreaElement>) => {\n event.preventDefault()\n const text = withValueChange(() => api.cutSelection())\n if (text !== '') event.clipboardData.setData('text/plain', text)\n }\n\n const addFiles = React.useCallback(\n (files: ArrayLike<File>) => {\n for (const file of Array.from(files)) {\n const previewUrl =\n typeof URL !== 'undefined' && typeof URL.createObjectURL === 'function'\n ? URL.createObjectURL(file)\n : undefined\n const attachmentId = api.addAttachment({\n kind: kindFromMime(file.type),\n name: file.name,\n mimeType: file.type || undefined,\n sizeBytes: file.size,\n previewUrl,\n })\n if (attachmentId === null) {\n // Rejected by the core (limits/accept) — release the preview URL.\n if (previewUrl !== undefined) URL.revokeObjectURL(previewUrl)\n continue\n }\n const added = api.getState().attachments.find((a) => a.id === attachmentId)\n if (added) onAttachmentAddRef.current?.(added)\n }\n },\n [api, onAttachmentAddRef],\n )\n\n const releasePreview = (previewUrl: string | undefined) => {\n if (\n previewUrl !== undefined &&\n typeof URL !== 'undefined' &&\n typeof URL.revokeObjectURL === 'function'\n ) {\n URL.revokeObjectURL(previewUrl)\n }\n }\n\n // Remove with an exit animation, then drop from core state. Reduced motion\n // (or SSR) removes immediately — no interpolation, still full feedback.\n const removeAttachment = React.useCallback(\n (id: string, previewUrl: string | undefined) => {\n const finish = () => {\n api.removeAttachment(id)\n releasePreview(previewUrl)\n exitTimersRef.current.delete(id)\n setExitingAttachments((prev) => {\n if (!prev.has(id)) return prev\n const next = new Set(prev)\n next.delete(id)\n return next\n })\n }\n if (prefersReducedMotion() || exitTimersRef.current.has(id)) {\n if (!exitTimersRef.current.has(id)) finish()\n return\n }\n setExitingAttachments((prev) => new Set(prev).add(id))\n exitTimersRef.current.set(id, setTimeout(finish, CHIP_EXIT_MS))\n },\n [api],\n )\n\n const handlePaste = (event: React.ClipboardEvent<HTMLTextAreaElement>) => {\n const files = event.clipboardData?.files\n if (files && files.length > 0) {\n // Mixed clipboard: files/images win over text (R23).\n event.preventDefault()\n addFiles(files)\n return\n }\n event.preventDefault()\n withValueChange(() => api.pasteText(event.clipboardData?.getData('text/plain') ?? ''))\n }\n\n const [dragActive, setDragActive] = React.useState(false)\n\n const dragHasPayload = (event: React.DragEvent) => {\n const types = event.dataTransfer ? Array.from(event.dataTransfer.types ?? []) : []\n return types.includes('Files') || types.includes('text/plain')\n }\n\n const handleDragOver = (event: React.DragEvent<HTMLDivElement>) => {\n if (!dragHasPayload(event)) return\n event.preventDefault()\n setDragActive(true)\n }\n\n const handleDragLeave = () => setDragActive(false)\n\n const handleDrop = (event: React.DragEvent<HTMLDivElement>) => {\n event.preventDefault()\n setDragActive(false)\n const transfer = event.dataTransfer\n if (!transfer) return\n if (transfer.files && transfer.files.length > 0) {\n addFiles(transfer.files)\n return\n }\n const text = transfer.getData('text/plain')\n if (text) withValueChange(() => api.pasteText(text))\n }\n\n const handleBlur = () => api.dismissSuggestionDeferred()\n\n const handleScroll = (event: React.UIEvent<HTMLTextAreaElement>) => {\n if (highlightRef.current) {\n highlightRef.current.scrollTop = event.currentTarget.scrollTop\n }\n }\n\n // ---- derived render data ----------------------------------------------\n\n const hasTriggers = (triggers?.length ?? 0) > 0\n const menuOpen = state.suggestion.isOpen\n const visibleItems = state.suggestion.visibleItems\n const activeIndex = state.suggestion.activeIndex\n\n // Space-aware suggestion menu: 'above' is the SSR-stable default; once\n // mounted (and on scroll/resize while open) the menu flips to whichever\n // side of the pill has more viewport room and caps its height to fit.\n const [menuSide, setMenuSide] = React.useState<'above' | 'below'>('above')\n const [menuMaxHeight, setMenuMaxHeight] = React.useState<number | undefined>(undefined)\n React.useLayoutEffect(() => {\n if (!menuOpen) return\n const measure = () => {\n const el = rootRef.current\n if (el === null) return\n const rect = el.getBoundingClientRect()\n const spaceAbove = rect.top\n const spaceBelow = window.innerHeight - rect.bottom\n const side = spaceAbove >= spaceBelow ? 'above' : 'below'\n setMenuSide(side)\n setMenuMaxHeight(Math.max(96, (side === 'above' ? spaceAbove : spaceBelow) - 16))\n }\n measure()\n window.addEventListener('resize', measure)\n window.addEventListener('scroll', measure, true)\n return () => {\n window.removeEventListener('resize', measure)\n window.removeEventListener('scroll', measure, true)\n }\n }, [menuOpen])\n const hasText = state.value.trim().length > 0\n const effectivePlaceholder =\n state.disabled && disabledPlaceholder !== undefined ? disabledPlaceholder : placeholder\n const segments = React.useMemo(\n () => segmentValue(state.value, state.tokens),\n [state.value, state.tokens],\n )\n\n const primaryContext: PrimaryActionContext = {\n hasText,\n canSend: state.canSend,\n busy: state.isBusy,\n }\n\n const defaultPrimaryAction = state.isBusy ? (\n <button\n key=\"stop\"\n type=\"button\"\n aria-label={strings.stopLabel}\n className={composerPrimaryActionVariants({ enabled: 'true' })}\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => onStopRef.current?.()}\n >\n <IconStop />\n </button>\n ) : (\n <button\n key=\"send\"\n type=\"button\"\n aria-label={strings.sendLabel}\n disabled={!state.canSend}\n className={composerPrimaryActionVariants({ enabled: state.canSend ? 'true' : 'false' })}\n onMouseDown={(event) => event.preventDefault()}\n onClick={submitNow}\n >\n <IconArrowUp />\n </button>\n )\n\n const menuBody = () => {\n if (state.suggestion.loading) {\n return (\n <div className=\"px-3 py-2 text-sm text-muted-foreground\">{strings.loadingLabel}</div>\n )\n }\n if (state.suggestion.error !== null) {\n return (\n <div className=\"flex items-center gap-2 px-3 py-2 text-sm text-destructive\">\n <span className=\"flex-1 truncate\">{state.suggestion.error}</span>\n <button\n type=\"button\"\n className=\"font-medium underline\"\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => api.retrySuggestions()}\n >\n {strings.retryLabel}\n </button>\n </div>\n )\n }\n if (visibleItems.length === 0) {\n return (\n <div className=\"px-3 py-2 text-sm text-muted-foreground\">\n {strings.noMatchesLabel(state.activeTrigger?.query ?? '')}\n </div>\n )\n }\n return visibleItems.map((candidate, index) => {\n const active = index === activeIndex\n return (\n <button\n key={candidate.id}\n type=\"button\"\n role=\"option\"\n id={optionId(index)}\n aria-selected={active}\n className={composerMenuItemVariants({ active: active ? 'true' : 'false' })}\n onMouseEnter={() => api.setSuggestionActiveIndex(index)}\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => withValueChange(() => api.applySuggestion(index))}\n >\n {renderSuggestion ? (\n renderSuggestion(candidate, { active, index })\n ) : (\n <>\n <span className=\"flex-1 truncate\">{candidate.display}</span>\n {candidate.subtitle !== undefined && (\n <span className=\"truncate text-xs text-muted-foreground\">\n {candidate.subtitle}\n </span>\n )}\n </>\n )}\n </button>\n )\n })\n }\n\n return (\n <div\n {...rest}\n ref={rootRef}\n id={idBase}\n dir={dir}\n role=\"form\"\n aria-label={strings.composerLabel}\n className={cn('relative', className)}\n onDragOver={handleDragOver}\n onDragLeave={handleDragLeave}\n onDrop={handleDrop}\n >\n {menuOpen && (\n <div\n id={listId}\n role=\"listbox\"\n aria-label={strings.suggestionsLabel}\n className={cn(\n composerMenuClass,\n 'absolute start-0',\n menuSide === 'above' ? 'bottom-full mb-2' : 'top-full mt-2 origin-top',\n )}\n style={\n menuMaxHeight !== undefined\n ? { maxHeight: menuMaxHeight, overflowY: 'auto' }\n : undefined\n }\n >\n {menuBody()}\n </div>\n )}\n\n <div\n data-dragover={dragActive ? '' : undefined}\n className={cn(\n composerSurfaceVariants({\n surface,\n disabled: state.disabled ? 'true' : 'false',\n error: state.error !== null ? 'true' : 'false',\n }),\n dragActive && 'border-primary/60 shadow-md',\n )}\n >\n {state.error !== null && (\n <div\n role=\"alert\"\n className=\"flex items-center gap-2 border-b border-border bg-destructive/5 px-3 py-2 text-xs text-destructive\"\n >\n <span className=\"flex-1 truncate\">{state.error}</span>\n </div>\n )}\n\n {state.mode === 'edit' && (\n <div className=\"flex items-center gap-2 border-b border-border px-3 py-2 text-xs text-muted-foreground\">\n <span className=\"flex-1 truncate\">{strings.editingLabel}</span>\n <button\n type=\"button\"\n className=\"font-medium underline\"\n onClick={() => {\n withValueChange(() => api.cancelEdit())\n onEditCancelRef.current?.()\n }}\n >\n {strings.cancelEditLabel}\n </button>\n </div>\n )}\n\n {trimmedNoticeVisible && (\n <div role=\"status\" className=\"px-3 pt-2 text-xs text-muted-foreground\">\n {strings.trimmedNotice}\n </div>\n )}\n\n {state.attachments.length > 0 && (\n <div className={composerTrayClass}>\n {state.attachments.map((attachment) => (\n <span\n key={attachment.id}\n className={composerAttachmentChipVariants({\n status: attachment.status,\n exiting: exitingAttachments.has(attachment.id) ? 'true' : 'false',\n })}\n >\n {attachment.name}\n <button\n type=\"button\"\n aria-label={strings.removeAttachmentLabel(attachment.name)}\n className=\"text-muted-foreground hover:text-destructive\"\n onClick={() => removeAttachment(attachment.id, attachment.previewUrl)}\n >\n ✕\n </button>\n </span>\n ))}\n </div>\n )}\n\n <div className=\"relative\">\n {/* Height driver + token highlight mirror: an in-flow copy of the\n value gives measure-free auto-grow (its height IS the content\n height between the min/max rails); token spans paint the pill\n background while the transparent text keeps glyph metrics\n identical to the textarea stacked above it. */}\n <div\n ref={highlightRef}\n aria-hidden=\"true\"\n className={cn(\n composerFieldClass,\n 'pointer-events-none overflow-hidden whitespace-pre-wrap break-words text-transparent',\n )}\n style={{\n lineHeight: FIELD_LINE_HEIGHT,\n minHeight: fieldHeightFor(minLines),\n maxHeight: fieldHeightFor(maxLines),\n }}\n >\n {segments.map((segment) =>\n segment.isToken ? (\n <span\n key={segment.key}\n data-token=\"\"\n className={cn(composerTokenPillClass, 'px-0 text-transparent')}\n >\n <bdi>{segment.text}</bdi>\n </span>\n ) : (\n <span key={segment.key}>{segment.text}</span>\n ),\n )}\n {'\\u200b'}\n </div>\n <textarea\n ref={mergedTextareaRef}\n id={`${idBase}-input`}\n rows={minLines}\n value={state.value}\n placeholder={effectivePlaceholder}\n disabled={state.disabled}\n readOnly={state.readOnly}\n autoFocus={autoFocus}\n aria-multiline=\"true\"\n aria-label={strings.messageLabel}\n {...(hasTriggers\n ? {\n 'aria-autocomplete': 'list' as const,\n 'aria-expanded': menuOpen,\n 'aria-controls': listId,\n 'aria-activedescendant':\n menuOpen && visibleItems.length > 0 ? optionId(activeIndex) : undefined,\n }\n : {})}\n className={cn(\n composerFieldClass,\n 'absolute inset-0 h-full overflow-y-auto focus-visible:outline-none',\n )}\n style={{ lineHeight: FIELD_LINE_HEIGHT, maxHeight: fieldHeightFor(maxLines) }}\n onChange={handleChange}\n onSelect={handleSelect}\n onKeyDown={handleKeyDown}\n onCompositionStart={handleCompositionStart}\n onCompositionEnd={handleCompositionEnd}\n onCopy={handleCopy}\n onCut={handleCut}\n onPaste={handlePaste}\n onBlur={handleBlur}\n onScroll={handleScroll}\n />\n </div>\n\n <div className=\"flex items-center gap-0.5 px-2 pb-2\">\n {accessoryPanel !== undefined && (\n <button\n type=\"button\"\n aria-label={strings.accessoryPanelLabel}\n aria-expanded={panelOpen}\n aria-controls={`${idBase}-accessory`}\n className={composerAccessoryToggleVariants({ active: panelOpen ? 'true' : 'false' })}\n onMouseDown={(event) => event.preventDefault()}\n onClick={togglePanel}\n >\n <IconSmiley />\n </button>\n )}\n {leading}\n <div className=\"flex-1\" />\n {state.counter.visible && state.counter.remaining !== null && (\n <div\n aria-live={state.counter.overLimit ? 'polite' : undefined}\n className={cn(\n composerCounterVariants({\n overLimit: state.counter.overLimit ? 'true' : 'false',\n }),\n 'me-2',\n )}\n >\n {strings.counterLabel(state.counter.remaining)}\n </div>\n )}\n {trailing}\n {primaryAction ? primaryAction(primaryContext) : defaultPrimaryAction}\n </div>\n\n {/* Inline expression panel — docked in the composer's own stack so the\n field and the message in progress stay fully visible. Never a\n floating portal over the text. */}\n {accessoryPanel !== undefined && panelOpen && (\n <div id={`${idBase}-accessory`} className={composerAccessoryPanelClass}>\n {accessoryPanel}\n </div>\n )}\n </div>\n\n <div aria-live=\"polite\" className=\"sr-only\">\n {menuOpen && !state.suggestion.loading && state.suggestion.error === null\n ? visibleItems.length === 0\n ? strings.noMatchesLabel(state.activeTrigger?.query ?? '')\n : strings.resultsAnnouncement(visibleItems.length)\n : ''}\n </div>\n </div>\n )\n },\n)\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["count", "window", "sliceStart", "caret", "generateId", "sharedGenerateId", "result", "React", "RefractionComposer", "menuOpen", "id"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cn,
|
|
3
|
+
cva
|
|
4
|
+
} from "./chunk-ZF3OHCGV.js";
|
|
5
|
+
|
|
6
|
+
// ../react-content-protection/dist/index.js
|
|
7
|
+
import * as React from "react";
|
|
8
|
+
|
|
9
|
+
// ../content-protection/dist/index.js
|
|
10
|
+
function createContentProtection(props = {}) {
|
|
11
|
+
const {
|
|
12
|
+
enabled = true,
|
|
13
|
+
disableCopy = true,
|
|
14
|
+
disableContextMenu = true,
|
|
15
|
+
watermarkText
|
|
16
|
+
} = props;
|
|
17
|
+
const eventHandlers = {};
|
|
18
|
+
if (enabled) {
|
|
19
|
+
if (disableCopy) {
|
|
20
|
+
const prevent = (e) => e.preventDefault();
|
|
21
|
+
eventHandlers.onCopy = prevent;
|
|
22
|
+
eventHandlers.onCut = prevent;
|
|
23
|
+
eventHandlers.onSelectStart = prevent;
|
|
24
|
+
}
|
|
25
|
+
if (disableContextMenu) {
|
|
26
|
+
eventHandlers.onContextMenu = (e) => e.preventDefault();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const watermarkConfig = watermarkText ? { text: watermarkText, opacity: 0.08, angle: -45 } : null;
|
|
30
|
+
const dataAttributes = {};
|
|
31
|
+
if (enabled) {
|
|
32
|
+
dataAttributes["data-protected"] = "true";
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
eventHandlers,
|
|
36
|
+
watermarkConfig,
|
|
37
|
+
dataAttributes
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
var contentProtectionVariants = cva({
|
|
41
|
+
base: "relative select-none"
|
|
42
|
+
});
|
|
43
|
+
var watermarkVariants = cva({
|
|
44
|
+
base: "pointer-events-none absolute inset-0 z-50 overflow-hidden"
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// ../react-content-protection/dist/index.js
|
|
48
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
49
|
+
var ContentProtection = React.forwardRef(
|
|
50
|
+
function ContentProtection2({
|
|
51
|
+
enabled,
|
|
52
|
+
disableCopy,
|
|
53
|
+
disableContextMenu,
|
|
54
|
+
watermarkText,
|
|
55
|
+
className,
|
|
56
|
+
children,
|
|
57
|
+
...props
|
|
58
|
+
}, ref) {
|
|
59
|
+
const api = createContentProtection({
|
|
60
|
+
enabled,
|
|
61
|
+
disableCopy,
|
|
62
|
+
disableContextMenu,
|
|
63
|
+
watermarkText
|
|
64
|
+
});
|
|
65
|
+
const classes = cn(contentProtectionVariants(), className);
|
|
66
|
+
return /* @__PURE__ */ jsxs(
|
|
67
|
+
"div",
|
|
68
|
+
{
|
|
69
|
+
ref,
|
|
70
|
+
className: classes,
|
|
71
|
+
...api.dataAttributes,
|
|
72
|
+
onCopy: api.eventHandlers.onCopy,
|
|
73
|
+
onCut: api.eventHandlers.onCut,
|
|
74
|
+
onContextMenu: api.eventHandlers.onContextMenu,
|
|
75
|
+
onSelect: api.eventHandlers.onSelectStart,
|
|
76
|
+
...props,
|
|
77
|
+
children: [
|
|
78
|
+
children,
|
|
79
|
+
api.watermarkConfig && /* @__PURE__ */ jsx(
|
|
80
|
+
"div",
|
|
81
|
+
{
|
|
82
|
+
className: watermarkVariants(),
|
|
83
|
+
"aria-hidden": "true",
|
|
84
|
+
style: {
|
|
85
|
+
opacity: api.watermarkConfig.opacity,
|
|
86
|
+
transform: `rotate(${api.watermarkConfig.angle}deg)`,
|
|
87
|
+
backgroundImage: `repeating-linear-gradient(
|
|
88
|
+
${api.watermarkConfig.angle}deg,
|
|
89
|
+
transparent,
|
|
90
|
+
transparent 80px,
|
|
91
|
+
currentColor 80px,
|
|
92
|
+
currentColor 80px
|
|
93
|
+
)`
|
|
94
|
+
},
|
|
95
|
+
children: /* @__PURE__ */ jsxs("svg", { width: "100%", height: "100%", children: [
|
|
96
|
+
/* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx(
|
|
97
|
+
"pattern",
|
|
98
|
+
{
|
|
99
|
+
id: "rfr-watermark",
|
|
100
|
+
x: "0",
|
|
101
|
+
y: "0",
|
|
102
|
+
width: "200",
|
|
103
|
+
height: "200",
|
|
104
|
+
patternUnits: "userSpaceOnUse",
|
|
105
|
+
patternTransform: `rotate(${api.watermarkConfig.angle})`,
|
|
106
|
+
children: /* @__PURE__ */ jsx(
|
|
107
|
+
"text",
|
|
108
|
+
{
|
|
109
|
+
x: "0",
|
|
110
|
+
y: "100",
|
|
111
|
+
fill: "currentColor",
|
|
112
|
+
fontSize: "16",
|
|
113
|
+
fontFamily: "sans-serif",
|
|
114
|
+
children: api.watermarkConfig.text
|
|
115
|
+
}
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
) }),
|
|
119
|
+
/* @__PURE__ */ jsx("rect", { width: "100%", height: "100%", fill: "url(#rfr-watermark)" })
|
|
120
|
+
] })
|
|
121
|
+
}
|
|
122
|
+
)
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
);
|
|
128
|
+
export {
|
|
129
|
+
ContentProtection,
|
|
130
|
+
contentProtectionVariants,
|
|
131
|
+
watermarkVariants
|
|
132
|
+
};
|
|
133
|
+
//# sourceMappingURL=react-content-protection.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../content-protection/src/content-protection.ts", "../../content-protection/src/content-protection.styles.ts", "../../react-content-protection/src/content-protection.tsx"],
|
|
4
|
+
"sourcesContent": ["export interface ContentProtectionProps {\n /** Enable content protection. Default: true */\n enabled?: boolean\n /** Disable copy/cut. Default: true */\n disableCopy?: boolean\n /** Disable right-click context menu. Default: true */\n disableContextMenu?: boolean\n /** Watermark text overlay */\n watermarkText?: string\n}\n\nexport interface WatermarkConfig {\n text: string\n opacity: number\n angle: number\n}\n\nexport interface ContentProtectionAPI {\n eventHandlers: {\n onCopy?: (e: { preventDefault(): void }) => void\n onCut?: (e: { preventDefault(): void }) => void\n onContextMenu?: (e: { preventDefault(): void }) => void\n onSelectStart?: (e: { preventDefault(): void }) => void\n }\n watermarkConfig: WatermarkConfig | null\n dataAttributes: Record<string, string>\n}\n\nexport function createContentProtection(\n props: ContentProtectionProps = {},\n): ContentProtectionAPI {\n const {\n enabled = true,\n disableCopy = true,\n disableContextMenu = true,\n watermarkText,\n } = props\n\n const eventHandlers: ContentProtectionAPI['eventHandlers'] = {}\n\n if (enabled) {\n if (disableCopy) {\n const prevent = (e: { preventDefault(): void }) => e.preventDefault()\n eventHandlers.onCopy = prevent\n eventHandlers.onCut = prevent\n eventHandlers.onSelectStart = prevent\n }\n if (disableContextMenu) {\n eventHandlers.onContextMenu = (e: { preventDefault(): void }) =>\n e.preventDefault()\n }\n }\n\n const watermarkConfig: WatermarkConfig | null = watermarkText\n ? { text: watermarkText, opacity: 0.08, angle: -45 }\n : null\n\n const dataAttributes: Record<string, string> = {}\n if (enabled) {\n dataAttributes['data-protected'] = 'true'\n }\n\n return {\n eventHandlers,\n watermarkConfig,\n dataAttributes,\n }\n}\n", "import { cva } from '@refraction-ui/shared'\n\nexport const contentProtectionVariants = cva({\n base: 'relative select-none',\n})\n\nexport const watermarkVariants = cva({\n base: 'pointer-events-none absolute inset-0 z-50 overflow-hidden',\n})\n", "import * as React from 'react'\nimport {\n createContentProtection,\n contentProtectionVariants,\n watermarkVariants,\n type ContentProtectionProps as CoreContentProtectionProps,\n} from '@refraction-ui/content-protection'\nimport { cn } from '@refraction-ui/shared'\n\nexport interface ContentProtectionProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Enable content protection. Default: true */\n enabled?: boolean\n /** Disable copy/cut. Default: true */\n disableCopy?: boolean\n /** Disable right-click context menu. Default: true */\n disableContextMenu?: boolean\n /** Watermark text overlay */\n watermarkText?: string\n}\n\n/**\n * ContentProtection component — wraps children to block copy/context menu\n * and optionally renders a watermark overlay.\n *\n * Uses the headless @refraction-ui/content-protection core for event handlers.\n */\nexport const ContentProtection = React.forwardRef<HTMLDivElement, ContentProtectionProps>(\n function ContentProtection(\n {\n enabled,\n disableCopy,\n disableContextMenu,\n watermarkText,\n className,\n children,\n ...props\n },\n ref,\n ) {\n const api = createContentProtection({\n enabled,\n disableCopy,\n disableContextMenu,\n watermarkText,\n })\n\n const classes = cn(contentProtectionVariants(), className)\n\n return (\n <div\n ref={ref}\n className={classes}\n {...api.dataAttributes}\n onCopy={api.eventHandlers.onCopy as React.ClipboardEventHandler}\n onCut={api.eventHandlers.onCut as React.ClipboardEventHandler}\n onContextMenu={api.eventHandlers.onContextMenu as React.MouseEventHandler}\n onSelect={api.eventHandlers.onSelectStart as React.ReactEventHandler}\n {...props}\n >\n {children}\n {api.watermarkConfig && (\n <div\n className={watermarkVariants()}\n aria-hidden=\"true\"\n style={{\n opacity: api.watermarkConfig.opacity,\n transform: `rotate(${api.watermarkConfig.angle}deg)`,\n backgroundImage: `repeating-linear-gradient(\n ${api.watermarkConfig.angle}deg,\n transparent,\n transparent 80px,\n currentColor 80px,\n currentColor 80px\n )`,\n }}\n >\n <svg width=\"100%\" height=\"100%\">\n <defs>\n <pattern\n id=\"rfr-watermark\"\n x=\"0\"\n y=\"0\"\n width=\"200\"\n height=\"200\"\n patternUnits=\"userSpaceOnUse\"\n patternTransform={`rotate(${api.watermarkConfig.angle})`}\n >\n <text\n x=\"0\"\n y=\"100\"\n fill=\"currentColor\"\n fontSize=\"16\"\n fontFamily=\"sans-serif\"\n >\n {api.watermarkConfig.text}\n </text>\n </pattern>\n </defs>\n <rect width=\"100%\" height=\"100%\" fill=\"url(#rfr-watermark)\" />\n </svg>\n </div>\n )}\n </div>\n )\n },\n)\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;AA4BO,SAAS,wBACd,QAAgC,CAAA,GACV;AACtB,QAAM;IACJ,UAAU;IACV,cAAc;IACd,qBAAqB;IACrB;EAAA,IACE;AAEJ,QAAM,gBAAuD,CAAA;AAE7D,MAAI,SAAS;AACX,QAAI,aAAa;AACf,YAAM,UAAU,CAAC,MAAkC,EAAE,eAAA;AACrD,oBAAc,SAAS;AACvB,oBAAc,QAAQ;AACtB,oBAAc,gBAAgB;IAChC;AACA,QAAI,oBAAoB;AACtB,oBAAc,gBAAgB,CAAC,MAC7B,EAAE,eAAA;IACN;EACF;AAEA,QAAM,kBAA0C,gBAC5C,EAAE,MAAM,eAAe,SAAS,MAAM,OAAO,IAAA,IAC7C;AAEJ,QAAM,iBAAyC,CAAA;AAC/C,MAAI,SAAS;AACX,mBAAe,gBAAgB,IAAI;EACrC;AAEA,SAAO;IACL;IACA;IACA;EAAA;AAEJ;ACjEO,IAAM,4BAA4B,IAAI;EAC3C,MAAM;AACR,CAAC;AAEM,IAAM,oBAAoB,IAAI;EACnC,MAAM;AACR,CAAC;;;;ACkBM,IAAM,oBAA0B;EACrC,SAASA,mBACP;IACE;IACA;IACA;IACA;IACA;IACA;IACA,GAAG;EAAA,GAEL,KACA;AACA,UAAM,MAAM,wBAAwB;MAClC;MACA;MACA;MACA;IAAA,CACD;AAED,UAAM,UAAU,GAAG,0BAAA,GAA6B,SAAS;AAEzD,WACE;MAAC;MAAA;QACC;QACA,WAAW;QACV,GAAG,IAAI;QACR,QAAQ,IAAI,cAAc;QAC1B,OAAO,IAAI,cAAc;QACzB,eAAe,IAAI,cAAc;QACjC,UAAU,IAAI,cAAc;QAC3B,GAAG;QAEH,UAAA;UAAA;UACA,IAAI,mBACH;YAAC;YAAA;cACC,WAAW,kBAAA;cACX,eAAY;cACZ,OAAO;gBACL,SAAS,IAAI,gBAAgB;gBAC7B,WAAW,UAAU,IAAI,gBAAgB,KAAK;gBAC9C,iBAAiB;kBACb,IAAI,gBAAgB,KAAK;;;;;;cAAA;cAQ/B,UAAA,qBAAC,OAAA,EAAI,OAAM,QAAO,QAAO,QACvB,UAAA;gBAAA,oBAAC,QAAA,EACC,UAAA;kBAAC;kBAAA;oBACC,IAAG;oBACH,GAAE;oBACF,GAAE;oBACF,OAAM;oBACN,QAAO;oBACP,cAAa;oBACb,kBAAkB,UAAU,IAAI,gBAAgB,KAAK;oBAErD,UAAA;sBAAC;sBAAA;wBACC,GAAE;wBACF,GAAE;wBACF,MAAK;wBACL,UAAS;wBACT,YAAW;wBAEV,UAAA,IAAI,gBAAgB;sBAAA;oBAAA;kBACvB;gBAAA,EACF,CACF;gBAAA,oBACC,QAAA,EAAK,OAAM,QAAO,QAAO,QAAO,MAAK,sBAAA,CAAsB;cAAA,EAAA,CAC9D;YAAA;UAAA;QACF;MAAA;IAAA;EAIR;AACF;",
|
|
6
|
+
"names": ["ContentProtection"]
|
|
7
|
+
}
|