@almadar/ui 2.52.0 → 2.53.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.
@@ -22132,6 +22132,26 @@ var CodeBlock = React125__namespace.default.memo(
22132
22132
  const codeRef = React125.useRef(null);
22133
22133
  const savedScrollLeftRef = React125.useRef(0);
22134
22134
  const [copied, setCopied] = React125.useState(false);
22135
+ const [editableValue, setEditableValue] = React125.useState(code);
22136
+ const [editableTextareaKey, setEditableTextareaKey] = React125.useState(0);
22137
+ const lastPropCodeRef = React125.useRef(code);
22138
+ const editableTextareaRef = React125.useRef(null);
22139
+ const editableOverlayRef = React125.useRef(null);
22140
+ React125.useEffect(() => {
22141
+ if (code !== lastPropCodeRef.current) {
22142
+ lastPropCodeRef.current = code;
22143
+ setEditableValue(code);
22144
+ setEditableTextareaKey((k) => k + 1);
22145
+ }
22146
+ }, [code]);
22147
+ const handleEditableScroll = React125.useCallback(() => {
22148
+ const ta = editableTextareaRef.current;
22149
+ const ov = editableOverlayRef.current;
22150
+ if (ta && ov) {
22151
+ ov.scrollTop = ta.scrollTop;
22152
+ ov.scrollLeft = ta.scrollLeft;
22153
+ }
22154
+ }, []);
22135
22155
  const isFoldable = foldableProp ?? (language === "orb" || language === "json");
22136
22156
  const [collapsed, setCollapsed] = React125.useState(() => /* @__PURE__ */ new Set());
22137
22157
  const foldRegions = React125.useMemo(
@@ -22292,33 +22312,106 @@ var CodeBlock = React125__namespace.default.memo(
22292
22312
  }
22293
22313
  ),
22294
22314
  editable ? (
22295
- /* GAP-51: editable mode composes the Textarea atom. Plain text editing,
22296
- no Prism highlighting overlay (follow-up). The textarea is uncontrolled
22297
- on the value side: we pass `code` as the initial value and forward
22298
- every keystroke via onChange the consumer is responsible for
22299
- debouncing and re-deriving `code` only after the user stops typing
22300
- so the cursor doesn't fight a re-render. */
22301
- /* @__PURE__ */ jsxRuntime.jsx(
22302
- Textarea,
22315
+ /* GAP-77: editable mode = transparent Textarea on top + Prism-highlighted
22316
+ overlay underneath. The textarea is uncontrolled (defaultValue + key)
22317
+ to avoid cursor jumps; the overlay reads `editableValue` which is
22318
+ mirrored from the textarea via onChange. Both elements share IDENTICAL
22319
+ font / line-height / padding so the highlighted text aligns with the
22320
+ textarea's invisible glyphs.
22321
+
22322
+ Scroll sync: the overlay has `pointer-events: none` and the textarea
22323
+ scrolls; `handleEditableScroll` keeps the overlay's scroll matched. */
22324
+ /* @__PURE__ */ jsxRuntime.jsxs(
22325
+ Box,
22303
22326
  {
22304
- defaultValue: code,
22305
- onChange: (e) => onChange?.(e.target.value),
22306
- spellCheck: false,
22307
22327
  style: {
22308
- fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace',
22309
- fontSize: "13px",
22310
- lineHeight: "1.5",
22328
+ position: "relative",
22311
22329
  backgroundColor: "#1e1e1e",
22312
- color: "#e6e6e6",
22313
22330
  borderRadius: hasHeader ? "0 0 0.5rem 0.5rem" : "0.5rem",
22314
- border: "none",
22315
- padding: "1rem",
22316
- resize: "none",
22317
22331
  minHeight: "160px",
22318
22332
  maxHeight,
22319
- width: "100%",
22320
- outline: "none"
22321
- }
22333
+ overflow: "hidden"
22334
+ },
22335
+ children: [
22336
+ /* @__PURE__ */ jsxRuntime.jsx(
22337
+ "div",
22338
+ {
22339
+ ref: editableOverlayRef,
22340
+ "aria-hidden": true,
22341
+ style: {
22342
+ position: "absolute",
22343
+ inset: 0,
22344
+ overflow: "hidden",
22345
+ pointerEvents: "none",
22346
+ maxHeight
22347
+ },
22348
+ children: /* @__PURE__ */ jsxRuntime.jsx(
22349
+ SyntaxHighlighter__default.default,
22350
+ {
22351
+ PreTag: "div",
22352
+ language,
22353
+ style: activeStyle,
22354
+ customStyle: {
22355
+ backgroundColor: "transparent",
22356
+ borderRadius: 0,
22357
+ padding: "1rem",
22358
+ margin: 0,
22359
+ whiteSpace: "pre",
22360
+ minWidth: "100%",
22361
+ minHeight: "160px",
22362
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace',
22363
+ fontSize: "13px",
22364
+ lineHeight: "1.5"
22365
+ },
22366
+ codeTagProps: {
22367
+ style: {
22368
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace',
22369
+ fontSize: "13px",
22370
+ lineHeight: "1.5"
22371
+ }
22372
+ },
22373
+ children: editableValue || " "
22374
+ }
22375
+ )
22376
+ }
22377
+ ),
22378
+ /* @__PURE__ */ jsxRuntime.jsx(
22379
+ Textarea,
22380
+ {
22381
+ ref: editableTextareaRef,
22382
+ defaultValue: code,
22383
+ onChange: (e) => {
22384
+ setEditableValue(e.target.value);
22385
+ onChange?.(e.target.value);
22386
+ },
22387
+ onScroll: handleEditableScroll,
22388
+ spellCheck: false,
22389
+ style: {
22390
+ position: "relative",
22391
+ zIndex: 1,
22392
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace',
22393
+ fontSize: "13px",
22394
+ lineHeight: "1.5",
22395
+ backgroundColor: "transparent",
22396
+ color: "transparent",
22397
+ caretColor: "#e6e6e6",
22398
+ borderRadius: hasHeader ? "0 0 0.5rem 0.5rem" : "0.5rem",
22399
+ border: "none",
22400
+ padding: "1rem",
22401
+ resize: "none",
22402
+ minHeight: "160px",
22403
+ maxHeight,
22404
+ width: "100%",
22405
+ height: "100%",
22406
+ outline: "none",
22407
+ whiteSpace: "pre",
22408
+ overflowWrap: "normal",
22409
+ overflow: "auto"
22410
+ }
22411
+ },
22412
+ editableTextareaKey
22413
+ )
22414
+ ]
22322
22415
  }
22323
22416
  )
22324
22417
  ) : /* @__PURE__ */ jsxRuntime.jsx(
@@ -48449,7 +48542,7 @@ function OrbInspector({ node, schema, editable = false, onSchemaChange, onClose
48449
48542
  onBlur: (e) => handlePropChange(propName, e.target.value)
48450
48543
  }
48451
48544
  ) : /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "small", className: "text-[11px] text-muted-foreground", children: [
48452
- displayValue || (ps.types?.join(" | ") ?? "string"),
48545
+ displayValue || "\u2014",
48453
48546
  ps.required ? " *" : ""
48454
48547
  ] })
48455
48548
  ] }, propName);
package/dist/avl/index.js CHANGED
@@ -22086,6 +22086,26 @@ var CodeBlock = React125__default.memo(
22086
22086
  const codeRef = useRef(null);
22087
22087
  const savedScrollLeftRef = useRef(0);
22088
22088
  const [copied, setCopied] = useState(false);
22089
+ const [editableValue, setEditableValue] = useState(code);
22090
+ const [editableTextareaKey, setEditableTextareaKey] = useState(0);
22091
+ const lastPropCodeRef = useRef(code);
22092
+ const editableTextareaRef = useRef(null);
22093
+ const editableOverlayRef = useRef(null);
22094
+ useEffect(() => {
22095
+ if (code !== lastPropCodeRef.current) {
22096
+ lastPropCodeRef.current = code;
22097
+ setEditableValue(code);
22098
+ setEditableTextareaKey((k) => k + 1);
22099
+ }
22100
+ }, [code]);
22101
+ const handleEditableScroll = useCallback(() => {
22102
+ const ta = editableTextareaRef.current;
22103
+ const ov = editableOverlayRef.current;
22104
+ if (ta && ov) {
22105
+ ov.scrollTop = ta.scrollTop;
22106
+ ov.scrollLeft = ta.scrollLeft;
22107
+ }
22108
+ }, []);
22089
22109
  const isFoldable = foldableProp ?? (language === "orb" || language === "json");
22090
22110
  const [collapsed, setCollapsed] = useState(() => /* @__PURE__ */ new Set());
22091
22111
  const foldRegions = useMemo(
@@ -22246,33 +22266,106 @@ var CodeBlock = React125__default.memo(
22246
22266
  }
22247
22267
  ),
22248
22268
  editable ? (
22249
- /* GAP-51: editable mode composes the Textarea atom. Plain text editing,
22250
- no Prism highlighting overlay (follow-up). The textarea is uncontrolled
22251
- on the value side: we pass `code` as the initial value and forward
22252
- every keystroke via onChange the consumer is responsible for
22253
- debouncing and re-deriving `code` only after the user stops typing
22254
- so the cursor doesn't fight a re-render. */
22255
- /* @__PURE__ */ jsx(
22256
- Textarea,
22269
+ /* GAP-77: editable mode = transparent Textarea on top + Prism-highlighted
22270
+ overlay underneath. The textarea is uncontrolled (defaultValue + key)
22271
+ to avoid cursor jumps; the overlay reads `editableValue` which is
22272
+ mirrored from the textarea via onChange. Both elements share IDENTICAL
22273
+ font / line-height / padding so the highlighted text aligns with the
22274
+ textarea's invisible glyphs.
22275
+
22276
+ Scroll sync: the overlay has `pointer-events: none` and the textarea
22277
+ scrolls; `handleEditableScroll` keeps the overlay's scroll matched. */
22278
+ /* @__PURE__ */ jsxs(
22279
+ Box,
22257
22280
  {
22258
- defaultValue: code,
22259
- onChange: (e) => onChange?.(e.target.value),
22260
- spellCheck: false,
22261
22281
  style: {
22262
- fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace',
22263
- fontSize: "13px",
22264
- lineHeight: "1.5",
22282
+ position: "relative",
22265
22283
  backgroundColor: "#1e1e1e",
22266
- color: "#e6e6e6",
22267
22284
  borderRadius: hasHeader ? "0 0 0.5rem 0.5rem" : "0.5rem",
22268
- border: "none",
22269
- padding: "1rem",
22270
- resize: "none",
22271
22285
  minHeight: "160px",
22272
22286
  maxHeight,
22273
- width: "100%",
22274
- outline: "none"
22275
- }
22287
+ overflow: "hidden"
22288
+ },
22289
+ children: [
22290
+ /* @__PURE__ */ jsx(
22291
+ "div",
22292
+ {
22293
+ ref: editableOverlayRef,
22294
+ "aria-hidden": true,
22295
+ style: {
22296
+ position: "absolute",
22297
+ inset: 0,
22298
+ overflow: "hidden",
22299
+ pointerEvents: "none",
22300
+ maxHeight
22301
+ },
22302
+ children: /* @__PURE__ */ jsx(
22303
+ SyntaxHighlighter,
22304
+ {
22305
+ PreTag: "div",
22306
+ language,
22307
+ style: activeStyle,
22308
+ customStyle: {
22309
+ backgroundColor: "transparent",
22310
+ borderRadius: 0,
22311
+ padding: "1rem",
22312
+ margin: 0,
22313
+ whiteSpace: "pre",
22314
+ minWidth: "100%",
22315
+ minHeight: "160px",
22316
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace',
22317
+ fontSize: "13px",
22318
+ lineHeight: "1.5"
22319
+ },
22320
+ codeTagProps: {
22321
+ style: {
22322
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace',
22323
+ fontSize: "13px",
22324
+ lineHeight: "1.5"
22325
+ }
22326
+ },
22327
+ children: editableValue || " "
22328
+ }
22329
+ )
22330
+ }
22331
+ ),
22332
+ /* @__PURE__ */ jsx(
22333
+ Textarea,
22334
+ {
22335
+ ref: editableTextareaRef,
22336
+ defaultValue: code,
22337
+ onChange: (e) => {
22338
+ setEditableValue(e.target.value);
22339
+ onChange?.(e.target.value);
22340
+ },
22341
+ onScroll: handleEditableScroll,
22342
+ spellCheck: false,
22343
+ style: {
22344
+ position: "relative",
22345
+ zIndex: 1,
22346
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace',
22347
+ fontSize: "13px",
22348
+ lineHeight: "1.5",
22349
+ backgroundColor: "transparent",
22350
+ color: "transparent",
22351
+ caretColor: "#e6e6e6",
22352
+ borderRadius: hasHeader ? "0 0 0.5rem 0.5rem" : "0.5rem",
22353
+ border: "none",
22354
+ padding: "1rem",
22355
+ resize: "none",
22356
+ minHeight: "160px",
22357
+ maxHeight,
22358
+ width: "100%",
22359
+ height: "100%",
22360
+ outline: "none",
22361
+ whiteSpace: "pre",
22362
+ overflowWrap: "normal",
22363
+ overflow: "auto"
22364
+ }
22365
+ },
22366
+ editableTextareaKey
22367
+ )
22368
+ ]
22276
22369
  }
22277
22370
  )
22278
22371
  ) : /* @__PURE__ */ jsx(
@@ -48403,7 +48496,7 @@ function OrbInspector({ node, schema, editable = false, onSchemaChange, onClose
48403
48496
  onBlur: (e) => handlePropChange(propName, e.target.value)
48404
48497
  }
48405
48498
  ) : /* @__PURE__ */ jsxs(Typography, { variant: "small", className: "text-[11px] text-muted-foreground", children: [
48406
- displayValue || (ps.types?.join(" | ") ?? "string"),
48499
+ displayValue || "\u2014",
48407
48500
  ps.required ? " *" : ""
48408
48501
  ] })
48409
48502
  ] }, propName);
@@ -9223,6 +9223,26 @@ var CodeBlock = React90__namespace.default.memo(
9223
9223
  const codeRef = React90.useRef(null);
9224
9224
  const savedScrollLeftRef = React90.useRef(0);
9225
9225
  const [copied, setCopied] = React90.useState(false);
9226
+ const [editableValue, setEditableValue] = React90.useState(code);
9227
+ const [editableTextareaKey, setEditableTextareaKey] = React90.useState(0);
9228
+ const lastPropCodeRef = React90.useRef(code);
9229
+ const editableTextareaRef = React90.useRef(null);
9230
+ const editableOverlayRef = React90.useRef(null);
9231
+ React90.useEffect(() => {
9232
+ if (code !== lastPropCodeRef.current) {
9233
+ lastPropCodeRef.current = code;
9234
+ setEditableValue(code);
9235
+ setEditableTextareaKey((k) => k + 1);
9236
+ }
9237
+ }, [code]);
9238
+ const handleEditableScroll = React90.useCallback(() => {
9239
+ const ta = editableTextareaRef.current;
9240
+ const ov = editableOverlayRef.current;
9241
+ if (ta && ov) {
9242
+ ov.scrollTop = ta.scrollTop;
9243
+ ov.scrollLeft = ta.scrollLeft;
9244
+ }
9245
+ }, []);
9226
9246
  const isFoldable = foldableProp ?? (language === "orb" || language === "json");
9227
9247
  const [collapsed, setCollapsed] = React90.useState(() => /* @__PURE__ */ new Set());
9228
9248
  const foldRegions = React90.useMemo(
@@ -9383,33 +9403,106 @@ var CodeBlock = React90__namespace.default.memo(
9383
9403
  }
9384
9404
  ),
9385
9405
  editable ? (
9386
- /* GAP-51: editable mode composes the Textarea atom. Plain text editing,
9387
- no Prism highlighting overlay (follow-up). The textarea is uncontrolled
9388
- on the value side: we pass `code` as the initial value and forward
9389
- every keystroke via onChange the consumer is responsible for
9390
- debouncing and re-deriving `code` only after the user stops typing
9391
- so the cursor doesn't fight a re-render. */
9392
- /* @__PURE__ */ jsxRuntime.jsx(
9393
- Textarea,
9406
+ /* GAP-77: editable mode = transparent Textarea on top + Prism-highlighted
9407
+ overlay underneath. The textarea is uncontrolled (defaultValue + key)
9408
+ to avoid cursor jumps; the overlay reads `editableValue` which is
9409
+ mirrored from the textarea via onChange. Both elements share IDENTICAL
9410
+ font / line-height / padding so the highlighted text aligns with the
9411
+ textarea's invisible glyphs.
9412
+
9413
+ Scroll sync: the overlay has `pointer-events: none` and the textarea
9414
+ scrolls; `handleEditableScroll` keeps the overlay's scroll matched. */
9415
+ /* @__PURE__ */ jsxRuntime.jsxs(
9416
+ Box,
9394
9417
  {
9395
- defaultValue: code,
9396
- onChange: (e) => onChange?.(e.target.value),
9397
- spellCheck: false,
9398
9418
  style: {
9399
- fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace',
9400
- fontSize: "13px",
9401
- lineHeight: "1.5",
9419
+ position: "relative",
9402
9420
  backgroundColor: "#1e1e1e",
9403
- color: "#e6e6e6",
9404
9421
  borderRadius: hasHeader ? "0 0 0.5rem 0.5rem" : "0.5rem",
9405
- border: "none",
9406
- padding: "1rem",
9407
- resize: "none",
9408
9422
  minHeight: "160px",
9409
9423
  maxHeight,
9410
- width: "100%",
9411
- outline: "none"
9412
- }
9424
+ overflow: "hidden"
9425
+ },
9426
+ children: [
9427
+ /* @__PURE__ */ jsxRuntime.jsx(
9428
+ "div",
9429
+ {
9430
+ ref: editableOverlayRef,
9431
+ "aria-hidden": true,
9432
+ style: {
9433
+ position: "absolute",
9434
+ inset: 0,
9435
+ overflow: "hidden",
9436
+ pointerEvents: "none",
9437
+ maxHeight
9438
+ },
9439
+ children: /* @__PURE__ */ jsxRuntime.jsx(
9440
+ SyntaxHighlighter__default.default,
9441
+ {
9442
+ PreTag: "div",
9443
+ language,
9444
+ style: activeStyle,
9445
+ customStyle: {
9446
+ backgroundColor: "transparent",
9447
+ borderRadius: 0,
9448
+ padding: "1rem",
9449
+ margin: 0,
9450
+ whiteSpace: "pre",
9451
+ minWidth: "100%",
9452
+ minHeight: "160px",
9453
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace',
9454
+ fontSize: "13px",
9455
+ lineHeight: "1.5"
9456
+ },
9457
+ codeTagProps: {
9458
+ style: {
9459
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace',
9460
+ fontSize: "13px",
9461
+ lineHeight: "1.5"
9462
+ }
9463
+ },
9464
+ children: editableValue || " "
9465
+ }
9466
+ )
9467
+ }
9468
+ ),
9469
+ /* @__PURE__ */ jsxRuntime.jsx(
9470
+ Textarea,
9471
+ {
9472
+ ref: editableTextareaRef,
9473
+ defaultValue: code,
9474
+ onChange: (e) => {
9475
+ setEditableValue(e.target.value);
9476
+ onChange?.(e.target.value);
9477
+ },
9478
+ onScroll: handleEditableScroll,
9479
+ spellCheck: false,
9480
+ style: {
9481
+ position: "relative",
9482
+ zIndex: 1,
9483
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace',
9484
+ fontSize: "13px",
9485
+ lineHeight: "1.5",
9486
+ backgroundColor: "transparent",
9487
+ color: "transparent",
9488
+ caretColor: "#e6e6e6",
9489
+ borderRadius: hasHeader ? "0 0 0.5rem 0.5rem" : "0.5rem",
9490
+ border: "none",
9491
+ padding: "1rem",
9492
+ resize: "none",
9493
+ minHeight: "160px",
9494
+ maxHeight,
9495
+ width: "100%",
9496
+ height: "100%",
9497
+ outline: "none",
9498
+ whiteSpace: "pre",
9499
+ overflowWrap: "normal",
9500
+ overflow: "auto"
9501
+ }
9502
+ },
9503
+ editableTextareaKey
9504
+ )
9505
+ ]
9413
9506
  }
9414
9507
  )
9415
9508
  ) : /* @__PURE__ */ jsxRuntime.jsx(
@@ -9178,6 +9178,26 @@ var CodeBlock = React90__default.memo(
9178
9178
  const codeRef = useRef(null);
9179
9179
  const savedScrollLeftRef = useRef(0);
9180
9180
  const [copied, setCopied] = useState(false);
9181
+ const [editableValue, setEditableValue] = useState(code);
9182
+ const [editableTextareaKey, setEditableTextareaKey] = useState(0);
9183
+ const lastPropCodeRef = useRef(code);
9184
+ const editableTextareaRef = useRef(null);
9185
+ const editableOverlayRef = useRef(null);
9186
+ useEffect(() => {
9187
+ if (code !== lastPropCodeRef.current) {
9188
+ lastPropCodeRef.current = code;
9189
+ setEditableValue(code);
9190
+ setEditableTextareaKey((k) => k + 1);
9191
+ }
9192
+ }, [code]);
9193
+ const handleEditableScroll = useCallback(() => {
9194
+ const ta = editableTextareaRef.current;
9195
+ const ov = editableOverlayRef.current;
9196
+ if (ta && ov) {
9197
+ ov.scrollTop = ta.scrollTop;
9198
+ ov.scrollLeft = ta.scrollLeft;
9199
+ }
9200
+ }, []);
9181
9201
  const isFoldable = foldableProp ?? (language === "orb" || language === "json");
9182
9202
  const [collapsed, setCollapsed] = useState(() => /* @__PURE__ */ new Set());
9183
9203
  const foldRegions = useMemo(
@@ -9338,33 +9358,106 @@ var CodeBlock = React90__default.memo(
9338
9358
  }
9339
9359
  ),
9340
9360
  editable ? (
9341
- /* GAP-51: editable mode composes the Textarea atom. Plain text editing,
9342
- no Prism highlighting overlay (follow-up). The textarea is uncontrolled
9343
- on the value side: we pass `code` as the initial value and forward
9344
- every keystroke via onChange the consumer is responsible for
9345
- debouncing and re-deriving `code` only after the user stops typing
9346
- so the cursor doesn't fight a re-render. */
9347
- /* @__PURE__ */ jsx(
9348
- Textarea,
9361
+ /* GAP-77: editable mode = transparent Textarea on top + Prism-highlighted
9362
+ overlay underneath. The textarea is uncontrolled (defaultValue + key)
9363
+ to avoid cursor jumps; the overlay reads `editableValue` which is
9364
+ mirrored from the textarea via onChange. Both elements share IDENTICAL
9365
+ font / line-height / padding so the highlighted text aligns with the
9366
+ textarea's invisible glyphs.
9367
+
9368
+ Scroll sync: the overlay has `pointer-events: none` and the textarea
9369
+ scrolls; `handleEditableScroll` keeps the overlay's scroll matched. */
9370
+ /* @__PURE__ */ jsxs(
9371
+ Box,
9349
9372
  {
9350
- defaultValue: code,
9351
- onChange: (e) => onChange?.(e.target.value),
9352
- spellCheck: false,
9353
9373
  style: {
9354
- fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace',
9355
- fontSize: "13px",
9356
- lineHeight: "1.5",
9374
+ position: "relative",
9357
9375
  backgroundColor: "#1e1e1e",
9358
- color: "#e6e6e6",
9359
9376
  borderRadius: hasHeader ? "0 0 0.5rem 0.5rem" : "0.5rem",
9360
- border: "none",
9361
- padding: "1rem",
9362
- resize: "none",
9363
9377
  minHeight: "160px",
9364
9378
  maxHeight,
9365
- width: "100%",
9366
- outline: "none"
9367
- }
9379
+ overflow: "hidden"
9380
+ },
9381
+ children: [
9382
+ /* @__PURE__ */ jsx(
9383
+ "div",
9384
+ {
9385
+ ref: editableOverlayRef,
9386
+ "aria-hidden": true,
9387
+ style: {
9388
+ position: "absolute",
9389
+ inset: 0,
9390
+ overflow: "hidden",
9391
+ pointerEvents: "none",
9392
+ maxHeight
9393
+ },
9394
+ children: /* @__PURE__ */ jsx(
9395
+ SyntaxHighlighter,
9396
+ {
9397
+ PreTag: "div",
9398
+ language,
9399
+ style: activeStyle,
9400
+ customStyle: {
9401
+ backgroundColor: "transparent",
9402
+ borderRadius: 0,
9403
+ padding: "1rem",
9404
+ margin: 0,
9405
+ whiteSpace: "pre",
9406
+ minWidth: "100%",
9407
+ minHeight: "160px",
9408
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace',
9409
+ fontSize: "13px",
9410
+ lineHeight: "1.5"
9411
+ },
9412
+ codeTagProps: {
9413
+ style: {
9414
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace',
9415
+ fontSize: "13px",
9416
+ lineHeight: "1.5"
9417
+ }
9418
+ },
9419
+ children: editableValue || " "
9420
+ }
9421
+ )
9422
+ }
9423
+ ),
9424
+ /* @__PURE__ */ jsx(
9425
+ Textarea,
9426
+ {
9427
+ ref: editableTextareaRef,
9428
+ defaultValue: code,
9429
+ onChange: (e) => {
9430
+ setEditableValue(e.target.value);
9431
+ onChange?.(e.target.value);
9432
+ },
9433
+ onScroll: handleEditableScroll,
9434
+ spellCheck: false,
9435
+ style: {
9436
+ position: "relative",
9437
+ zIndex: 1,
9438
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace',
9439
+ fontSize: "13px",
9440
+ lineHeight: "1.5",
9441
+ backgroundColor: "transparent",
9442
+ color: "transparent",
9443
+ caretColor: "#e6e6e6",
9444
+ borderRadius: hasHeader ? "0 0 0.5rem 0.5rem" : "0.5rem",
9445
+ border: "none",
9446
+ padding: "1rem",
9447
+ resize: "none",
9448
+ minHeight: "160px",
9449
+ maxHeight,
9450
+ width: "100%",
9451
+ height: "100%",
9452
+ outline: "none",
9453
+ whiteSpace: "pre",
9454
+ overflowWrap: "normal",
9455
+ overflow: "auto"
9456
+ }
9457
+ },
9458
+ editableTextareaKey
9459
+ )
9460
+ ]
9368
9461
  }
9369
9462
  )
9370
9463
  ) : /* @__PURE__ */ jsx(
@@ -24,10 +24,15 @@ export interface CodeBlockProps {
24
24
  /** Additional CSS classes */
25
25
  className?: string;
26
26
  /**
27
- * GAP-51: when true, render an editable surface (composes the `Textarea` atom)
28
- * instead of the syntax-highlighted read-only display. Folding + Prism
29
- * highlighting are skipped in editable mode for the first cut. Default: false
30
- * (existing read-only behavior unchanged).
27
+ * When true, render an editable surface that composes a transparent `Textarea`
28
+ * over a Prism-highlighted overlay. The overlay re-tokenizes on each keystroke
29
+ * (driven from a local mirror of the textarea value), so users see syntax-highlighted
30
+ * code while still being able to type. Folding is skipped in editable mode.
31
+ *
32
+ * History: GAP-51 first-cut shipped plain (no-highlighting) editable text;
33
+ * GAP-77 (2026-04-12) added the Prism overlay layer.
34
+ *
35
+ * Default: false (existing read-only behavior unchanged).
31
36
  */
32
37
  editable?: boolean;
33
38
  /**
@@ -4108,6 +4108,26 @@ var CodeBlock = React114__namespace.default.memo(
4108
4108
  const codeRef = React114.useRef(null);
4109
4109
  const savedScrollLeftRef = React114.useRef(0);
4110
4110
  const [copied, setCopied] = React114.useState(false);
4111
+ const [editableValue, setEditableValue] = React114.useState(code);
4112
+ const [editableTextareaKey, setEditableTextareaKey] = React114.useState(0);
4113
+ const lastPropCodeRef = React114.useRef(code);
4114
+ const editableTextareaRef = React114.useRef(null);
4115
+ const editableOverlayRef = React114.useRef(null);
4116
+ React114.useEffect(() => {
4117
+ if (code !== lastPropCodeRef.current) {
4118
+ lastPropCodeRef.current = code;
4119
+ setEditableValue(code);
4120
+ setEditableTextareaKey((k) => k + 1);
4121
+ }
4122
+ }, [code]);
4123
+ const handleEditableScroll = React114.useCallback(() => {
4124
+ const ta = editableTextareaRef.current;
4125
+ const ov = editableOverlayRef.current;
4126
+ if (ta && ov) {
4127
+ ov.scrollTop = ta.scrollTop;
4128
+ ov.scrollLeft = ta.scrollLeft;
4129
+ }
4130
+ }, []);
4111
4131
  const isFoldable = foldableProp ?? (language === "orb" || language === "json");
4112
4132
  const [collapsed, setCollapsed] = React114.useState(() => /* @__PURE__ */ new Set());
4113
4133
  const foldRegions = React114.useMemo(
@@ -4268,33 +4288,106 @@ var CodeBlock = React114__namespace.default.memo(
4268
4288
  }
4269
4289
  ),
4270
4290
  editable ? (
4271
- /* GAP-51: editable mode composes the Textarea atom. Plain text editing,
4272
- no Prism highlighting overlay (follow-up). The textarea is uncontrolled
4273
- on the value side: we pass `code` as the initial value and forward
4274
- every keystroke via onChange the consumer is responsible for
4275
- debouncing and re-deriving `code` only after the user stops typing
4276
- so the cursor doesn't fight a re-render. */
4277
- /* @__PURE__ */ jsxRuntime.jsx(
4278
- Textarea,
4291
+ /* GAP-77: editable mode = transparent Textarea on top + Prism-highlighted
4292
+ overlay underneath. The textarea is uncontrolled (defaultValue + key)
4293
+ to avoid cursor jumps; the overlay reads `editableValue` which is
4294
+ mirrored from the textarea via onChange. Both elements share IDENTICAL
4295
+ font / line-height / padding so the highlighted text aligns with the
4296
+ textarea's invisible glyphs.
4297
+
4298
+ Scroll sync: the overlay has `pointer-events: none` and the textarea
4299
+ scrolls; `handleEditableScroll` keeps the overlay's scroll matched. */
4300
+ /* @__PURE__ */ jsxRuntime.jsxs(
4301
+ Box,
4279
4302
  {
4280
- defaultValue: code,
4281
- onChange: (e) => onChange?.(e.target.value),
4282
- spellCheck: false,
4283
4303
  style: {
4284
- fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace',
4285
- fontSize: "13px",
4286
- lineHeight: "1.5",
4304
+ position: "relative",
4287
4305
  backgroundColor: "#1e1e1e",
4288
- color: "#e6e6e6",
4289
4306
  borderRadius: hasHeader ? "0 0 0.5rem 0.5rem" : "0.5rem",
4290
- border: "none",
4291
- padding: "1rem",
4292
- resize: "none",
4293
4307
  minHeight: "160px",
4294
4308
  maxHeight,
4295
- width: "100%",
4296
- outline: "none"
4297
- }
4309
+ overflow: "hidden"
4310
+ },
4311
+ children: [
4312
+ /* @__PURE__ */ jsxRuntime.jsx(
4313
+ "div",
4314
+ {
4315
+ ref: editableOverlayRef,
4316
+ "aria-hidden": true,
4317
+ style: {
4318
+ position: "absolute",
4319
+ inset: 0,
4320
+ overflow: "hidden",
4321
+ pointerEvents: "none",
4322
+ maxHeight
4323
+ },
4324
+ children: /* @__PURE__ */ jsxRuntime.jsx(
4325
+ SyntaxHighlighter__default.default,
4326
+ {
4327
+ PreTag: "div",
4328
+ language,
4329
+ style: activeStyle,
4330
+ customStyle: {
4331
+ backgroundColor: "transparent",
4332
+ borderRadius: 0,
4333
+ padding: "1rem",
4334
+ margin: 0,
4335
+ whiteSpace: "pre",
4336
+ minWidth: "100%",
4337
+ minHeight: "160px",
4338
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace',
4339
+ fontSize: "13px",
4340
+ lineHeight: "1.5"
4341
+ },
4342
+ codeTagProps: {
4343
+ style: {
4344
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace',
4345
+ fontSize: "13px",
4346
+ lineHeight: "1.5"
4347
+ }
4348
+ },
4349
+ children: editableValue || " "
4350
+ }
4351
+ )
4352
+ }
4353
+ ),
4354
+ /* @__PURE__ */ jsxRuntime.jsx(
4355
+ Textarea,
4356
+ {
4357
+ ref: editableTextareaRef,
4358
+ defaultValue: code,
4359
+ onChange: (e) => {
4360
+ setEditableValue(e.target.value);
4361
+ onChange?.(e.target.value);
4362
+ },
4363
+ onScroll: handleEditableScroll,
4364
+ spellCheck: false,
4365
+ style: {
4366
+ position: "relative",
4367
+ zIndex: 1,
4368
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace',
4369
+ fontSize: "13px",
4370
+ lineHeight: "1.5",
4371
+ backgroundColor: "transparent",
4372
+ color: "transparent",
4373
+ caretColor: "#e6e6e6",
4374
+ borderRadius: hasHeader ? "0 0 0.5rem 0.5rem" : "0.5rem",
4375
+ border: "none",
4376
+ padding: "1rem",
4377
+ resize: "none",
4378
+ minHeight: "160px",
4379
+ maxHeight,
4380
+ width: "100%",
4381
+ height: "100%",
4382
+ outline: "none",
4383
+ whiteSpace: "pre",
4384
+ overflowWrap: "normal",
4385
+ overflow: "auto"
4386
+ }
4387
+ },
4388
+ editableTextareaKey
4389
+ )
4390
+ ]
4298
4391
  }
4299
4392
  )
4300
4393
  ) : /* @__PURE__ */ jsxRuntime.jsx(
@@ -4063,6 +4063,26 @@ var CodeBlock = React114__default.memo(
4063
4063
  const codeRef = useRef(null);
4064
4064
  const savedScrollLeftRef = useRef(0);
4065
4065
  const [copied, setCopied] = useState(false);
4066
+ const [editableValue, setEditableValue] = useState(code);
4067
+ const [editableTextareaKey, setEditableTextareaKey] = useState(0);
4068
+ const lastPropCodeRef = useRef(code);
4069
+ const editableTextareaRef = useRef(null);
4070
+ const editableOverlayRef = useRef(null);
4071
+ useEffect(() => {
4072
+ if (code !== lastPropCodeRef.current) {
4073
+ lastPropCodeRef.current = code;
4074
+ setEditableValue(code);
4075
+ setEditableTextareaKey((k) => k + 1);
4076
+ }
4077
+ }, [code]);
4078
+ const handleEditableScroll = useCallback(() => {
4079
+ const ta = editableTextareaRef.current;
4080
+ const ov = editableOverlayRef.current;
4081
+ if (ta && ov) {
4082
+ ov.scrollTop = ta.scrollTop;
4083
+ ov.scrollLeft = ta.scrollLeft;
4084
+ }
4085
+ }, []);
4066
4086
  const isFoldable = foldableProp ?? (language === "orb" || language === "json");
4067
4087
  const [collapsed, setCollapsed] = useState(() => /* @__PURE__ */ new Set());
4068
4088
  const foldRegions = useMemo(
@@ -4223,33 +4243,106 @@ var CodeBlock = React114__default.memo(
4223
4243
  }
4224
4244
  ),
4225
4245
  editable ? (
4226
- /* GAP-51: editable mode composes the Textarea atom. Plain text editing,
4227
- no Prism highlighting overlay (follow-up). The textarea is uncontrolled
4228
- on the value side: we pass `code` as the initial value and forward
4229
- every keystroke via onChange the consumer is responsible for
4230
- debouncing and re-deriving `code` only after the user stops typing
4231
- so the cursor doesn't fight a re-render. */
4232
- /* @__PURE__ */ jsx(
4233
- Textarea,
4246
+ /* GAP-77: editable mode = transparent Textarea on top + Prism-highlighted
4247
+ overlay underneath. The textarea is uncontrolled (defaultValue + key)
4248
+ to avoid cursor jumps; the overlay reads `editableValue` which is
4249
+ mirrored from the textarea via onChange. Both elements share IDENTICAL
4250
+ font / line-height / padding so the highlighted text aligns with the
4251
+ textarea's invisible glyphs.
4252
+
4253
+ Scroll sync: the overlay has `pointer-events: none` and the textarea
4254
+ scrolls; `handleEditableScroll` keeps the overlay's scroll matched. */
4255
+ /* @__PURE__ */ jsxs(
4256
+ Box,
4234
4257
  {
4235
- defaultValue: code,
4236
- onChange: (e) => onChange?.(e.target.value),
4237
- spellCheck: false,
4238
4258
  style: {
4239
- fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace',
4240
- fontSize: "13px",
4241
- lineHeight: "1.5",
4259
+ position: "relative",
4242
4260
  backgroundColor: "#1e1e1e",
4243
- color: "#e6e6e6",
4244
4261
  borderRadius: hasHeader ? "0 0 0.5rem 0.5rem" : "0.5rem",
4245
- border: "none",
4246
- padding: "1rem",
4247
- resize: "none",
4248
4262
  minHeight: "160px",
4249
4263
  maxHeight,
4250
- width: "100%",
4251
- outline: "none"
4252
- }
4264
+ overflow: "hidden"
4265
+ },
4266
+ children: [
4267
+ /* @__PURE__ */ jsx(
4268
+ "div",
4269
+ {
4270
+ ref: editableOverlayRef,
4271
+ "aria-hidden": true,
4272
+ style: {
4273
+ position: "absolute",
4274
+ inset: 0,
4275
+ overflow: "hidden",
4276
+ pointerEvents: "none",
4277
+ maxHeight
4278
+ },
4279
+ children: /* @__PURE__ */ jsx(
4280
+ SyntaxHighlighter,
4281
+ {
4282
+ PreTag: "div",
4283
+ language,
4284
+ style: activeStyle,
4285
+ customStyle: {
4286
+ backgroundColor: "transparent",
4287
+ borderRadius: 0,
4288
+ padding: "1rem",
4289
+ margin: 0,
4290
+ whiteSpace: "pre",
4291
+ minWidth: "100%",
4292
+ minHeight: "160px",
4293
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace',
4294
+ fontSize: "13px",
4295
+ lineHeight: "1.5"
4296
+ },
4297
+ codeTagProps: {
4298
+ style: {
4299
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace',
4300
+ fontSize: "13px",
4301
+ lineHeight: "1.5"
4302
+ }
4303
+ },
4304
+ children: editableValue || " "
4305
+ }
4306
+ )
4307
+ }
4308
+ ),
4309
+ /* @__PURE__ */ jsx(
4310
+ Textarea,
4311
+ {
4312
+ ref: editableTextareaRef,
4313
+ defaultValue: code,
4314
+ onChange: (e) => {
4315
+ setEditableValue(e.target.value);
4316
+ onChange?.(e.target.value);
4317
+ },
4318
+ onScroll: handleEditableScroll,
4319
+ spellCheck: false,
4320
+ style: {
4321
+ position: "relative",
4322
+ zIndex: 1,
4323
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace',
4324
+ fontSize: "13px",
4325
+ lineHeight: "1.5",
4326
+ backgroundColor: "transparent",
4327
+ color: "transparent",
4328
+ caretColor: "#e6e6e6",
4329
+ borderRadius: hasHeader ? "0 0 0.5rem 0.5rem" : "0.5rem",
4330
+ border: "none",
4331
+ padding: "1rem",
4332
+ resize: "none",
4333
+ minHeight: "160px",
4334
+ maxHeight,
4335
+ width: "100%",
4336
+ height: "100%",
4337
+ outline: "none",
4338
+ whiteSpace: "pre",
4339
+ overflowWrap: "normal",
4340
+ overflow: "auto"
4341
+ }
4342
+ },
4343
+ editableTextareaKey
4344
+ )
4345
+ ]
4253
4346
  }
4254
4347
  )
4255
4348
  ) : /* @__PURE__ */ jsx(
@@ -10028,6 +10028,26 @@ var CodeBlock = React114__namespace.default.memo(
10028
10028
  const codeRef = React114.useRef(null);
10029
10029
  const savedScrollLeftRef = React114.useRef(0);
10030
10030
  const [copied, setCopied] = React114.useState(false);
10031
+ const [editableValue, setEditableValue] = React114.useState(code);
10032
+ const [editableTextareaKey, setEditableTextareaKey] = React114.useState(0);
10033
+ const lastPropCodeRef = React114.useRef(code);
10034
+ const editableTextareaRef = React114.useRef(null);
10035
+ const editableOverlayRef = React114.useRef(null);
10036
+ React114.useEffect(() => {
10037
+ if (code !== lastPropCodeRef.current) {
10038
+ lastPropCodeRef.current = code;
10039
+ setEditableValue(code);
10040
+ setEditableTextareaKey((k) => k + 1);
10041
+ }
10042
+ }, [code]);
10043
+ const handleEditableScroll = React114.useCallback(() => {
10044
+ const ta = editableTextareaRef.current;
10045
+ const ov = editableOverlayRef.current;
10046
+ if (ta && ov) {
10047
+ ov.scrollTop = ta.scrollTop;
10048
+ ov.scrollLeft = ta.scrollLeft;
10049
+ }
10050
+ }, []);
10031
10051
  const isFoldable = foldableProp ?? (language === "orb" || language === "json");
10032
10052
  const [collapsed, setCollapsed] = React114.useState(() => /* @__PURE__ */ new Set());
10033
10053
  const foldRegions = React114.useMemo(
@@ -10188,33 +10208,106 @@ var CodeBlock = React114__namespace.default.memo(
10188
10208
  }
10189
10209
  ),
10190
10210
  editable ? (
10191
- /* GAP-51: editable mode composes the Textarea atom. Plain text editing,
10192
- no Prism highlighting overlay (follow-up). The textarea is uncontrolled
10193
- on the value side: we pass `code` as the initial value and forward
10194
- every keystroke via onChange the consumer is responsible for
10195
- debouncing and re-deriving `code` only after the user stops typing
10196
- so the cursor doesn't fight a re-render. */
10197
- /* @__PURE__ */ jsxRuntime.jsx(
10198
- Textarea,
10211
+ /* GAP-77: editable mode = transparent Textarea on top + Prism-highlighted
10212
+ overlay underneath. The textarea is uncontrolled (defaultValue + key)
10213
+ to avoid cursor jumps; the overlay reads `editableValue` which is
10214
+ mirrored from the textarea via onChange. Both elements share IDENTICAL
10215
+ font / line-height / padding so the highlighted text aligns with the
10216
+ textarea's invisible glyphs.
10217
+
10218
+ Scroll sync: the overlay has `pointer-events: none` and the textarea
10219
+ scrolls; `handleEditableScroll` keeps the overlay's scroll matched. */
10220
+ /* @__PURE__ */ jsxRuntime.jsxs(
10221
+ Box,
10199
10222
  {
10200
- defaultValue: code,
10201
- onChange: (e) => onChange?.(e.target.value),
10202
- spellCheck: false,
10203
10223
  style: {
10204
- fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace',
10205
- fontSize: "13px",
10206
- lineHeight: "1.5",
10224
+ position: "relative",
10207
10225
  backgroundColor: "#1e1e1e",
10208
- color: "#e6e6e6",
10209
10226
  borderRadius: hasHeader ? "0 0 0.5rem 0.5rem" : "0.5rem",
10210
- border: "none",
10211
- padding: "1rem",
10212
- resize: "none",
10213
10227
  minHeight: "160px",
10214
10228
  maxHeight,
10215
- width: "100%",
10216
- outline: "none"
10217
- }
10229
+ overflow: "hidden"
10230
+ },
10231
+ children: [
10232
+ /* @__PURE__ */ jsxRuntime.jsx(
10233
+ "div",
10234
+ {
10235
+ ref: editableOverlayRef,
10236
+ "aria-hidden": true,
10237
+ style: {
10238
+ position: "absolute",
10239
+ inset: 0,
10240
+ overflow: "hidden",
10241
+ pointerEvents: "none",
10242
+ maxHeight
10243
+ },
10244
+ children: /* @__PURE__ */ jsxRuntime.jsx(
10245
+ SyntaxHighlighter__default.default,
10246
+ {
10247
+ PreTag: "div",
10248
+ language,
10249
+ style: activeStyle,
10250
+ customStyle: {
10251
+ backgroundColor: "transparent",
10252
+ borderRadius: 0,
10253
+ padding: "1rem",
10254
+ margin: 0,
10255
+ whiteSpace: "pre",
10256
+ minWidth: "100%",
10257
+ minHeight: "160px",
10258
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace',
10259
+ fontSize: "13px",
10260
+ lineHeight: "1.5"
10261
+ },
10262
+ codeTagProps: {
10263
+ style: {
10264
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace',
10265
+ fontSize: "13px",
10266
+ lineHeight: "1.5"
10267
+ }
10268
+ },
10269
+ children: editableValue || " "
10270
+ }
10271
+ )
10272
+ }
10273
+ ),
10274
+ /* @__PURE__ */ jsxRuntime.jsx(
10275
+ Textarea,
10276
+ {
10277
+ ref: editableTextareaRef,
10278
+ defaultValue: code,
10279
+ onChange: (e) => {
10280
+ setEditableValue(e.target.value);
10281
+ onChange?.(e.target.value);
10282
+ },
10283
+ onScroll: handleEditableScroll,
10284
+ spellCheck: false,
10285
+ style: {
10286
+ position: "relative",
10287
+ zIndex: 1,
10288
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace',
10289
+ fontSize: "13px",
10290
+ lineHeight: "1.5",
10291
+ backgroundColor: "transparent",
10292
+ color: "transparent",
10293
+ caretColor: "#e6e6e6",
10294
+ borderRadius: hasHeader ? "0 0 0.5rem 0.5rem" : "0.5rem",
10295
+ border: "none",
10296
+ padding: "1rem",
10297
+ resize: "none",
10298
+ minHeight: "160px",
10299
+ maxHeight,
10300
+ width: "100%",
10301
+ height: "100%",
10302
+ outline: "none",
10303
+ whiteSpace: "pre",
10304
+ overflowWrap: "normal",
10305
+ overflow: "auto"
10306
+ }
10307
+ },
10308
+ editableTextareaKey
10309
+ )
10310
+ ]
10218
10311
  }
10219
10312
  )
10220
10313
  ) : /* @__PURE__ */ jsxRuntime.jsx(
@@ -9983,6 +9983,26 @@ var CodeBlock = React114__default.memo(
9983
9983
  const codeRef = useRef(null);
9984
9984
  const savedScrollLeftRef = useRef(0);
9985
9985
  const [copied, setCopied] = useState(false);
9986
+ const [editableValue, setEditableValue] = useState(code);
9987
+ const [editableTextareaKey, setEditableTextareaKey] = useState(0);
9988
+ const lastPropCodeRef = useRef(code);
9989
+ const editableTextareaRef = useRef(null);
9990
+ const editableOverlayRef = useRef(null);
9991
+ useEffect(() => {
9992
+ if (code !== lastPropCodeRef.current) {
9993
+ lastPropCodeRef.current = code;
9994
+ setEditableValue(code);
9995
+ setEditableTextareaKey((k) => k + 1);
9996
+ }
9997
+ }, [code]);
9998
+ const handleEditableScroll = useCallback(() => {
9999
+ const ta = editableTextareaRef.current;
10000
+ const ov = editableOverlayRef.current;
10001
+ if (ta && ov) {
10002
+ ov.scrollTop = ta.scrollTop;
10003
+ ov.scrollLeft = ta.scrollLeft;
10004
+ }
10005
+ }, []);
9986
10006
  const isFoldable = foldableProp ?? (language === "orb" || language === "json");
9987
10007
  const [collapsed, setCollapsed] = useState(() => /* @__PURE__ */ new Set());
9988
10008
  const foldRegions = useMemo(
@@ -10143,33 +10163,106 @@ var CodeBlock = React114__default.memo(
10143
10163
  }
10144
10164
  ),
10145
10165
  editable ? (
10146
- /* GAP-51: editable mode composes the Textarea atom. Plain text editing,
10147
- no Prism highlighting overlay (follow-up). The textarea is uncontrolled
10148
- on the value side: we pass `code` as the initial value and forward
10149
- every keystroke via onChange the consumer is responsible for
10150
- debouncing and re-deriving `code` only after the user stops typing
10151
- so the cursor doesn't fight a re-render. */
10152
- /* @__PURE__ */ jsx(
10153
- Textarea,
10166
+ /* GAP-77: editable mode = transparent Textarea on top + Prism-highlighted
10167
+ overlay underneath. The textarea is uncontrolled (defaultValue + key)
10168
+ to avoid cursor jumps; the overlay reads `editableValue` which is
10169
+ mirrored from the textarea via onChange. Both elements share IDENTICAL
10170
+ font / line-height / padding so the highlighted text aligns with the
10171
+ textarea's invisible glyphs.
10172
+
10173
+ Scroll sync: the overlay has `pointer-events: none` and the textarea
10174
+ scrolls; `handleEditableScroll` keeps the overlay's scroll matched. */
10175
+ /* @__PURE__ */ jsxs(
10176
+ Box,
10154
10177
  {
10155
- defaultValue: code,
10156
- onChange: (e) => onChange?.(e.target.value),
10157
- spellCheck: false,
10158
10178
  style: {
10159
- fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace',
10160
- fontSize: "13px",
10161
- lineHeight: "1.5",
10179
+ position: "relative",
10162
10180
  backgroundColor: "#1e1e1e",
10163
- color: "#e6e6e6",
10164
10181
  borderRadius: hasHeader ? "0 0 0.5rem 0.5rem" : "0.5rem",
10165
- border: "none",
10166
- padding: "1rem",
10167
- resize: "none",
10168
10182
  minHeight: "160px",
10169
10183
  maxHeight,
10170
- width: "100%",
10171
- outline: "none"
10172
- }
10184
+ overflow: "hidden"
10185
+ },
10186
+ children: [
10187
+ /* @__PURE__ */ jsx(
10188
+ "div",
10189
+ {
10190
+ ref: editableOverlayRef,
10191
+ "aria-hidden": true,
10192
+ style: {
10193
+ position: "absolute",
10194
+ inset: 0,
10195
+ overflow: "hidden",
10196
+ pointerEvents: "none",
10197
+ maxHeight
10198
+ },
10199
+ children: /* @__PURE__ */ jsx(
10200
+ SyntaxHighlighter,
10201
+ {
10202
+ PreTag: "div",
10203
+ language,
10204
+ style: activeStyle,
10205
+ customStyle: {
10206
+ backgroundColor: "transparent",
10207
+ borderRadius: 0,
10208
+ padding: "1rem",
10209
+ margin: 0,
10210
+ whiteSpace: "pre",
10211
+ minWidth: "100%",
10212
+ minHeight: "160px",
10213
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace',
10214
+ fontSize: "13px",
10215
+ lineHeight: "1.5"
10216
+ },
10217
+ codeTagProps: {
10218
+ style: {
10219
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace',
10220
+ fontSize: "13px",
10221
+ lineHeight: "1.5"
10222
+ }
10223
+ },
10224
+ children: editableValue || " "
10225
+ }
10226
+ )
10227
+ }
10228
+ ),
10229
+ /* @__PURE__ */ jsx(
10230
+ Textarea,
10231
+ {
10232
+ ref: editableTextareaRef,
10233
+ defaultValue: code,
10234
+ onChange: (e) => {
10235
+ setEditableValue(e.target.value);
10236
+ onChange?.(e.target.value);
10237
+ },
10238
+ onScroll: handleEditableScroll,
10239
+ spellCheck: false,
10240
+ style: {
10241
+ position: "relative",
10242
+ zIndex: 1,
10243
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace',
10244
+ fontSize: "13px",
10245
+ lineHeight: "1.5",
10246
+ backgroundColor: "transparent",
10247
+ color: "transparent",
10248
+ caretColor: "#e6e6e6",
10249
+ borderRadius: hasHeader ? "0 0 0.5rem 0.5rem" : "0.5rem",
10250
+ border: "none",
10251
+ padding: "1rem",
10252
+ resize: "none",
10253
+ minHeight: "160px",
10254
+ maxHeight,
10255
+ width: "100%",
10256
+ height: "100%",
10257
+ outline: "none",
10258
+ whiteSpace: "pre",
10259
+ overflowWrap: "normal",
10260
+ overflow: "auto"
10261
+ }
10262
+ },
10263
+ editableTextareaKey
10264
+ )
10265
+ ]
10173
10266
  }
10174
10267
  )
10175
10268
  ) : /* @__PURE__ */ jsx(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/ui",
3
- "version": "2.52.0",
3
+ "version": "2.53.0",
4
4
  "description": "React UI components, hooks, and providers for Almadar",
5
5
  "type": "module",
6
6
  "main": "./dist/components/index.js",