@jsenv/navi 0.28.8 → 0.28.10

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.
@@ -5353,10 +5353,12 @@ const stateSignal = (defaultValue, options = {}) => {
5353
5353
  // Convert numeric IDs to strings for consistency
5354
5354
  const signalIdString = String(signalId);
5355
5355
  if (globalSignalRegistry.has(signalIdString)) {
5356
- const conflictInfo = globalSignalRegistry.get(signalIdString);
5357
- throw new Error(
5358
- `Signal ID conflict: A signal with ID "${signalIdString}" already exists (existing default: ${conflictInfo.options.getDefaultValue()})`,
5359
- );
5356
+ const existingEntry = globalSignalRegistry.get(signalIdString);
5357
+ {
5358
+ throw new Error(
5359
+ `Signal ID conflict: A signal with ID "${signalIdString}" already exists (existing default: ${existingEntry.options.getDefaultValue()})`,
5360
+ );
5361
+ }
5360
5362
  }
5361
5363
 
5362
5364
  // Determine localStorage key: use id if persists=true, or legacy localStorage option
@@ -5481,7 +5483,12 @@ const stateSignal = (defaultValue, options = {}) => {
5481
5483
  // can reflect the current input state without silently correcting it.
5482
5484
  return validity.value;
5483
5485
  };
5484
- const preactSignal = signal(processValue(getFallbackValue()));
5486
+ const preactSignal = signal(
5487
+ processValue(
5488
+ getFallbackValue()
5489
+ ,
5490
+ ),
5491
+ );
5485
5492
 
5486
5493
  // Override the value setter on the instance to intercept writes and apply processValue.
5487
5494
  // We do this on the instance (not the prototype) so preactSignal remains a real Signal
@@ -6514,6 +6521,13 @@ const DIMENSION_PROPS = {
6514
6521
  const inHorizontalFlexFlow =
6515
6522
  parentBoxFlow === "flex-x" || parentBoxFlow === "inline-flex-x";
6516
6523
  if (inHorizontalFlexFlow) {
6524
+ if (value === "content") {
6525
+ // flex-basis stays auto: the item still takes the free space, but its
6526
+ // content size is what line-breaking sees — so in a flexWrap parent a
6527
+ // neighbor that no longer fits wraps to the next line. With basis 0%
6528
+ // (below) nothing ever wraps: every item claims a size of zero.
6529
+ return { flexGrow: 1 };
6530
+ }
6517
6531
  // Parent is flex-x: grow as flex item
6518
6532
  return { flexGrow: 1, flexBasis: "0%" };
6519
6533
  }
@@ -6533,6 +6547,11 @@ const DIMENSION_PROPS = {
6533
6547
  const inVerticalFlexFlow =
6534
6548
  parentBoxFlow === "flex-y" || parentBoxFlow === "inline-flex-y";
6535
6549
  if (inVerticalFlexFlow) {
6550
+ if (value === "content") {
6551
+ // Same as expandX="content": grow from the content size, so a
6552
+ // flexWrap parent can wrap.
6553
+ return { flexGrow: 1 };
6554
+ }
6536
6555
  // Parent is flex-y: grow as flex item
6537
6556
  return { flexGrow: 1, flexBasis: "0%" };
6538
6557
  }
@@ -17428,8 +17447,12 @@ const canContainCallout = element => {
17428
17447
  if (VOID_ELEMENT_TAG_NAMES.has(element.tagName)) {
17429
17448
  return false;
17430
17449
  }
17431
- if (element.tagName === "BUTTON") {
17432
- // callout itself contains a button, browser would not let that happen
17450
+ if (element.tagName === "BUTTON" || element.getAttribute("role") === "button") {
17451
+ // Never mount the callout inside a button, native or role="button":
17452
+ // - a <button> cannot contain the callout's own close button
17453
+ // - presses inside the callout would bubble into the button's own press
17454
+ // handlers: a preventDefault there makes the callout text unselectable,
17455
+ // and a handler that opens the callout re-opens it (visible blink)
17433
17456
  return false;
17434
17457
  }
17435
17458
  return true;
@@ -35063,7 +35086,7 @@ installImportMetaCssBuild(import.meta);/**
35063
35086
  * - variant="discrete" + backgroundColor: the color applies at rest and hover,
35064
35087
  * and the field goes transparent while focused.
35065
35088
  */
35066
- const css$D = /* css */`
35089
+ const inputCss = /* css */`
35067
35090
  @layer navi {
35068
35091
  .navi_input {
35069
35092
  --border-radius: var(--navi-control-border-radius);
@@ -35394,7 +35417,7 @@ const useInputTextualProps = props => {
35394
35417
  });
35395
35418
  };
35396
35419
  const InputTextualUI = props => {
35397
- import.meta.css = [css$D, "@jsenv/navi/src/control/input/input_textual.jsx"];
35420
+ import.meta.css = [inputCss, "@jsenv/navi/src/control/input/input_textual.jsx"];
35398
35421
  // Spacing props travel to CSS as a raw custom property value, so the size
35399
35422
  // keywords have to become lengths here — "s" reaching CSS untouched makes the
35400
35423
  // declaration invalid, silently, and the gap just goes away.
@@ -35513,6 +35536,9 @@ const RealInput = ({
35513
35536
  "navi-max-length": maxLength
35514
35537
  });
35515
35538
  };
35539
+
35540
+ // Shared with textarea.jsx: a textarea is styled as a .navi_input box, so the
35541
+ // two read the same style props and pseudo states.
35516
35542
  const InputStyleCSSVars = {
35517
35543
  "slotSpacing": "--slot-spacing",
35518
35544
  "outlineWidth": "--outline-width",
@@ -36051,7 +36077,7 @@ installImportMetaCssBuild(import.meta);/**
36051
36077
  * This means an editable thing MUST have a parent with position relative that wraps the content and the eventual editable input
36052
36078
  *
36053
36079
  */
36054
- const css$C = /* css */`
36080
+ const css$D = /* css */`
36055
36081
  .navi_editable_wrapper {
36056
36082
  --inset-top: 0px;
36057
36083
  --inset-right: 0px;
@@ -36100,7 +36126,7 @@ const useEditionController = () => {
36100
36126
  };
36101
36127
  };
36102
36128
  const Editable = props => {
36103
- import.meta.css = [css$C, "@jsenv/navi/src/control/edition/editable.jsx"];
36129
+ import.meta.css = [css$D, "@jsenv/navi/src/control/edition/editable.jsx"];
36104
36130
  let {
36105
36131
  children,
36106
36132
  action,
@@ -36514,7 +36540,7 @@ HTMLFormElement.prototype.requestSubmit = function (submitter) {
36514
36540
  // form.dispatchEvent(customEvent);
36515
36541
  // };
36516
36542
 
36517
- installImportMetaCssBuild(import.meta);const css$B = /* css */`
36543
+ installImportMetaCssBuild(import.meta);const css$C = /* css */`
36518
36544
  .navi_group {
36519
36545
  --group-border-width: 1px;
36520
36546
 
@@ -36610,7 +36636,7 @@ const Group = ({
36610
36636
  vertical = row,
36611
36637
  ...props
36612
36638
  }) => {
36613
- import.meta.css = [css$B, "@jsenv/navi/src/control/group.jsx"];
36639
+ import.meta.css = [css$C, "@jsenv/navi/src/control/group.jsx"];
36614
36640
  return jsx(Box, {
36615
36641
  baseClassName: "navi_group",
36616
36642
  "data-vertical": vertical ? "" : undefined,
@@ -37020,7 +37046,7 @@ installImportMetaCssBuild(import.meta);/**
37020
37046
  * pair. One popup holding slides of its own contents has no such problem — and
37021
37047
  * it is the same component in the document, in a dialog or in a popover.
37022
37048
  */
37023
- const css$A = /* css */`
37049
+ const css$B = /* css */`
37024
37050
  /* Every slide in the same grid cell: the box then measures itself on the
37025
37051
  LARGEST of them, in both directions, without anything being measured by
37026
37052
  hand — which is also why nothing here resizes as the slides change. Each
@@ -37318,7 +37344,7 @@ const SlideContainer = ({
37318
37344
  children,
37319
37345
  ...rest
37320
37346
  }) => {
37321
- import.meta.css = [css$A, "@jsenv/navi/src/layout/slide_container.jsx"];
37347
+ import.meta.css = [css$B, "@jsenv/navi/src/layout/slide_container.jsx"];
37322
37348
  const debugFocus = useDebugFocus();
37323
37349
  const trackRef = useRef();
37324
37350
  // The box itself: it is what takes the keyboard when what is on screen holds
@@ -39918,7 +39944,7 @@ installImportMetaCssBuild(import.meta);/**
39918
39944
  * reaches the real container.
39919
39945
  */
39920
39946
  let openLocalDialogCount = 0;
39921
- const css$z = /* css */`
39947
+ const css$A = /* css */`
39922
39948
  @layer navi {
39923
39949
  .navi_dialog {
39924
39950
  /* Min gap between the dialog and the edges of its container. Written
@@ -40325,7 +40351,7 @@ const css$z = /* css */`
40325
40351
  * @param {import("ignore:preact").ComponentChildren} props.children
40326
40352
  */
40327
40353
  const Dialog = props => {
40328
- import.meta.css = [css$z, "@jsenv/navi/src/layout/dialog.jsx"];
40354
+ import.meta.css = [css$A, "@jsenv/navi/src/layout/dialog.jsx"];
40329
40355
  if (props.openController) {
40330
40356
  return jsx(ControlledDialog, {
40331
40357
  ...props
@@ -41143,7 +41169,7 @@ installImportMetaCssBuild(import.meta);/**
41143
41169
  * and applied.
41144
41170
  */
41145
41171
  let openLocalPopoverCount = 0;
41146
- const css$y = /* css */`
41172
+ const css$z = /* css */`
41147
41173
  @layer navi {
41148
41174
  .navi_popover {
41149
41175
  /* soft: user-configurable preferred max-height. Kept as a *default*
@@ -41510,7 +41536,7 @@ const css$y = /* css */`
41510
41536
  * @param {import("ignore:preact").ComponentChildren} props.children
41511
41537
  */
41512
41538
  const Popover = props => {
41513
- import.meta.css = [css$y, "@jsenv/navi/src/layout/popover.jsx"];
41539
+ import.meta.css = [css$z, "@jsenv/navi/src/layout/popover.jsx"];
41514
41540
  if (props.openController) {
41515
41541
  return jsx(ControlledPopover, {
41516
41542
  ...props
@@ -42474,7 +42500,7 @@ installImportMetaCssBuild(import.meta);/**
42474
42500
  * pass through untouched via `...rest` to whichever of Popover/Dialog
42475
42501
  * actually renders.
42476
42502
  */
42477
- const css$x = /* css */`
42503
+ const css$y = /* css */`
42478
42504
  @layer navi {
42479
42505
  .navi_popup {
42480
42506
  --popup-border-radius: var(--navi-popup-border-radius);
@@ -42568,7 +42594,7 @@ const css$x = /* css */`
42568
42594
  * @param {import("ignore:preact").ComponentChildren} props.children
42569
42595
  */
42570
42596
  const Popup = props => {
42571
- import.meta.css = [css$x, "@jsenv/navi/src/layout/popup.jsx"];
42597
+ import.meta.css = [css$y, "@jsenv/navi/src/layout/popup.jsx"];
42572
42598
  const {
42573
42599
  mode: modeProp,
42574
42600
  maxWidth,
@@ -42711,7 +42737,7 @@ const commitSubtree = (controller, e) => {
42711
42737
  }
42712
42738
  };
42713
42739
 
42714
- installImportMetaCssBuild(import.meta);const css$w = /* css */`
42740
+ installImportMetaCssBuild(import.meta);const css$x = /* css */`
42715
42741
  .navi_picker {
42716
42742
  /* Sizing ceilings (maxmax), background, box-shadow, outline, padding,
42717
42743
  overflow... are already handled correctly by Popup/Popover/Dialog
@@ -42823,7 +42849,7 @@ installImportMetaCssBuild(import.meta);const css$w = /* css */`
42823
42849
  }
42824
42850
  `;
42825
42851
  const PickerCustomResolver = props => {
42826
- import.meta.css = [css$w, "@jsenv/navi/src/control/picker/picker_custom.jsx"];
42852
+ import.meta.css = [css$x, "@jsenv/navi/src/control/picker/picker_custom.jsx"];
42827
42853
  if (props.children === undefined) {
42828
42854
  return jsx(PickerNative, {
42829
42855
  ...props
@@ -43468,7 +43494,7 @@ const LoadingIndicator = ({
43468
43494
  });
43469
43495
  };
43470
43496
 
43471
- installImportMetaCssBuild(import.meta);const css$v = /* css */`
43497
+ installImportMetaCssBuild(import.meta);const css$w = /* css */`
43472
43498
  @layer navi {
43473
43499
  .navi_separator {
43474
43500
  --size: 1px;
@@ -43546,7 +43572,7 @@ const Separator = ({
43546
43572
  style,
43547
43573
  ...props
43548
43574
  }) => {
43549
- import.meta.css = [css$v, "@jsenv/navi/src/layout/separator.jsx"];
43575
+ import.meta.css = [css$w, "@jsenv/navi/src/layout/separator.jsx"];
43550
43576
  return jsx(Box, {
43551
43577
  as: vertical ? "span" : "hr",
43552
43578
  ...props,
@@ -44003,7 +44029,7 @@ const ListItemFooter = props => {
44003
44029
  });
44004
44030
  };
44005
44031
 
44006
- installImportMetaCssBuild(import.meta);const css$u = /* css */`
44032
+ installImportMetaCssBuild(import.meta);const css$v = /* css */`
44007
44033
  @layer navi {
44008
44034
  .navi_list_container[navi-selectable] {
44009
44035
  /* Focus outline */
@@ -44207,7 +44233,7 @@ const ListSelectableResolver = props => {
44207
44233
  };
44208
44234
  const ListSelectable = props => {
44209
44235
  const Next = useNextResolver();
44210
- import.meta.css = [css$u, "@jsenv/navi/src/control/list/list_selectable.jsx"];
44236
+ import.meta.css = [css$v, "@jsenv/navi/src/control/list/list_selectable.jsx"];
44211
44237
  // we allow ourselves to auto-generate a name
44212
44238
  const defaultName = useId();
44213
44239
  props.name = props.name || `listbox_${defaultName}`;
@@ -44749,7 +44775,7 @@ const SeparatorContext = createContext(null);
44749
44775
  // Set by <List itemTransition>: each row then gets a view-transition-name of
44750
44776
  // its own, so a change wrapped in a view transition animates row by row.
44751
44777
  const ItemTransitionContext = createContext(false);
44752
- const css$t = /* css */`
44778
+ const css$u = /* css */`
44753
44779
  @layer navi {
44754
44780
  .navi_list_container {
44755
44781
  --list-outline-width: 1px;
@@ -45237,7 +45263,7 @@ const css$t = /* css */`
45237
45263
  }
45238
45264
  `;
45239
45265
  const ListUI = props => {
45240
- import.meta.css = [css$t, "@jsenv/navi/src/control/list/list.jsx"];
45266
+ import.meta.css = [css$u, "@jsenv/navi/src/control/list/list.jsx"];
45241
45267
  const {
45242
45268
  ref,
45243
45269
  renderBudget: renderBudgetProp = RENDER_BUDGET_DEFAULT,
@@ -46802,7 +46828,7 @@ const PickerPresetResolver = props => {
46802
46828
  });
46803
46829
  };
46804
46830
 
46805
- installImportMetaCssBuild(import.meta);const css$s = /* css */`
46831
+ installImportMetaCssBuild(import.meta);const css$t = /* css */`
46806
46832
  @layer navi {
46807
46833
  }
46808
46834
  .navi_badge {
@@ -46881,7 +46907,7 @@ const Badge = ({
46881
46907
  className,
46882
46908
  ...props
46883
46909
  }) => {
46884
- import.meta.css = [css$s, "@jsenv/navi/src/text/badge.jsx"];
46910
+ import.meta.css = [css$t, "@jsenv/navi/src/text/badge.jsx"];
46885
46911
  const defaultRef = useRef();
46886
46912
  props.ref = props.ref || defaultRef;
46887
46913
  const {
@@ -46928,7 +46954,7 @@ const BadgeButton = props => {
46928
46954
  };
46929
46955
  Badge.Button = BadgeButton;
46930
46956
 
46931
- installImportMetaCssBuild(import.meta);const css$r = /* css */`
46957
+ installImportMetaCssBuild(import.meta);const css$s = /* css */`
46932
46958
  @layer navi {
46933
46959
  }
46934
46960
  .navi_badge_list {
@@ -46953,7 +46979,7 @@ const BadgeList = ({
46953
46979
  max,
46954
46980
  ...props
46955
46981
  }) => {
46956
- import.meta.css = [css$r, "@jsenv/navi/src/text/badge_list.jsx"];
46982
+ import.meta.css = [css$s, "@jsenv/navi/src/text/badge_list.jsx"];
46957
46983
  const measureRef = useRef();
46958
46984
  const visibleRef = useRef();
46959
46985
  useLayoutEffect(() => {
@@ -47028,7 +47054,7 @@ const BadgeList = ({
47028
47054
  });
47029
47055
  };
47030
47056
 
47031
- installImportMetaCssBuild(import.meta);const css$q = /* css */`
47057
+ installImportMetaCssBuild(import.meta);const css$r = /* css */`
47032
47058
  .navi_color {
47033
47059
  display: block;
47034
47060
  aspect-ratio: 1/1;
@@ -47059,7 +47085,7 @@ const Color = ({
47059
47085
  children,
47060
47086
  ...rest
47061
47087
  }) => {
47062
- import.meta.css = [css$q, "@jsenv/navi/src/text/color.jsx"];
47088
+ import.meta.css = [css$r, "@jsenv/navi/src/text/color.jsx"];
47063
47089
  const color = children || undefined;
47064
47090
  return jsx(Box, {
47065
47091
  as: "span",
@@ -47514,7 +47540,7 @@ const PickerFileUI = () => {
47514
47540
  return String(value);
47515
47541
  };
47516
47542
 
47517
- installImportMetaCssBuild(import.meta);const css$p = /* css */`
47543
+ installImportMetaCssBuild(import.meta);const css$q = /* css */`
47518
47544
  @layer navi {
47519
47545
  .navi_picker {
47520
47546
  --picker-border-radius: var(--navi-control-border-radius);
@@ -47840,7 +47866,7 @@ installImportMetaCssBuild(import.meta);const css$p = /* css */`
47840
47866
  }
47841
47867
  `;
47842
47868
  const PickerButton = props => {
47843
- import.meta.css = [css$p, "@jsenv/navi/src/control/picker/picker.jsx"];
47869
+ import.meta.css = [css$q, "@jsenv/navi/src/control/picker/picker.jsx"];
47844
47870
  if (typeof props.maxLines === "string") {
47845
47871
  props.maxLines = parseInt(props.maxLines);
47846
47872
  }
@@ -48298,10 +48324,14 @@ installImportMetaCssBuild(import.meta);/**
48298
48324
  * is what keeps the focus where the travel happens instead of moving it into a
48299
48325
  * slide that is about to leave.
48300
48326
  */
48301
- const css$o = /* css */`
48327
+ const css$p = /* css */`
48302
48328
  .navi_picker_spin {
48303
48329
  /* What the loading outline is drawn around. */
48304
48330
  position: relative;
48331
+ /* Written in the control font, like the picker it wraps: the day and its
48332
+ two chevrons are a control, not running text. */
48333
+ font-size: var(--navi-control-font-size);
48334
+ font-family: var(--navi-control-font-family);
48305
48335
  /* Framed like every other control (see navi_css_vars.js): what one steps
48306
48336
  through is a value one edits, and a box around it is what says so. Said
48307
48337
  as CSS rather than as defaults on the Box, so borderWidth="0" or a radius
@@ -48533,7 +48563,7 @@ const DaySpin = ({
48533
48563
  nextLabel,
48534
48564
  ...rest
48535
48565
  }) => {
48536
- import.meta.css = [css$o, "@jsenv/navi/src/control/picker/picker_spin.jsx"];
48566
+ import.meta.css = [css$p, "@jsenv/navi/src/control/picker/picker_spin.jsx"];
48537
48567
  const id = useId();
48538
48568
  const containerId = `${id}_days`;
48539
48569
  const pickerId = `${id}_picker`;
@@ -48928,7 +48958,7 @@ const addDays = (day, count) => {
48928
48958
  };
48929
48959
 
48930
48960
  installImportMetaCssBuild(import.meta);// TOFIX: select in data then reset, it reset to red/blue instead of red/blue/green
48931
- const css$n = /* css */`
48961
+ const css$o = /* css */`
48932
48962
  .navi_checkbox_group {
48933
48963
  border-style: solid;
48934
48964
 
@@ -48948,7 +48978,7 @@ const CheckboxGroup = props => {
48948
48978
  return checkboxGroup;
48949
48979
  };
48950
48980
  const CheckboxGroupInterface = props => {
48951
- import.meta.css = [css$n, "@jsenv/navi/src/control/input/checkbox_group.jsx"];
48981
+ import.meta.css = [css$o, "@jsenv/navi/src/control/input/checkbox_group.jsx"];
48952
48982
  const {
48953
48983
  ref
48954
48984
  } = props;
@@ -48979,6 +49009,197 @@ const CheckboxGroupInterface = props => {
48979
49009
  });
48980
49010
  };
48981
49011
 
49012
+ installImportMetaCssBuild(import.meta);/**
49013
+ * Multiline text control that grows with what is typed.
49014
+ *
49015
+ * Autosize is native: `field-sizing: content` lets the browser size the
49016
+ * textarea from its value — no hidden mirror textarea to measure against (the
49017
+ * technique libraries used before the property existed). `minRows`/`maxRows`
49018
+ * become min/max heights in `lh` units on top of it; past `maxRows` the
49019
+ * content scrolls.
49020
+ *
49021
+ * TextareaCharCount is the counter that goes with it, and the caller places
49022
+ * it: under the box, in a form footer, next to a label — fed with the same
49023
+ * value/signal as the textarea. The textarea draws no counter of its own.
49024
+ *
49025
+ * Styled as a `.navi_input` box (border, background, focus ring, readonly and
49026
+ * disabled fades, variants): one look for everything one types into. The
49027
+ * shared sheet is registered here too — a page may render a Textarea without
49028
+ * any Input.
49029
+ */
49030
+ const css$n = /* css */`
49031
+ .navi_input.navi_textarea {
49032
+ .navi_control_input {
49033
+ min-height: calc(var(--textarea-min-rows, 1.5) * 1lh);
49034
+ /* Above maxRows the box stops growing and the content scrolls. The
49035
+ 99999 fallback means "no cap" without needing a conditional rule. */
49036
+ max-height: calc(var(--textarea-max-rows, 99999) * 1lh);
49037
+ field-sizing: content;
49038
+ /* Explicit, never normal: minRows/maxRows are lengths in lh, and with
49039
+ line-height normal the lh unit resolves to a theoretical value that
49040
+ does not match the real rendered line — the box then jumps by a few
49041
+ pixels the moment the first character replaces the theory with a real
49042
+ line. One number for both keeps every row count exact. */
49043
+ line-height: 1.5;
49044
+ /* The control grows itself; resizable below hands the handle back. */
49045
+ resize: none;
49046
+ overflow: auto;
49047
+ }
49048
+ &[data-resizable] .navi_control_input {
49049
+ height: calc(var(--textarea-min-rows, 1.5) * 1lh);
49050
+ /* The two are exclusive: with field-sizing content the browser removes
49051
+ the resize handle (the size follows the content, there is nothing to
49052
+ drag). resizable means the hand takes over — fixed sizing, starting
49053
+ at minRows, and the drag writes its own inline height from there. */
49054
+ field-sizing: fixed;
49055
+ resize: vertical;
49056
+ }
49057
+ }
49058
+ .navi_textarea_char_count {
49059
+ color: color-mix(in srgb, currentColor 60%, transparent);
49060
+ font-size: 0.75em;
49061
+ user-select: none;
49062
+ }
49063
+ `;
49064
+
49065
+ /**
49066
+ * @type {import("ignore:preact").FunctionComponent<{
49067
+ * value?: string,
49068
+ * defaultValue?: string,
49069
+ * signal?: import("@preact/signals").Signal<string>,
49070
+ * name?: string,
49071
+ * minRows?: number,
49072
+ * maxRows?: number,
49073
+ * resizable?: boolean,
49074
+ * maxLength?: number,
49075
+ * width?: string,
49076
+ * [key: string]: any,
49077
+ * }>}
49078
+ * @param {number} [minRows=1.5] Height the empty control starts at, in lines.
49079
+ * The default shows half of a second line: enough to read "multiline" at a
49080
+ * glance without the height of a full extra row.
49081
+ * @param {number} [maxRows] Lines after which the control stops growing and
49082
+ * scrolls instead. Without it the control grows with its content.
49083
+ * @param {boolean} [resizable] Give the browser's vertical resize handle back.
49084
+ * A manual resize takes over from the automatic growth.
49085
+ * @param {number} [maxLength] The character limit, validated at submit. Pair
49086
+ * with `maxLengthGuard` to block typing past it, and render a
49087
+ * TextareaCharCount to show it.
49088
+ * @param {string} [width="35ch"] The control's width. Fixed on purpose: with
49089
+ * field-sizing the width would otherwise follow the longest line, and a box
49090
+ * that widens while one types is a box one chases.
49091
+ */
49092
+ const Textarea = ({
49093
+ // Destructured, never deleted off the props object: Preact reuses the same
49094
+ // props object when an internal state update re-renders the component, so a
49095
+ // delete would make these props vanish from the second render on (the box
49096
+ // then jumps back to the default minRows at the first keystroke).
49097
+ minRows = 1.5,
49098
+ maxRows,
49099
+ resizable,
49100
+ width = "35ch",
49101
+ ...props
49102
+ }) => {
49103
+ import.meta.css = [inputCss + css$n, "@jsenv/navi/src/control/input/textarea.jsx"];
49104
+ const defaultRef = useRef(null);
49105
+ props.ref = props.ref || defaultRef;
49106
+ const [rootProps, hostProps, childrenWrapperProps] = useControlProps(props, {
49107
+ controlType: "input"
49108
+ });
49109
+ const {
49110
+ basePseudoState,
49111
+ children
49112
+ } = hostProps;
49113
+ // Children go through ControlChildrenWrapper below; inside the <textarea>
49114
+ // element they would become its text content.
49115
+ delete hostProps.children;
49116
+ const loading = basePseudoState[":-navi-loading"];
49117
+ delete rootProps.width;
49118
+ hostProps.width = width;
49119
+ return jsxs(Box, {
49120
+ as: "span",
49121
+ inline: true,
49122
+ flex: true,
49123
+ baseClassName: "navi_input",
49124
+ className: "navi_textarea",
49125
+ ...rootProps,
49126
+ basePseudoState: basePseudoState,
49127
+ "data-resizable": resizable ? "" : undefined,
49128
+ styleCSSVars: InputStyleCSSVars,
49129
+ pseudoStateSelector: ".navi_control_input",
49130
+ pseudoClasses: InputPseudoClasses,
49131
+ pseudoElements: InputPseudoElements,
49132
+ "data-callout-anchor": ".navi_control_input",
49133
+ style: {
49134
+ "--textarea-min-rows": minRows,
49135
+ "--textarea-max-rows": maxRows,
49136
+ ...rootProps.style
49137
+ },
49138
+ children: [jsx(LoadingOutline, {
49139
+ loading: loading,
49140
+ color: "var(--loader-color)",
49141
+ inset: -1
49142
+ }), jsx(RealTextarea, {
49143
+ ...hostProps
49144
+ }), jsx(ControlChildrenWrapper, {
49145
+ ...childrenWrapperProps,
49146
+ children: children
49147
+ })]
49148
+ });
49149
+ };
49150
+
49151
+ /**
49152
+ * The counter that goes with a Textarea: "50/200" — how many characters are
49153
+ * typed over how many the limit allows, the way Material writes it (just the
49154
+ * count when there is no `maxLength`). Where it goes is the caller's call,
49155
+ * which is why it is a separate component rather than something the textarea
49156
+ * draws: put it under the box, in a form footer, next to a label, and feed it
49157
+ * the same value or signal as the textarea.
49158
+ *
49159
+ * @type {import("ignore:preact").FunctionComponent<{
49160
+ * value?: string,
49161
+ * signal?: import("@preact/signals").Signal<string>,
49162
+ * maxLength?: number,
49163
+ * [key: string]: any,
49164
+ * }>}
49165
+ * @param {string} [value] The text being counted. Say `signal` instead for a
49166
+ * two-way bound textarea: reading it here subscribes the count to it.
49167
+ * @param {number} [maxLength] The limit, shown after the count ("50/200").
49168
+ * Without it the count stands alone.
49169
+ */
49170
+ const TextareaCharCount = ({
49171
+ value,
49172
+ signal,
49173
+ maxLength,
49174
+ ...rest
49175
+ }) => {
49176
+ import.meta.css = [css$n, "@jsenv/navi/src/control/input/textarea.jsx"];
49177
+ const resolvedValue = signal ? signal.value : value;
49178
+ const length = typeof resolvedValue === "string" ? resolvedValue.length : 0;
49179
+ return jsx(Box, {
49180
+ as: "span",
49181
+ baseClassName: "navi_textarea_char_count",
49182
+ ...rest,
49183
+ children: maxLength === undefined ? length : `${length}/${maxLength}`
49184
+ });
49185
+ };
49186
+ const RealTextarea = ({
49187
+ maxLength,
49188
+ ...domProps
49189
+ }) => {
49190
+ const autoSelectReadOnlyProps = useAutoSelectReadOnly(domProps);
49191
+ return jsx(Box, {
49192
+ ...domProps,
49193
+ as: "textarea",
49194
+ baseClassName: "navi_control_input",
49195
+ ...autoSelectReadOnlyProps,
49196
+ // Native maxLength stays off, like RealInput in input_textual.jsx: the
49197
+ // maxLengthGuard handles live blocking, the constraint validates at
49198
+ // submit, and navi-max-length keeps the value readable from the DOM.
49199
+ "navi-max-length": maxLength
49200
+ });
49201
+ };
49202
+
48982
49203
  const Unit = ({
48983
49204
  unit,
48984
49205
  plural,
@@ -51015,7 +51236,11 @@ const useWheelInteractions = ({
51015
51236
  if (vp.contains(documentWheelEvent.target)) {
51016
51237
  return; // on the scroll surface → its own onWheel handles it
51017
51238
  }
51018
- documentWheelEvent.preventDefault();
51239
+ if (documentWheelEvent.cancelable) {
51240
+ // cancelable=false during an inertial scroll the browser already owns;
51241
+ // preventDefault would be ignored with an intervention warning.
51242
+ documentWheelEvent.preventDefault();
51243
+ }
51019
51244
  keepClaimingGesture(); // the gesture continues elsewhere → keep swallowing it
51020
51245
  };
51021
51246
  const stopClaimingGesture = () => {
@@ -51315,7 +51540,11 @@ const useWheelInteractions = ({
51315
51540
  // preventing touchstart has wider side effects. Requires a non-passive
51316
51541
  // listener. See docs/MOBILE_TAP_SUPPRESSION_AFTER_DRAG.md.
51317
51542
  const onTouchMove = e => {
51318
- if (drag) {
51543
+ // cancelable=false when the touch landed while a scroll was already in
51544
+ // progress (e.g. the page still coasting from a fling): the browser owns
51545
+ // that scroll, preventDefault would be a no-op and Chrome logs an
51546
+ // intervention warning for the attempt.
51547
+ if (drag && e.cancelable) {
51319
51548
  e.preventDefault();
51320
51549
  }
51321
51550
  };
@@ -58392,5 +58621,5 @@ const UserSvg = () => jsx("svg", {
58392
58621
  })
58393
58622
  });
58394
58623
 
58395
- export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, BadgeList, Box, Button, ButtonCopyToClipboard, Caption, CardLayout, CheckSvg, CheckboxGroup, CloseSvg, Code, Col, Colgroup, Color, ConstructionSvg, ControlGroup, DaySpin, Details, Dialog, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, EyeClosedSvg, EyeSvg, Field, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, Input, InputDuration, Interpolate, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, LinkCurrentSvg, List, ListItem, ListItemGroup, Loading, LoadingDotsSvg, LoadingIndicator, LoadingIndicatorFluid, LoadingOutline, MessageBox, Meter, Nav, NaviDebug, Paragraph, Picker, Popover, Popup, Quantity, RadioGroup, Route, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, SelectableInput, SelectionContext, Separator, SettingsSvg, SidePanel, Slide, SlideContainer, StarSvg, SummaryMarker, Svg, Table, TableCell, Tbody, Text, TextBox, Thead, Time, Title, Tr, UITransition, Unit, UserSvg, ViewportLayout, Wheel, WheelGroup, WheelItem, actionRunEffect, anyMatchingRouteSignal, applySearch, arraySignalMembership, coarsePointerSignal, compareTwoJsValues, createAction, createAvailableConstraint, createRequestCanceller, createSearch, createSelectionKeyboardShortcuts, createSlot, enableDebugActions, enableDebugOnDocumentLoading, ensureDocumentStartViewTransition, filterTableSelection, formatDatetime, formatDay, formatDayRelative, formatMonth, formatNumber, formatTime, formatTimeRelative, getNowHours, getNowHoursRoundedToStep, interpolateText, isCellSelected, isColumnSelected, isRowSelected, isToday, languagesSignal, localStorageSignal, moveArrayItemByIndex, navBack, navForward, navIntegratedVia, navTo, naviI18n, openCallout, rawUrlPart, registerGlobalConstraint, reload, rerunActions, resource, route, routeAction, setBaseUrl, setPreferredLanguage, setSupportedLanguages, setupRoutes, stateSignal, stopLoad, stringifyTableSelectionValue, swapArrayItemByIndex, syncOwnedResourceToSignals, syncResourceToSignals, updateActions, useActionStatus, useArraySignalMembership, useAsyncData, useCalloutRequestClose, useCancelPrevious, useCellGridFromRows, useConstraintValidityState, useDependenciesDiff, useDisplayedLayoutEffect, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useKeyboardShortcuts, useNavState, useOrderedColumns, usePopupMode, useRouteStatus, useRunOnMount, useSearchText, useSelectableElement, useSelectionController, useSignalSync, useSlideValue, useStateArray, useTitleLevel, useUrlSearchParam, valueInLocalStorage, windowWidthSignal };
58624
+ export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, BadgeList, Box, Button, ButtonCopyToClipboard, Caption, CardLayout, CheckSvg, CheckboxGroup, CloseSvg, Code, Col, Colgroup, Color, ConstructionSvg, ControlGroup, DaySpin, Details, Dialog, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, EyeClosedSvg, EyeSvg, Field, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, Input, InputDuration, Interpolate, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, LinkCurrentSvg, List, ListItem, ListItemGroup, Loading, LoadingDotsSvg, LoadingIndicator, LoadingIndicatorFluid, LoadingOutline, MessageBox, Meter, Nav, NaviDebug, Paragraph, Picker, Popover, Popup, Quantity, RadioGroup, Route, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, SelectableInput, SelectionContext, Separator, SettingsSvg, SidePanel, Slide, SlideContainer, StarSvg, SummaryMarker, Svg, Table, TableCell, Tbody, Text, TextBox, Textarea, TextareaCharCount, Thead, Time, Title, Tr, UITransition, Unit, UserSvg, ViewportLayout, Wheel, WheelGroup, WheelItem, actionRunEffect, anyMatchingRouteSignal, applySearch, arraySignalMembership, coarsePointerSignal, compareTwoJsValues, createAction, createAvailableConstraint, createRequestCanceller, createSearch, createSelectionKeyboardShortcuts, createSlot, enableDebugActions, enableDebugOnDocumentLoading, ensureDocumentStartViewTransition, filterTableSelection, formatDatetime, formatDay, formatDayRelative, formatMonth, formatNumber, formatTime, formatTimeRelative, getNowHours, getNowHoursRoundedToStep, interpolateText, isCellSelected, isColumnSelected, isRowSelected, isToday, languagesSignal, localStorageSignal, moveArrayItemByIndex, navBack, navForward, navIntegratedVia, navTo, naviI18n, openCallout, rawUrlPart, registerGlobalConstraint, reload, rerunActions, resource, route, routeAction, setBaseUrl, setPreferredLanguage, setSupportedLanguages, setupRoutes, stateSignal, stopLoad, stringifyTableSelectionValue, swapArrayItemByIndex, syncOwnedResourceToSignals, syncResourceToSignals, updateActions, useActionStatus, useArraySignalMembership, useAsyncData, useCalloutRequestClose, useCancelPrevious, useCellGridFromRows, useConstraintValidityState, useDependenciesDiff, useDisplayedLayoutEffect, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useKeyboardShortcuts, useNavState, useOrderedColumns, usePopupMode, useRouteStatus, useRunOnMount, useSearchText, useSelectableElement, useSelectionController, useSignalSync, useSlideValue, useStateArray, useTitleLevel, useUrlSearchParam, valueInLocalStorage, windowWidthSignal };
58396
58625
  //# sourceMappingURL=jsenv_navi.js.map