@ai-matrx/design-system 0.86.328 → 0.86.330

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.
@@ -55,9 +55,7 @@ import "./chunk-MOZRIHJF.js";
55
55
  import "./chunk-57OD2WTY.js";
56
56
  import "./chunk-BNXNIN2S.js";
57
57
  import "./chunk-XREKZXAL.js";
58
- import {
59
- Button
60
- } from "./chunk-7IOSA55D.js";
58
+ import "./chunk-7IOSA55D.js";
61
59
  import "./chunk-WAT2XW7L.js";
62
60
  import "./chunk-FXXSZNER.js";
63
61
  import "./chunk-DGNHZBJY.js";
@@ -72,7 +70,6 @@ import {
72
70
  Loader2Icon,
73
71
  Maximize2Icon,
74
72
  MicIcon,
75
- RotateCcwIcon,
76
73
  SendIcon,
77
74
  SlidersHorizontalIcon,
78
75
  XIcon
@@ -192,83 +189,7 @@ function clusterPlacementProblems(p) {
192
189
 
193
190
  // src/writing-field/add-ons.tsx
194
191
  import * as React from "react";
195
-
196
- // src/writing-field/voice-chrome.tsx
197
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
198
- function ListeningStrip({ liveTranscript, className }) {
199
- return /* @__PURE__ */ jsxs("div", { className: cn("absolute flex items-center gap-1.5 px-2 py-1 rounded-md", className), children: [
200
- /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "w-2 h-2 bg-primary rounded-full flex-shrink-0 animate-pulse" }),
201
- /* @__PURE__ */ jsx("span", { className: "type-secondary text-primary font-medium truncate", children: liveTranscript ? liveTranscript.slice(-60) : "Listening..." })
202
- ] });
203
- }
204
- function FinalizingStrip({ className }) {
205
- return /* @__PURE__ */ jsxs(
206
- "div",
207
- {
208
- className: cn(
209
- "absolute flex items-center gap-1.5 px-2 py-1 rounded-md bg-blue-100 dark:bg-blue-900/30",
210
- className
211
- ),
212
- children: [
213
- /* @__PURE__ */ jsx(Loader2Icon, { className: "w-3 h-3 animate-spin text-blue-600 dark:text-blue-400" }),
214
- /* @__PURE__ */ jsx("span", { className: "type-secondary text-blue-600 dark:text-blue-400 font-medium", children: "Finalizing..." })
215
- ]
216
- }
217
- );
218
- }
219
- function VoiceProtectionDialog({ voice }) {
220
- const { isRecording, isTranscribing } = voice;
221
- const busy = isRecording || isTranscribing;
222
- return /* @__PURE__ */ jsx(AlertDialog, { open: voice.showProtectionDialog, onOpenChange: voice.setShowProtectionDialog, children: /* @__PURE__ */ jsxs(AlertDialogContent, { children: [
223
- /* @__PURE__ */ jsxs(AlertDialogHeader, { children: [
224
- /* @__PURE__ */ jsx("div", { className: "flex items-center gap-3 mb-2", children: busy ? /* @__PURE__ */ jsxs(Fragment, { children: [
225
- /* @__PURE__ */ jsx(Loader2Icon, { className: "h-5 w-5 animate-spin text-blue-500" }),
226
- /* @__PURE__ */ jsx(AlertDialogTitle, { children: isRecording ? "Recording in Progress" : "Transcription in Progress" })
227
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
228
- /* @__PURE__ */ jsx(CheckIcon, { className: "h-5 w-5 text-green-500" }),
229
- /* @__PURE__ */ jsx(AlertDialogTitle, { children: "Voice Input Complete" })
230
- ] }) }),
231
- /* @__PURE__ */ jsx(AlertDialogDescription, { children: isRecording ? "Your voice is currently being recorded. If you close now, the recording will be stopped and lost." : isTranscribing ? "Your voice recording is currently being transcribed. If you close now, the transcription will be lost." : "Your voice input has been processed successfully! You can now safely close this panel." })
232
- ] }),
233
- /* @__PURE__ */ jsx(AlertDialogFooter, { children: busy ? /* @__PURE__ */ jsxs(Fragment, { children: [
234
- /* @__PURE__ */ jsx(AlertDialogCancel, { onClick: voice.cancelClose, children: "Cancel & Wait" }),
235
- /* @__PURE__ */ jsx(
236
- AlertDialogAction,
237
- {
238
- onClick: voice.confirmClose,
239
- className: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
240
- children: isRecording ? "Stop Recording" : "End Transcription"
241
- }
242
- )
243
- ] }) : /* @__PURE__ */ jsx(AlertDialogAction, { onClick: voice.confirmClose, children: "Close" }) })
244
- ] }) });
245
- }
246
- function FieldMenuRow({
247
- icon,
248
- children,
249
- onClick,
250
- disabled
251
- }) {
252
- return /* @__PURE__ */ jsx(
253
- Tile,
254
- {
255
- type: "button",
256
- disabled,
257
- onClick,
258
- variant: "quiet",
259
- className: "w-full",
260
- icon,
261
- title: children
262
- }
263
- );
264
- }
265
- function micAriaLabel(isRecording, label) {
266
- if (isRecording) return label ? `Stop recording for ${label}` : "Stop recording";
267
- return label ? `Start voice input for ${label}` : "Start voice input";
268
- }
269
-
270
- // src/writing-field/add-ons.tsx
271
- import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
192
+ import { jsx } from "react/jsx-runtime";
272
193
  var ADD_ON_PROP_NAMES = [
273
194
  "surfaceName",
274
195
  "getApplicationScope",
@@ -296,87 +217,25 @@ function splitAddOnProps(props, extra = []) {
296
217
  }
297
218
  return { dom, addOnProps };
298
219
  }
299
- var KEY = /* @__PURE__ */ Symbol.for("@ai-matrx/design-system/writing-field-door");
220
+ var KEY = /* @__PURE__ */ Symbol.for("@ai-matrx/design-system/writing-field-add-ons");
300
221
  var registry = globalThis;
301
- var DoorContext = registry[KEY] ?? (registry[KEY] = React.createContext(null));
302
- var EMPTY = {};
303
- var NO_LIGHT = {};
222
+ var AddOnsContext = registry[KEY] ?? (registry[KEY] = React.createContext(null));
304
223
  function WritingFieldAddOnsProvider({
305
224
  addOns,
306
- load,
307
- light = NO_LIGHT,
308
225
  children
309
226
  }) {
310
227
  const first = React.useRef(addOns);
311
- if (addOns && first.current !== addOns) {
228
+ if (first.current !== addOns) {
312
229
  announceStandIn(
313
230
  "writing-field-add-ons-swap",
314
231
  "WritingFieldAddOnsProvider received a different addOns object; the first one is kept (hook ports must be module-constant)."
315
232
  );
316
233
  }
317
- const loader = React.useRef(load);
318
- const [loaded, setLoaded] = React.useState(first.current ?? null);
319
- const [failed, setFailed] = React.useState(false);
320
- const inflight = React.useRef(null);
321
- const engage = React.useCallback(() => {
322
- if (first.current) return Promise.resolve(first.current);
323
- if (inflight.current) return inflight.current;
324
- const run = loader.current;
325
- if (!run) return Promise.resolve(null);
326
- setFailed(false);
327
- inflight.current = run().then(
328
- (next) => {
329
- setLoaded(next);
330
- return next;
331
- },
332
- (error) => {
333
- inflight.current = null;
334
- setFailed(true);
335
- console.error("[writing-field] the writing-field add-ons failed to load; the next press retries.", error);
336
- return null;
337
- }
338
- );
339
- return inflight.current;
340
- }, []);
341
- const pendingSet = React.useMemo(
342
- () => ({
343
- MicButton: PendingMicButton,
344
- Menu: PendingMenu,
345
- menuHasItems: light.menuHasItems ?? (() => true),
346
- TextStats: light.TextStats,
347
- propNames: light.propNames,
348
- notify: {
349
- error: (message, options) => void engage().then((a) => (a?.notify ?? STAND_IN_NOTIFY).error(message, options)),
350
- info: (message) => void engage().then((a) => (a?.notify ?? STAND_IN_NOTIFY).info(message)),
351
- success: (message) => void engage().then((a) => (a?.notify ?? STAND_IN_NOTIFY).success(message))
352
- }
353
- }),
354
- [light, engage]
355
- );
356
- const pending = !loaded && !!loader.current;
357
- const door = React.useMemo(
358
- () => ({ addOns: loaded, pending, failed, light, engage, effective: loaded ?? (pending ? pendingSet : EMPTY) }),
359
- [loaded, pending, failed, light, engage, pendingSet]
360
- );
361
- return /* @__PURE__ */ jsx2(DoorContext.Provider, { value: door, children });
362
- }
363
- var NO_DOOR = {
364
- addOns: null,
365
- pending: false,
366
- failed: false,
367
- light: NO_LIGHT,
368
- engage: () => Promise.resolve(null),
369
- effective: EMPTY
370
- };
371
- function useDoor() {
372
- return React.useContext(DoorContext) ?? NO_DOOR;
234
+ return /* @__PURE__ */ jsx(AddOnsContext.Provider, { value: first.current, children });
373
235
  }
236
+ var EMPTY = {};
374
237
  function useWritingFieldAddOns() {
375
- return useDoor().effective;
376
- }
377
- function useWritingFieldDoor() {
378
- const door = useDoor();
379
- return { pending: door.pending, failed: door.failed, engage: door.engage };
238
+ return React.useContext(AddOnsContext) ?? EMPTY;
380
239
  }
381
240
  function announceMissingPort(port) {
382
241
  announceStandIn(
@@ -433,188 +292,83 @@ function resolveHookPorts(addOns) {
433
292
  notify: addOns.notify ?? STAND_IN_NOTIFY
434
293
  };
435
294
  }
436
- var SNAPSHOT_KEYS = [
437
- "microphone",
438
- "available",
439
- "isRecording",
440
- "isTranscribing",
441
- "audioLevel",
442
- "liveTranscript",
443
- "showProtectionDialog"
444
- ];
445
- var PENDING_SNAPSHOT = {
446
- microphone: false,
447
- available: true,
448
- isRecording: false,
449
- isTranscribing: false,
450
- audioLevel: 0,
451
- liveTranscript: "",
452
- showProtectionDialog: false
453
- };
454
- function useFieldPorts(addOns) {
455
- const door = useDoor();
456
- const live = !door.pending;
457
- const shared = React.useRef({ voice: null, openAudioSettings: null, queue: [], options: null, drain: null }).current;
458
- const [snapshot, setSnapshot] = React.useState(null);
459
- const [micApi, setMicApi] = React.useState(false);
460
- React.useEffect(() => {
461
- if (!live) setMicApi(typeof navigator !== "undefined" && !!navigator.mediaDevices?.getUserMedia);
462
- }, [live]);
463
- const fns = React.useState(() => {
464
- const run = (fn, fail) => {
465
- if (shared.voice && shared.openAudioSettings) {
466
- fn(shared.voice, shared.openAudioSettings);
467
- return;
468
- }
469
- shared.queue.push({ fn, fail });
470
- void door.engage().then((addOnsNow) => {
471
- if (addOnsNow) setTimeout(() => shared.drain?.(), 0);
472
- else {
473
- const dropped = shared.queue.splice(0);
474
- for (const q of dropped) q.fail?.(new Error("The writing field's add-ons did not load."));
475
- }
476
- });
477
- };
478
- return {
479
- run,
480
- handleVoiceClick: () => run((v) => void v.handleVoiceClick()),
481
- startDictation: () => new Promise(
482
- (resolve, reject) => run((v) => void Promise.resolve(v.startDictation()).then(resolve, reject), reject)
483
- ),
484
- stopDictation: () => shared.voice?.stopDictation(),
485
- requestClose: () => shared.voice ? shared.voice.requestClose() : shared.options?.onRequestClose?.(),
486
- setShowProtectionDialog: (open) => shared.voice?.setShowProtectionDialog(open),
487
- confirmClose: () => shared.voice ? shared.voice.confirmClose() : shared.options?.onRequestClose?.(),
488
- cancelClose: () => shared.voice?.cancelClose(),
489
- openAudioSettings: () => run((_v, open) => open())
490
- };
491
- })[0];
492
- const args = {};
493
- return {
494
- /** The add-ons are still on the way (draw the light stand-ins, open the door on engagement). */
495
- pending: door.pending,
496
- failed: door.failed,
497
- /** Hold the first right-click for the add-ons' menu (only where it will open one). */
498
- holdsContextMenu: (field) => door.pending && !door.failed && (door.light.holdsContextMenu?.(field) ?? false),
499
- /** Open the door now (a press, a key, a right-click, the mic, "…"). */
500
- engage: door.engage,
501
- notify: addOns.notify ?? STAND_IN_NOTIFY,
502
- useOpenAudioSettings: () => fns.openAudioSettings,
503
- useFormatting: (element, on, props) => {
504
- args.formatting = [element, on, props];
505
- },
506
- useVoice: (options) => {
507
- args.voice = options;
508
- shared.options = options;
509
- const s = snapshot ?? { ...PENDING_SNAPSHOT, microphone: live ? false : micApi };
510
- return {
511
- ...s,
512
- handleVoiceClick: fns.handleVoiceClick,
513
- startDictation: fns.startDictation,
514
- stopDictation: fns.stopDictation,
515
- requestClose: fns.requestClose,
516
- setShowProtectionDialog: fns.setShowProtectionDialog,
517
- confirmClose: fns.confirmClose,
518
- cancelClose: fns.cancelClose,
519
- dialogs: null
520
- };
521
- },
522
- /** The runner element: render it once inside the field (it draws the voice port's dialogs). */
523
- renderRunner: () => live && args.voice ? /* @__PURE__ */ jsx2(
524
- PortRunner,
525
- {
526
- addOns,
527
- voiceOptions: args.voice,
528
- formatting: args.formatting ?? null,
529
- shared,
530
- publish: setSnapshot
531
- }
532
- ) : null
533
- };
295
+
296
+ // src/writing-field/voice-chrome.tsx
297
+ import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
298
+ function ListeningStrip({ liveTranscript, className }) {
299
+ return /* @__PURE__ */ jsxs("div", { className: cn("absolute flex items-center gap-1.5 px-2 py-1 rounded-md", className), children: [
300
+ /* @__PURE__ */ jsx2("span", { "aria-hidden": true, className: "w-2 h-2 bg-primary rounded-full flex-shrink-0 animate-pulse" }),
301
+ /* @__PURE__ */ jsx2("span", { className: "type-secondary text-primary font-medium truncate", children: liveTranscript ? liveTranscript.slice(-60) : "Listening..." })
302
+ ] });
534
303
  }
535
- function PortRunner({
536
- addOns,
537
- voiceOptions,
538
- formatting,
539
- shared,
540
- publish
541
- }) {
542
- const ports = resolveHookPorts(addOns);
543
- const openAudioSettings = ports.useOpenAudioSettings();
544
- ports.useFormatting(formatting?.[0] ?? null, formatting?.[1] ?? false, formatting?.[2] ?? EMPTY_PROPS);
545
- const voice = ports.useVoice(voiceOptions);
546
- const last = React.useRef(null);
547
- React.useLayoutEffect(() => {
548
- shared.voice = voice;
549
- shared.openAudioSettings = openAudioSettings;
550
- shared.drain = () => {
551
- if (!shared.voice || !shared.openAudioSettings) return;
552
- for (const q of shared.queue.splice(0)) q.fn(shared.voice, shared.openAudioSettings);
553
- };
554
- const prev = last.current;
555
- if (!prev || SNAPSHOT_KEYS.some((k) => prev[k] !== voice[k])) {
556
- const next = Object.fromEntries(SNAPSHOT_KEYS.map((k) => [k, voice[k]]));
557
- last.current = next;
558
- publish(next);
304
+ function FinalizingStrip({ className }) {
305
+ return /* @__PURE__ */ jsxs(
306
+ "div",
307
+ {
308
+ className: cn(
309
+ "absolute flex items-center gap-1.5 px-2 py-1 rounded-md bg-blue-100 dark:bg-blue-900/30",
310
+ className
311
+ ),
312
+ children: [
313
+ /* @__PURE__ */ jsx2(Loader2Icon, { className: "w-3 h-3 animate-spin text-blue-600 dark:text-blue-400" }),
314
+ /* @__PURE__ */ jsx2("span", { className: "type-secondary text-blue-600 dark:text-blue-400 font-medium", children: "Finalizing..." })
315
+ ]
559
316
  }
560
- });
561
- React.useEffect(() => {
562
- if (shared.queue.length) shared.drain?.();
563
- });
564
- React.useEffect(
565
- () => () => {
566
- shared.voice = null;
567
- shared.openAudioSettings = null;
568
- shared.drain = null;
569
- },
570
- [shared]
571
317
  );
572
- return /* @__PURE__ */ jsx2(Fragment2, { children: voice.dialogs });
573
318
  }
574
- var EMPTY_PROPS = {};
575
- function PendingMicButton({ onMicClick, disabled, micAriaLabel: label = "Start voice input", tabIndex, dense = false }) {
576
- const { engage, failed } = useWritingFieldDoor();
577
- const title = failed ? "Voice input did not load. Press to try again." : label;
578
- return /* @__PURE__ */ jsx2("div", { className: cn("relative mx-[3px] inline-flex shrink-0 items-center", dense ? "h-6" : "h-7 pointer-coarse:h-10"), children: /* @__PURE__ */ jsx2("div", { className: cn("relative flex items-stretch rounded-full", dense ? "h-6" : "h-7 pointer-coarse:h-9"), children: /* @__PURE__ */ jsx2(
579
- Button,
319
+ function VoiceProtectionDialog({ voice }) {
320
+ const { isRecording, isTranscribing } = voice;
321
+ const busy = isRecording || isTranscribing;
322
+ return /* @__PURE__ */ jsx2(AlertDialog, { open: voice.showProtectionDialog, onOpenChange: voice.setShowProtectionDialog, children: /* @__PURE__ */ jsxs(AlertDialogContent, { children: [
323
+ /* @__PURE__ */ jsxs(AlertDialogHeader, { children: [
324
+ /* @__PURE__ */ jsx2("div", { className: "flex items-center gap-3 mb-2", children: busy ? /* @__PURE__ */ jsxs(Fragment, { children: [
325
+ /* @__PURE__ */ jsx2(Loader2Icon, { className: "h-5 w-5 animate-spin text-blue-500" }),
326
+ /* @__PURE__ */ jsx2(AlertDialogTitle, { children: isRecording ? "Recording in Progress" : "Transcription in Progress" })
327
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
328
+ /* @__PURE__ */ jsx2(CheckIcon, { className: "h-5 w-5 text-green-500" }),
329
+ /* @__PURE__ */ jsx2(AlertDialogTitle, { children: "Voice Input Complete" })
330
+ ] }) }),
331
+ /* @__PURE__ */ jsx2(AlertDialogDescription, { children: isRecording ? "Your voice is currently being recorded. If you close now, the recording will be stopped and lost." : isTranscribing ? "Your voice recording is currently being transcribed. If you close now, the transcription will be lost." : "Your voice input has been processed successfully! You can now safely close this panel." })
332
+ ] }),
333
+ /* @__PURE__ */ jsx2(AlertDialogFooter, { children: busy ? /* @__PURE__ */ jsxs(Fragment, { children: [
334
+ /* @__PURE__ */ jsx2(AlertDialogCancel, { onClick: voice.cancelClose, children: "Cancel & Wait" }),
335
+ /* @__PURE__ */ jsx2(
336
+ AlertDialogAction,
337
+ {
338
+ onClick: voice.confirmClose,
339
+ className: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
340
+ children: isRecording ? "Stop Recording" : "End Transcription"
341
+ }
342
+ )
343
+ ] }) : /* @__PURE__ */ jsx2(AlertDialogAction, { onClick: voice.confirmClose, children: "Close" }) })
344
+ ] }) });
345
+ }
346
+ function FieldMenuRow({
347
+ icon,
348
+ children,
349
+ onClick,
350
+ disabled
351
+ }) {
352
+ return /* @__PURE__ */ jsx2(
353
+ Tile,
580
354
  {
581
355
  type: "button",
582
- variant: "ghost",
583
- size: "xs",
584
- tabIndex,
585
- onClick: onMicClick,
586
- onContextMenu: (e) => {
587
- e.preventDefault();
588
- e.stopPropagation();
589
- void engage();
590
- },
591
356
  disabled,
592
- "aria-label": title,
593
- title,
594
- "data-touch-exempt": "The field row owns the microphone touch target.",
595
- className: cn(
596
- "relative z-[1] h-full touch-manipulation select-none rounded-full transition-colors focus-visible:ring-primary/40 disabled:opacity-40 hover:bg-muted/60 hover:text-muted-foreground active:bg-muted-foreground/15 active:scale-100 text-muted-foreground",
597
- dense ? "px-1 [&_svg]:size-3.5" : "px-1.5 pointer-coarse:px-3 [&_svg]:size-4",
598
- failed && "text-destructive"
599
- ),
600
- children: /* @__PURE__ */ jsx2(MicIcon, { className: dense ? "h-3.5 w-3.5" : "h-4 w-4" })
357
+ onClick,
358
+ variant: "quiet",
359
+ className: "w-full",
360
+ icon,
361
+ title: children
601
362
  }
602
- ) }) });
363
+ );
603
364
  }
604
- function PendingMenu({ ownRows, trailingRows }) {
605
- const { engage, failed } = useWritingFieldDoor();
606
- React.useEffect(() => {
607
- void engage();
608
- }, [engage]);
609
- return /* @__PURE__ */ jsxs2("div", { className: "flex w-48 flex-col p-1", "data-writing-field-host": "pending", children: [
610
- ownRows,
611
- failed ? /* @__PURE__ */ jsx2(FieldMenuRow, { icon: /* @__PURE__ */ jsx2(RotateCcwIcon, {}), onClick: () => void engage(), children: "Actions did not load. Retry" }) : /* @__PURE__ */ jsx2("div", { className: "flex items-center justify-center py-2 text-muted-foreground", role: "status", "aria-label": "Loading actions", children: /* @__PURE__ */ jsx2(Loader2Icon, { className: "h-4 w-4 animate-spin" }) }),
612
- trailingRows
613
- ] });
365
+ function micAriaLabel(isRecording, label) {
366
+ if (isRecording) return label ? `Stop recording for ${label}` : "Stop recording";
367
+ return label ? `Start voice input for ${label}` : "Start voice input";
614
368
  }
615
369
 
616
370
  // src/writing-field/pro-textarea.tsx
617
- import { Fragment as Fragment3, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
371
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
618
372
  var FILL_HEIGHT_REGEX = /(?:^|\s)(h-full|h-dvh|flex-1|grow|inset-0|min-h-0)(?:\s|$)/;
619
373
  function wantsFillHeight(className, wrapperClassName) {
620
374
  return FILL_HEIGHT_REGEX.test(className ?? "") || FILL_HEIGHT_REGEX.test(wrapperClassName ?? "");
@@ -666,7 +420,7 @@ var ProTextarea = React2.forwardRef(
666
420
  ...rest
667
421
  }, ref) => {
668
422
  const addOns = useWritingFieldAddOns();
669
- const ports = useFieldPorts(addOns);
423
+ const ports = resolveHookPorts(addOns);
670
424
  const { dom: props, addOnProps } = splitAddOnProps(
671
425
  rest,
672
426
  addOns.propNames
@@ -730,51 +484,11 @@ var ProTextarea = React2.forwardRef(
730
484
  const [showTextStatsBar, setShowTextStatsBar] = React2.useState(defaultShowTextStatsBar);
731
485
  const internalRef = React2.useRef(null);
732
486
  const textareaRef = internalRef;
733
- const handoff = React2.useRef(null);
734
- const valueRef = React2.useRef(value);
735
- valueRef.current = value;
736
- const setTextareaRef = React2.useCallback((node) => {
737
- const prev = internalRef.current;
738
- if (!node && prev) {
739
- handoff.current = {
740
- from: prev,
741
- value: prev.value,
742
- start: prev.selectionStart ?? 0,
743
- end: prev.selectionEnd ?? 0,
744
- scroll: prev.scrollTop,
745
- focused: typeof document !== "undefined" && document.activeElement === prev
746
- };
747
- } else if (node && handoff.current) {
748
- const carried = handoff.current;
749
- handoff.current = null;
750
- if (carried.from !== node) {
751
- if (valueRef.current === void 0 && node.value !== carried.value) node.value = carried.value;
752
- node.scrollTop = carried.scroll;
753
- if (carried.focused) {
754
- node.focus({ preventScroll: true });
755
- node.setSelectionRange(carried.start, carried.end);
756
- }
757
- }
758
- }
487
+ const setTextareaRef = (node) => {
759
488
  internalRef.current = node;
760
489
  if (typeof ref === "function") ref(node);
761
490
  else if (ref) ref.current = node;
762
- }, [ref]);
763
- const heldContextMenu = React2.useRef(null);
764
- const holdContextMenuUntilLoaded = (e) => {
765
- e.preventDefault();
766
- heldContextMenu.current = { x: e.clientX, y: e.clientY };
767
- void ports.engage();
768
491
  };
769
- React2.useEffect(() => {
770
- const held = heldContextMenu.current;
771
- if (ports.pending || !held) return;
772
- heldContextMenu.current = null;
773
- if (!addOns.ContextMenu) return;
774
- internalRef.current?.dispatchEvent(
775
- new MouseEvent("contextmenu", { bubbles: true, cancelable: true, clientX: held.x, clientY: held.y })
776
- );
777
- }, [ports.pending, addOns.ContextMenu]);
778
492
  const formattingOn = markdownFormatting ?? (!editor && !submitOnEnter && (props.rows === void 0 || Number(props.rows) >= 2));
779
493
  const [formatElement, setFormatElement] = React2.useState(null);
780
494
  React2.useLayoutEffect(() => {
@@ -966,8 +680,8 @@ var ProTextarea = React2.forwardRef(
966
680
  const Stats = addOns.TextStats;
967
681
  const showTextStats = enableTextStats && showMenu && !!Stats;
968
682
  React2.useEffect(() => {
969
- if (enableTextStats && !addOns.TextStats && !ports.pending) announceMissingPort("TextStats");
970
- }, [enableTextStats, addOns.TextStats, ports.pending]);
683
+ if (enableTextStats && !addOns.TextStats) announceMissingPort("TextStats");
684
+ }, [enableTextStats, addOns.TextStats]);
971
685
  const showPinnedTextStatsBar = showTextStats && showTextStatsBar;
972
686
  const fillHeight = !autoGrow && wantsFillHeight(className, wrapperClassName);
973
687
  React2.useEffect(() => {
@@ -1004,7 +718,7 @@ var ProTextarea = React2.forwardRef(
1004
718
  ...inlineControls ? inlineControlsFieldPadding(clusterWidth, inlineMinHeight) : {}
1005
719
  };
1006
720
  const closeMenu = () => setMenuOpen(false);
1007
- const ownRows = expandInMenu || voiceInMenu || audioSettingsInMenu ? /* @__PURE__ */ jsxs3(Fragment3, { children: [
721
+ const ownRows = expandInMenu || voiceInMenu || audioSettingsInMenu ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
1008
722
  expandInMenu ? /* @__PURE__ */ jsx3(
1009
723
  FieldMenuRow,
1010
724
  {
@@ -1041,7 +755,7 @@ var ProTextarea = React2.forwardRef(
1041
755
  ) : null,
1042
756
  showMenu ? /* @__PURE__ */ jsx3("div", { className: "my-1 h-px bg-border", role: "separator" }) : null
1043
757
  ] }) : null;
1044
- const trailingRows = showTextStats && Stats ? /* @__PURE__ */ jsxs3(Fragment3, { children: [
758
+ const trailingRows = showTextStats && Stats ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
1045
759
  /* @__PURE__ */ jsx3("div", { className: "my-1 h-px bg-border", role: "separator" }),
1046
760
  /* @__PURE__ */ jsx3(
1047
761
  Stats.MenuItems,
@@ -1054,7 +768,7 @@ var ProTextarea = React2.forwardRef(
1054
768
  ] }) : null;
1055
769
  const Menu = addOns.Menu;
1056
770
  const menuBody = statsOpen && Stats ? /* @__PURE__ */ jsx3("div", { className: "w-56", children: /* @__PURE__ */ jsx3(Stats.Panel, { text: valueAsString, onBack: () => setStatsOpen(false), onClose: closeMenu }) }) : Menu && menuActions !== "none" ? /* @__PURE__ */ jsx3(Menu, { field: fieldApi, ownRows, trailingRows, close: closeMenu }) : /* @__PURE__ */ jsx3(StandInMenu, { field: fieldApi, ownRows, trailingRows, close: closeMenu, registry: menuActions !== "none" });
1057
- const cluster = /* @__PURE__ */ jsxs3(
771
+ const cluster = /* @__PURE__ */ jsxs2(
1058
772
  "div",
1059
773
  {
1060
774
  ref: setClusterNode,
@@ -1077,7 +791,7 @@ var ProTextarea = React2.forwardRef(
1077
791
  className: "text-muted-foreground"
1078
792
  }
1079
793
  ) : null,
1080
- /* @__PURE__ */ jsxs3(
794
+ /* @__PURE__ */ jsxs2(
1081
795
  "div",
1082
796
  {
1083
797
  "data-matrx-glass-plane": true,
@@ -1100,7 +814,7 @@ var ProTextarea = React2.forwardRef(
1100
814
  micAriaLabel: micAriaLabel(isRecording, auxiliaryControlsLabel)
1101
815
  }
1102
816
  ) : null,
1103
- menuShown && /* @__PURE__ */ jsxs3(
817
+ menuShown && /* @__PURE__ */ jsxs2(
1104
818
  Popover,
1105
819
  {
1106
820
  open: menuOpen,
@@ -1155,18 +869,14 @@ var ProTextarea = React2.forwardRef(
1155
869
  {
1156
870
  ref: setFieldBoxNode,
1157
871
  className: cn("relative group", wrapperClassName),
1158
- "data-writing-field": "textarea",
1159
- "data-writing-field-host": ports.pending ? "pending" : addOns.Menu && addOns.useVoice ? void 0 : UNCONFIGURED,
1160
- onPointerDownCapture: ports.pending ? () => void ports.engage() : void 0,
1161
- onKeyDownCapture: ports.pending ? () => void ports.engage() : void 0,
1162
- onContextMenuCapture: !disabled && ports.holdsContextMenu({ kind: "textarea", addOnProps }) ? holdContextMenuUntilLoaded : void 0,
872
+ "data-writing-field-host": addOns.Menu && addOns.useVoice ? void 0 : UNCONFIGURED,
1163
873
  onMouseEnter: () => setIsHovered(true),
1164
874
  onMouseMove: () => {
1165
875
  if (!isHovered) setIsHovered(true);
1166
876
  },
1167
877
  onMouseLeave: () => setIsHovered(false),
1168
- children: /* @__PURE__ */ jsxs3("div", { className: cn("flex min-h-0 min-w-0 w-full flex-col", fillHeight ? "h-full flex-1" : "flex-1"), children: [
1169
- /* @__PURE__ */ jsxs3("div", { className: cn("relative min-w-0 w-full", fillHeight && "flex min-h-0 flex-1 flex-col"), children: [
878
+ children: /* @__PURE__ */ jsxs2("div", { className: cn("flex min-h-0 min-w-0 w-full flex-col", fillHeight ? "h-full flex-1" : "flex-1"), children: [
879
+ /* @__PURE__ */ jsxs2("div", { className: cn("relative min-w-0 w-full", fillHeight && "flex min-h-0 flex-1 flex-col"), children: [
1170
880
  editor ? editor.render({
1171
881
  id: inputId,
1172
882
  className: fieldClassName,
@@ -1199,7 +909,7 @@ var ProTextarea = React2.forwardRef(
1199
909
  ...props
1200
910
  }
1201
911
  ),
1202
- expandable && !editor ? /* @__PURE__ */ jsx3(Dialog, { open: expandOpen, onOpenChange: setExpandOpen, children: /* @__PURE__ */ jsxs3(DialogContent, { className: "flex max-h-[90dvh] w-[min(56rem,calc(100vw-2rem))] max-w-none flex-col gap-3 sm:max-w-none", children: [
912
+ expandable && !editor ? /* @__PURE__ */ jsx3(Dialog, { open: expandOpen, onOpenChange: setExpandOpen, children: /* @__PURE__ */ jsxs2(DialogContent, { className: "flex max-h-[90dvh] w-[min(56rem,calc(100vw-2rem))] max-w-none flex-col gap-3 sm:max-w-none", children: [
1203
913
  /* @__PURE__ */ jsx3(DialogTitle, { className: "truncate", children: auxiliaryControlsLabel ?? floatingLabel ?? "Text" }),
1204
914
  /* @__PURE__ */ jsx3(
1205
915
  ProTextarea,
@@ -1248,7 +958,6 @@ var ProTextarea = React2.forwardRef(
1248
958
  enableVoice && isTranscribing && !isRecording && /* @__PURE__ */ jsx3(FinalizingStrip, { className: inlineControls ? "left-0 bottom-full mb-1.5" : "left-2 bottom-2" })
1249
959
  ] }),
1250
960
  voice.dialogs,
1251
- ports.renderRunner(),
1252
961
  /* @__PURE__ */ jsx3(VoiceProtectionDialog, { voice }),
1253
962
  showPinnedTextStatsBar && Stats ? /* @__PURE__ */ jsx3("div", { className: "hidden sm:block", children: /* @__PURE__ */ jsx3(Stats.Bar, { text: valueAsString }) }) : null
1254
963
  ] })
@@ -1270,7 +979,7 @@ function StandInMenu({
1270
979
  React2.useEffect(() => {
1271
980
  if (registry2) announceMissingPort("Menu");
1272
981
  }, [registry2]);
1273
- return /* @__PURE__ */ jsxs3("div", { className: "flex w-48 flex-col p-1", "data-writing-field-host": registry2 ? UNCONFIGURED : void 0, children: [
982
+ return /* @__PURE__ */ jsxs2("div", { className: "flex w-48 flex-col p-1", "data-writing-field-host": registry2 ? UNCONFIGURED : void 0, children: [
1274
983
  ownRows,
1275
984
  registry2 && field.showCopyButton ? /* @__PURE__ */ jsx3(
1276
985
  FieldMenuRow,
@@ -1314,7 +1023,7 @@ function hoverRevealsCluster(inputWidth) {
1314
1023
  }
1315
1024
 
1316
1025
  // src/writing-field/pro-input.tsx
1317
- import { Fragment as Fragment4, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1026
+ import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1318
1027
  var joinWithSpace = (base, text) => `${base}${base.endsWith(" ") ? "" : " "}${text}`;
1319
1028
  var ProInput = React3.forwardRef(
1320
1029
  ({
@@ -1350,7 +1059,7 @@ var ProInput = React3.forwardRef(
1350
1059
  ...rest
1351
1060
  }, ref) => {
1352
1061
  const addOns = useWritingFieldAddOns();
1353
- const ports = useFieldPorts(addOns);
1062
+ const ports = resolveHookPorts(addOns);
1354
1063
  const { dom: props, addOnProps } = splitAddOnProps(rest, addOns.propNames);
1355
1064
  const generatedId = React3.useId();
1356
1065
  const { size: _nativeSize, width: nativeWidth, style: nativeStyle, ...fieldProps } = props;
@@ -1548,7 +1257,7 @@ var ProInput = React3.forwardRef(
1548
1257
  const labelFloated = isFocused || valueAsString.length > 0;
1549
1258
  const showVoiceStatus = isRecording || isTranscribing && !isRecording;
1550
1259
  const closeMenu = () => setMenuOpen(false);
1551
- const ownRows = /* @__PURE__ */ jsxs4(Fragment4, { children: [
1260
+ const ownRows = /* @__PURE__ */ jsxs3(Fragment3, { children: [
1552
1261
  clearInMenu ? /* @__PURE__ */ jsx4(
1553
1262
  FieldMenuRow,
1554
1263
  {
@@ -1586,14 +1295,11 @@ var ProInput = React3.forwardRef(
1586
1295
  clearInMenu || voiceInMenu || audioSettingsInMenu ? /* @__PURE__ */ jsx4("div", { className: "my-1 h-px bg-border", role: "separator" }) : null
1587
1296
  ] });
1588
1297
  const Menu = addOns.Menu;
1589
- return /* @__PURE__ */ jsxs4(
1298
+ return /* @__PURE__ */ jsxs3(
1590
1299
  "div",
1591
1300
  {
1592
1301
  className: cn("relative group", wrapperClassName),
1593
- "data-writing-field": "input",
1594
- "data-writing-field-host": ports.pending ? "pending" : addOns.Menu && addOns.useVoice ? void 0 : UNCONFIGURED,
1595
- onPointerDownCapture: ports.pending ? () => void ports.engage() : void 0,
1596
- onKeyDownCapture: ports.pending ? () => void ports.engage() : void 0,
1302
+ "data-writing-field-host": addOns.Menu && addOns.useVoice ? void 0 : UNCONFIGURED,
1597
1303
  onMouseEnter: () => setIsHovered(true),
1598
1304
  onMouseMove: () => {
1599
1305
  if (!isHovered) setIsHovered(true);
@@ -1633,7 +1339,7 @@ var ProInput = React3.forwardRef(
1633
1339
  children: floatingLabel
1634
1340
  }
1635
1341
  ),
1636
- /* @__PURE__ */ jsxs4(
1342
+ /* @__PURE__ */ jsxs3(
1637
1343
  "div",
1638
1344
  {
1639
1345
  ref: setClusterNode,
@@ -1642,7 +1348,7 @@ var ProInput = React3.forwardRef(
1642
1348
  "data-matrx-tap-dense": denseField ? "" : void 0,
1643
1349
  className: "absolute right-0 top-1/2 -translate-y-1/2 flex items-center z-10",
1644
1350
  children: [
1645
- /* @__PURE__ */ jsxs4(
1351
+ /* @__PURE__ */ jsxs3(
1646
1352
  "div",
1647
1353
  {
1648
1354
  ref: auxRef,
@@ -1664,7 +1370,7 @@ var ProInput = React3.forwardRef(
1664
1370
  micAriaLabel: micAriaLabel(isRecording, auxiliaryControlsLabel)
1665
1371
  }
1666
1372
  ) : null,
1667
- showMenu && /* @__PURE__ */ jsxs4(Popover, { open: menuOpen, onOpenChange: setMenuOpen, children: [
1373
+ showMenu && /* @__PURE__ */ jsxs3(Popover, { open: menuOpen, onOpenChange: setMenuOpen, children: [
1668
1374
  /* @__PURE__ */ jsx4(PopoverTrigger, { asChild: true, children: hasCopied ? /* @__PURE__ */ jsx4(
1669
1375
  CheckTapButton,
1670
1376
  {
@@ -1723,12 +1429,11 @@ var ProInput = React3.forwardRef(
1723
1429
  ]
1724
1430
  }
1725
1431
  ),
1726
- showVoiceStatus && /* @__PURE__ */ jsx4("div", { className: "absolute left-0 top-full mt-1 flex items-center gap-1.5 px-2 py-1 rounded-md max-w-full", children: isRecording ? /* @__PURE__ */ jsxs4("div", { className: "relative flex items-center gap-1.5 px-2 py-1 bg-primary/10 dark:bg-primary/15 rounded-md max-w-full", children: [
1432
+ showVoiceStatus && /* @__PURE__ */ jsx4("div", { className: "absolute left-0 top-full mt-1 flex items-center gap-1.5 px-2 py-1 rounded-md max-w-full", children: isRecording ? /* @__PURE__ */ jsxs3("div", { className: "relative flex items-center gap-1.5 px-2 py-1 bg-primary/10 dark:bg-primary/15 rounded-md max-w-full", children: [
1727
1433
  /* @__PURE__ */ jsx4("span", { "aria-hidden": true, className: "w-2 h-2 bg-primary rounded-full flex-shrink-0 animate-pulse" }),
1728
1434
  /* @__PURE__ */ jsx4("span", { className: "type-secondary text-primary font-medium truncate", children: voice.liveTranscript ? voice.liveTranscript.slice(-60) : "Listening..." })
1729
1435
  ] }) : /* @__PURE__ */ jsx4(FinalizingStrip, { className: "relative" }) }),
1730
1436
  voice.dialogs,
1731
- ports.renderRunner(),
1732
1437
  /* @__PURE__ */ jsx4(VoiceProtectionDialog, { voice })
1733
1438
  ]
1734
1439
  }
@@ -1766,7 +1471,6 @@ export {
1766
1471
  proTextareaClusterPlacement,
1767
1472
  reservedRightPaddingPx,
1768
1473
  splitAddOnProps,
1769
- useWritingFieldAddOns,
1770
- useWritingFieldDoor
1474
+ useWritingFieldAddOns
1771
1475
  };
1772
1476
  //# sourceMappingURL=writing-field.js.map