@jsenv/navi 0.29.112 → 0.29.115

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.
@@ -4256,14 +4256,6 @@ const findControlProxy = (el) => {
4256
4256
  return firstProxy;
4257
4257
  };
4258
4258
 
4259
- let renderMessageText = text => text;
4260
- const setCalloutMessageTextRenderer = renderer => {
4261
- renderMessageText = renderer;
4262
- };
4263
-
4264
- // What a callout's own JSX may know about the callout around it: how to close
4265
- // it, and the element — whose id is what `commandFor` needs when the button
4266
- // closing it is not inside it (inside, `--navi-close` finds it on its own).
4267
4259
  const CalloutContext = createContext();
4268
4260
  const useCalloutRequestClose = () => {
4269
4261
  return useContext(CalloutContext)?.requestClose;
@@ -4285,9 +4277,10 @@ const renderIntoCallout = (jsx$1, calloutMessageElement, {
4285
4277
  render(calloutJsx, calloutMessageElement);
4286
4278
  };
4287
4279
 
4288
- // An HTML message is rendered through preact rather than innerHTML so that its
4289
- // text can go through renderMessageText: an emoji in a validation message must
4290
- // not make the first line taller than the icon and close button beside it.
4280
+ // An HTML message is rendered through preact rather than innerHTML: what a
4281
+ // message holds is then a preact tree like any other, so a callout's own JSX
4282
+ // (its context, its close button) works the same whether the message arrived
4283
+ // as markup or as elements.
4291
4284
  const renderHtmlIntoCallout = (html, calloutMessageElement, {
4292
4285
  requestClose,
4293
4286
  element
@@ -4311,7 +4304,7 @@ const domToVNodes = node => {
4311
4304
  const vnodes = [];
4312
4305
  for (const child of node.childNodes) {
4313
4306
  if (child.nodeType === Node.TEXT_NODE) {
4314
- vnodes.push(renderMessageText(child.data));
4307
+ vnodes.push(child.data);
4315
4308
  continue;
4316
4309
  }
4317
4310
  if (child.nodeType !== Node.ELEMENT_NODE) {
@@ -4452,11 +4445,12 @@ const css$11 = /* css */`
4452
4445
  --callout-icon-color: black;
4453
4446
  --callout-padding: 8px;
4454
4447
  --callout-z-index: var(--navi-z-index-callout);
4455
- /* The callout's own, like its font: the icon and the cross are columns
4456
- one line tall (1lh), and that line has to be the message's first
4457
- line — which it is only if both read the same line-height, rather
4458
- than whatever the element the callout sits in happens to use. */
4459
- --callout-line-height: 1.5;
4448
+ /* The page's line, said here rather than inherited: the icon and the
4449
+ cross are columns one line tall (1lh), and that line has to be the
4450
+ message's first line — which it is only if both read the same
4451
+ line-height, rather than whatever the element the callout sits in
4452
+ happens to use. */
4453
+ --callout-line-height: var(--navi-line-height);
4460
4454
  /* The cross is furniture, not content: quieter than the message beside
4461
4455
  it, and the size of a glyph on the message's first line. */
4462
4456
  --callout-close-button-color: color-mix(
@@ -17681,13 +17675,17 @@ const singleLineEllipsisStyles = () => {
17681
17675
  };
17682
17676
  };
17683
17677
  // The lines beyond the clamp are still laid out, so the clip edge decides
17684
- // whether the top of the first hidden one is visible: "overflow: hidden" clips
17685
- // at the padding box and lets it show inside the block-end padding. Clipping at
17686
- // the content box instead ends the element right after its last visible line.
17678
+ // whether the top of the first hidden one is visible: the padding box lets it
17679
+ // show inside the block-end padding, the content box ends the element right
17680
+ // after its last visible line. That box also ends the inline axis at the last
17681
+ // glyph's advance width, and a script or italic face draws past it, so the
17682
+ // clip is pushed back out by the half-leading: room for ink that leaves the
17683
+ // advance width on every side, still above the hidden line's own em box.
17684
+ // `overflow-clip-margin` has no per-axis form, hence one length for all four.
17687
17685
  const lineClampStyles = (value) => {
17688
17686
  return {
17689
17687
  "overflow": "clip",
17690
- "overflowClipMargin": "content-box",
17688
+ "overflowClipMargin": "content-box calc((1lh - 1em) / 2)",
17691
17689
  "display": "-webkit-box",
17692
17690
  "-webkit-box-orient": "vertical",
17693
17691
  "-webkit-line-clamp": value,
@@ -37537,10 +37535,14 @@ const css$Y = /* css */`
37537
37535
  * the child so that its visual position matches the requested `textAnchor` value — regardless of
37538
37536
  * font-size, display type (inline, inline-block, inline-flex…), or the active `vertical-align`.
37539
37537
  *
37540
- * @param {"line-top"|"char-top"|"center"|"char-bottom"|"line-bottom"} [textAnchor="char-bottom"]
37538
+ * @param {"line-top"|"char-top"|"center"|"char-center"|"char-bottom"|"line-bottom"} [textAnchor="char-bottom"]
37541
37539
  * - `"line-top"` — child top aligns with the top of the surrounding line box
37542
37540
  * - `"char-top"` — child top aligns with the top of visible characters (ink ascent)
37543
37541
  * - `"center"` — child is vertically centered on the surrounding line box
37542
+ * - `"char-center"` — child is vertically centered on the capitals: midway between the
37543
+ * ink ascent and the baseline. What "centered on the text" means to
37544
+ * the eye — the line box center sits above it, by half the leading
37545
+ * plus whatever the font keeps above its capitals.
37544
37546
  * - `"char-bottom"` — child bottom aligns to the text baseline (no correction, browser default)
37545
37547
  * - `"line-bottom"` — child bottom aligns with the bottom of the surrounding line box
37546
37548
  * @param {{ size?: number, verticalAlign?: string }} [lineLayout]
@@ -37650,13 +37652,14 @@ const computeTopOffset = ({
37650
37652
  let desiredChildTopY = 0;
37651
37653
  if (textAnchor === "line-top") {
37652
37654
  desiredChildTopY = anchorRect.top;
37653
- } else if (textAnchor === "char-top") {
37655
+ } else if (textAnchor === "char-top" || textAnchor === "char-center") {
37654
37656
  const anchorStyle = getComputedStyle(anchorEl);
37655
37657
  const ctx = charTopCanvas.getContext("2d");
37656
37658
  ctx.font = `${anchorStyle.fontWeight} ${anchorStyle.fontSize} ${anchorStyle.fontFamily}`;
37657
37659
  const m = ctx.measureText("M");
37658
37660
  const baselineY = anchorRect.bottom - m.fontBoundingBoxDescent;
37659
- desiredChildTopY = baselineY - m.actualBoundingBoxAscent;
37661
+ const capTopY = baselineY - m.actualBoundingBoxAscent;
37662
+ desiredChildTopY = textAnchor === "char-top" ? capTopY : (capTopY + baselineY) / 2 - childH / 2;
37660
37663
  } else if (textAnchor === "center") {
37661
37664
  const anchorCenterY = (anchorRect.top + anchorRect.bottom) / 2;
37662
37665
  desiredChildTopY = anchorCenterY - childH / 2;
@@ -38062,12 +38065,15 @@ const CustomWidthSpace = ({
38062
38065
  // copy-pasting the text produces an actual space, but we also want
38063
38066
  // full control over the visual width of that gap.
38064
38067
  // - First span: contains the real space but rendered at font-size:0 so it
38065
- // takes up zero visual space.
38068
+ // takes up zero visual space. line-height:0 with it: a line-height
38069
+ // inherited as a length (a control's, snapped to the pixel) would give
38070
+ // this empty box that height, centered on the baseline — and its lower
38071
+ // half would push the line box down under the text.
38066
38072
  // - Second span: a zero-width joiner (​) with padding-left set to
38067
38073
  // the desired gap size. This is the only visible part.
38068
38074
  return jsxs("span", {
38069
38075
  children: [jsx("span", {
38070
- style: "font-size: 0",
38076
+ style: "font-size: 0; line-height: 0",
38071
38077
  children: " "
38072
38078
  }), jsx("span", {
38073
38079
  style: `padding-left: ${value}`,
@@ -38223,7 +38229,6 @@ const shouldInjectSpacingBetween = (left, right) => {
38223
38229
  * loading?: boolean,
38224
38230
  * skeleton?: boolean,
38225
38231
  * attachLastChild?: boolean,
38226
- * emojiAsIcon?: boolean,
38227
38232
  * preventSpaceUnderlines?: boolean,
38228
38233
  * holdSpaceForStyle?: import("ignore:preact").JSX.CSSProperties,
38229
38234
  * boldStable?: boolean,
@@ -38263,14 +38268,6 @@ const shouldInjectSpacingBetween = (left, right) => {
38263
38268
  * truncation belongs outside the `Text` instead (see `docs/typography.md`).
38264
38269
  * `Link` sets it on its own whenever it renders an end icon.
38265
38270
  *
38266
- * @param {boolean} [emojiAsIcon]
38267
- * Renders every emoji found in the string children as an `Icon`, so it sits
38268
- * in the line like a character and never makes the line taller than the
38269
- * text. For free text a user typed (a message, a description); see
38270
- * `docs/typography.md`. Only the strings this `Text` receives are rewritten —
38271
- * a string a child component renders is out of reach, and that component
38272
- * calls `renderEmojiAsIcon()` itself instead.
38273
- *
38274
38271
  * @param {boolean} [preventSpaceUnderlines]
38275
38272
  * Replaces real space characters between children with padding-based spaces.
38276
38273
  * Useful inside `<a>` elements where browsers draw an underline under spaces.
@@ -38385,7 +38382,6 @@ const TextUI = props => {
38385
38382
  spacing,
38386
38383
  preventSpaceUnderlines = false,
38387
38384
  attachLastChild = false,
38388
- emojiAsIcon = false,
38389
38385
  boldStable,
38390
38386
  holdSpaceForStyle,
38391
38387
  capitalize,
@@ -38416,11 +38412,6 @@ const TextUI = props => {
38416
38412
  } else {
38417
38413
  children = applySpacingOnTextChildren(children, resolvedSpacing, defaultSpace);
38418
38414
  }
38419
- if (emojiAsIcon) {
38420
- // After the spacing pass: an emoji glued to a word ("hello👋") must not
38421
- // get a separator injected as if it were a child element.
38422
- children = renderEmojiAsIcon(children);
38423
- }
38424
38415
  if (boldStable) {
38425
38416
  const {
38426
38417
  bold
@@ -38568,12 +38559,12 @@ const TextWithSelectRange = ({
38568
38559
  * meaning-bearing icon that needs to be exposed to assistive tech.
38569
38560
  * @param {(event: MouseEvent) => void} [props.onClick] - Makes the icon
38570
38561
  * interactive (`data-interactive`, pointer cursor) and non-decorative.
38571
- * @param {"line-top"|"char-top"|"center"|"char-bottom"|"line-bottom"} [props.textAnchor="center"]
38562
+ * @param {"line-top"|"char-top"|"center"|"char-center"|"char-bottom"|"line-bottom"} [props.textAnchor="center"]
38572
38563
  * - Vertical alignment within the surrounding text line for the inline
38573
38564
  * char-like mode, forwarded to `TextAnchor`: `"line-top"`/`"line-bottom"`
38574
38565
  * align to the line box edges, `"char-top"` to the ink ascent, `"center"`
38575
- * centers on the line box, `"char-bottom"` sits on the baseline. See
38576
- * `text_anchor.jsx`.
38566
+ * centers on the line box, `"char-center"` on the capitals, `"char-bottom"`
38567
+ * sits on the baseline. See `text_anchor.jsx`.
38577
38568
  * @param {{ size?: number, verticalAlign?: string }} [props.lineLayout] -
38578
38569
  * Describes the surrounding line context (font size / vertical-align),
38579
38570
  * forwarded to `TextAnchor` so it recomputes the vertical correction when
@@ -38697,84 +38688,6 @@ const Icon = ({
38697
38688
  });
38698
38689
  };
38699
38690
 
38700
- // An emoji-presentation character (🌸), or a pictogram forced into emoji
38701
- // presentation by VS16 (❤️), followed by its skin-tone modifiers and ZWJ
38702
- // sequence. A flag is two regional indicators that must stay together.
38703
- const EMOJI_REGEX = /\p{Regional_Indicator}{2}|(?:\p{Emoji_Presentation}|[\p{Extended_Pictographic}--\p{Emoji_Presentation}]\uFE0F)(?:[\p{Emoji_Modifier}\uFE0F]|\u200D\p{Extended_Pictographic}\uFE0F?)*/gv;
38704
-
38705
- /**
38706
- * What `emojiAsIcon` does, for something that renders text without going
38707
- * through `Text` (a callout's message): every emoji in the string children
38708
- * comes back wrapped in an `Icon`. The system emoji fonts have a taller
38709
- * ascent/descent than text fonts, so a raw emoji glyph makes its line taller
38710
- * than the lines around it; inside an Icon it is capped at 1em and centered on
38711
- * the line like any glyph icon.
38712
- *
38713
- * Children come back in the shape they arrived in: a string stays a string
38714
- * when it holds no emoji, and the array is only built once a child actually
38715
- * needs rewriting.
38716
- */
38717
- const renderEmojiAsIcon = children => {
38718
- if (typeof children === "string") {
38719
- return renderEmojiInString(children);
38720
- }
38721
- const childArray = toChildArray(children);
38722
- let result = null;
38723
- let index = 0;
38724
- for (const child of childArray) {
38725
- if (typeof child === "string") {
38726
- const rendered = renderEmojiInString(child);
38727
- if (rendered !== child) {
38728
- if (result === null) {
38729
- result = childArray.slice(0, index);
38730
- }
38731
- for (const part of rendered) {
38732
- result.push(part);
38733
- }
38734
- index++;
38735
- continue;
38736
- }
38737
- }
38738
- if (result !== null) {
38739
- result.push(child);
38740
- }
38741
- index++;
38742
- }
38743
- if (result === null) {
38744
- return children;
38745
- }
38746
- return result;
38747
- };
38748
- const renderEmojiInString = string => {
38749
- let parts = null;
38750
- let lastIndex = 0;
38751
- for (const match of string.matchAll(EMOJI_REGEX)) {
38752
- if (parts === null) {
38753
- parts = [];
38754
- }
38755
- if (match.index > lastIndex) {
38756
- parts.push(string.slice(lastIndex, match.index));
38757
- }
38758
- parts.push(jsx(Icon, {
38759
- decorative: false,
38760
- children: jsx("span", {
38761
- children: match[0]
38762
- })
38763
- }));
38764
- lastIndex = match.index + match[0].length;
38765
- }
38766
- if (parts === null) {
38767
- return string;
38768
- }
38769
- if (lastIndex < string.length) {
38770
- parts.push(string.slice(lastIndex));
38771
- }
38772
- return parts;
38773
- };
38774
- // A callout message is free text like any other; see callout.jsx for why it
38775
- // cannot import this itself.
38776
- setCalloutMessageTextRenderer(renderEmojiAsIcon);
38777
-
38778
38691
  /**
38779
38692
  * Hook that reactively checks if a URL is visited.
38780
38693
  * Re-renders when the visited URL set changes.
@@ -39782,12 +39695,23 @@ const css$V = /* css */`
39782
39695
  }
39783
39696
  }
39784
39697
 
39785
- &[data-expand] {
39698
+ /* A nav asked to expand is a nav whose tabs share it: one equal slice each,
39699
+ the label in the middle of the target that slice makes — a row that fills
39700
+ its container while its tabs sit at their text width stops in the middle,
39701
+ and gives every tab a different size to aim at.
39702
+ The main axis only: a vertical nav expanding horizontally fills the width
39703
+ (align-items below) rather than sharing its height between its tabs. */
39704
+ &[data-expand-x] {
39786
39705
  flex-grow: 1;
39787
39706
 
39788
- .navi_tab {
39707
+ &:not([data-vertical]) .navi_link {
39708
+ flex: 1;
39709
+ justify-content: center;
39710
+ }
39711
+ }
39712
+ &[data-expand-y][data-vertical] {
39713
+ .navi_link {
39789
39714
  flex: 1;
39790
- justify-content: start;
39791
39715
  }
39792
39716
  }
39793
39717
  /* Vertical layout */
@@ -39795,17 +39719,6 @@ const css$V = /* css */`
39795
39719
  /* overflow-x: hidden; */
39796
39720
  /* overflow-y: auto; */
39797
39721
  align-items: stretch;
39798
-
39799
- &[data-expand] {
39800
- .navi_tab {
39801
- align-items: stretch;
39802
- }
39803
- }
39804
- .navi_tab {
39805
- width: 100%;
39806
- flex-direction: row;
39807
- text-align: left;
39808
- }
39809
39722
  }
39810
39723
 
39811
39724
  /* Folder tabs: the current tab and the panel share one surface. Every tab
@@ -39921,6 +39834,7 @@ const Nav = ({
39921
39834
  vertical,
39922
39835
  expand,
39923
39836
  expandX,
39837
+ expandY,
39924
39838
  linkBorderRadiusInherit,
39925
39839
  currentIndicator,
39926
39840
  currentIndicatorSlides = true,
@@ -40057,7 +39971,8 @@ const Nav = ({
40057
39971
  column: !vertical,
40058
39972
  baseClassName: "navi_nav",
40059
39973
  "data-link-border-radius-inherit": linkBorderRadiusInherit ? "" : undefined,
40060
- "data-expand": expand || expandX ? "" : undefined,
39974
+ "data-expand-x": expand || expandX ? "" : undefined,
39975
+ "data-expand-y": expand || expandY ? "" : undefined,
40061
39976
  "data-vertical": vertical ? "" : undefined,
40062
39977
  "data-panel-position": panelPosition,
40063
39978
  "data-nav-indicator": indicatorPosition ?? undefined
@@ -40075,6 +39990,7 @@ const Nav = ({
40075
39990
  "aria-orientation": slideContainer && vertical ? "vertical" : undefined,
40076
39991
  expand: expand,
40077
39992
  expandX: expandX,
39993
+ expandY: expandY,
40078
39994
  spacing: spacing,
40079
39995
  ...props,
40080
39996
  styleCSSVars: NavStyleCSSVars,
@@ -45533,6 +45449,10 @@ installImportMetaCssBuild(import.meta);const css$J = /* css */`
45533
45449
  display: inline-block; /* So box css can override when wanting to put button inline flex */
45534
45450
  font-size: var(--button-font-size);
45535
45451
  font-family: var(--button-font-family);
45452
+ /* A form control comes with a line of its own from the browser, and that
45453
+ line is "normal": a label holding an emoji would then be taller than
45454
+ the same label without one. The page's line, like the page's font. */
45455
+ line-height: var(--navi-control-line-height);
45536
45456
  }
45537
45457
  }
45538
45458
 
@@ -45905,7 +45825,6 @@ const ButtonUI = props => {
45905
45825
  icon,
45906
45826
  cta,
45907
45827
  spacing,
45908
- emojiAsIcon = true,
45909
45828
  // Whether the button draws the loading outline itself. A button that is
45910
45829
  // one half of a bigger control says no: what is busy is the control, and
45911
45830
  // the outline belongs around the whole of it (see split_button.jsx).
@@ -45969,7 +45888,6 @@ const ButtonUI = props => {
45969
45888
 
45970
45889
  type: "button",
45971
45890
  spacing: undefined,
45972
- emojiAsIcon: undefined,
45973
45891
  cta: undefined,
45974
45892
  pressEffect: undefined,
45975
45893
  loadingOutline: undefined,
@@ -46023,7 +45941,6 @@ const ButtonUI = props => {
46023
45941
  ...controlChildrenWrapperProps,
46024
45942
  children: jsx(ButtonContent, {
46025
45943
  spacing: spacing,
46026
- emojiAsIcon: emojiAsIcon,
46027
45944
  children: children
46028
45945
  })
46029
45946
  })]
@@ -46031,7 +45948,6 @@ const ButtonUI = props => {
46031
45948
  };
46032
45949
  const ButtonContent = ({
46033
45950
  spacing,
46034
- emojiAsIcon,
46035
45951
  children
46036
45952
  }) => {
46037
45953
  const boxForwardedProps = useContext(BoxForwardedPropsContext);
@@ -46039,7 +45955,6 @@ const ButtonContent = ({
46039
45955
  ...boxForwardedProps,
46040
45956
  display: "inherit",
46041
45957
  spacing: spacing,
46042
- emojiAsIcon: emojiAsIcon,
46043
45958
  className: "navi_button_content",
46044
45959
  children: [children, jsx(ButtonShadow, {})]
46045
45960
  });
@@ -46198,7 +46113,6 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
46198
46113
  /**
46199
46114
  * @type {import("ignore:preact").FunctionComponent<{
46200
46115
  * ownTarget?: boolean | "refuse" | "always",
46201
- * emojiAsIcon?: boolean,
46202
46116
  * replace?: boolean,
46203
46117
  * [key: string]: any,
46204
46118
  * }>}
@@ -46209,9 +46123,6 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
46209
46123
  * @param {Function} [action] On a button with an `href` or a `route`, the
46210
46124
  * same order as a Link's: it runs on the press, before the navigation, and
46211
46125
  * the navigation does not wait for it (see Link's `action`).
46212
- * @param {boolean} [emojiAsIcon=true] Renders the emoji of the label as icons
46213
- * so the button keeps the height of its text — `Text`'s prop, on by default
46214
- * here. Pass `false` to let an emoji draw at its natural size.
46215
46126
  * @param {boolean|"refuse"|"always"} [ownTarget] A real target inside a zone
46216
46127
  * that belongs to another control — a chip's cross on a picker's façade, an
46217
46128
  * eye on a pressable row, a diskette inside a slide that travels. The press is
@@ -46814,7 +46725,8 @@ const inputCss = /* css */`
46814
46725
  --background-color: var(--navi-surface-color);
46815
46726
  --color: currentColor;
46816
46727
  --color-dimmed: color-mix(in srgb, currentColor 60%, transparent);
46817
- --placeholder-color: var(--color-dimmed);
46728
+ --placeholder-color: var(--navi-placeholder-color);
46729
+ --placeholder-font-style: var(--navi-placeholder-font-style);
46818
46730
  /* Hover */
46819
46731
  --border-color-hover: color-mix(in srgb, var(--border-color) 70%, black);
46820
46732
  --background-color-hover: color-mix(
@@ -46852,6 +46764,7 @@ const inputCss = /* css */`
46852
46764
  --x-background-color: var(--background-color);
46853
46765
  --x-color: var(--color);
46854
46766
  --x-placeholder-color: var(--placeholder-color);
46767
+ --x-placeholder-font-style: var(--placeholder-font-style);
46855
46768
  --x-padding-top: var(
46856
46769
  --padding-top,
46857
46770
  var(--padding-y, var(--padding, var(--navi-control-padding-y-default)))
@@ -46881,6 +46794,13 @@ const inputCss = /* css */`
46881
46794
  font-size: var(--font-size);
46882
46795
  font-family: var(--font-family);
46883
46796
  text-align: initial;
46797
+ /* On the root, not only on the field: what sits beside the field — a unit
46798
+ slot, a prefix, an icon — is text of the same box and has to land on
46799
+ the same row as what is typed. A slot left on the page's line would sit
46800
+ a fraction of a pixel away from the field's own, and read as misaligned
46801
+ ("+33" next to a number). The field repeats it below because a form
46802
+ control does not inherit it. */
46803
+ line-height: var(--navi-control-line-height);
46884
46804
  background-color: var(--x-background-color);
46885
46805
  border-width: var(--border-width);
46886
46806
  border-style: solid;
@@ -46931,9 +46851,12 @@ const inputCss = /* css */`
46931
46851
  font-size: inherit;
46932
46852
  /* A form control does not inherit the font on its own — the browser has
46933
46853
  one of its own for it, monospace for a <textarea> — so the box's font
46934
- (--navi-control-font-family) is handed down by hand. */
46854
+ (--navi-control-font-family) is handed down by hand. Its line comes
46855
+ from the page's token for the same reason: what is typed must sit on
46856
+ the same line as what displays it afterwards, emoji included. */
46935
46857
  font-family: inherit;
46936
46858
  text-align: inherit;
46859
+ line-height: var(--navi-control-line-height);
46937
46860
  background: none;
46938
46861
  border: none;
46939
46862
  border-radius: inherit;
@@ -46942,6 +46865,7 @@ const inputCss = /* css */`
46942
46865
 
46943
46866
  &::placeholder {
46944
46867
  color: var(--x-placeholder-color);
46868
+ font-style: var(--x-placeholder-font-style);
46945
46869
  }
46946
46870
  /* Webkit is putting a slight blue bckground on autofilled input */
46947
46871
  /* For now we override with out custom background color */
@@ -46969,10 +46893,6 @@ const inputCss = /* css */`
46969
46893
  /* Nothing to read yet is still a line: the box may not collapse just
46970
46894
  because the value is empty. */
46971
46895
  min-height: 1lh;
46972
- /* A form control keeps a line of its own whatever line-height the page
46973
- is written in; the text that stands in for one has to say the same
46974
- number, or the box it is meant to match changes height. */
46975
- line-height: normal;
46976
46896
  }
46977
46897
  /* The value is cut by a box of its own, and that is the whole reason it
46978
46898
  exists: a field ends its text at the content edge, while an overflow set
@@ -48014,15 +47934,321 @@ Input.UI = {
48014
47934
  };
48015
47935
 
48016
47936
  installImportMetaCssBuild(import.meta);/**
48017
- * - We must keep the edited element in the DOM so that
48018
- * the layout remains the same (especially important for table cells)
48019
- * And the editable part is in absolute so that it takes the original content dimensions
48020
- * AND for table cells it can actually take the table cell dimensions
47937
+ * Multiline text control that grows with what is typed.
48021
47938
  *
48022
- * This means an editable thing MUST have a parent with position relative that wraps the content and the eventual editable input
47939
+ * Autosize is native: `field-sizing: content` lets the browser size the
47940
+ * textarea from its value — no hidden mirror textarea to measure against (the
47941
+ * technique libraries used before the property existed). `minRows`/`maxRows`
47942
+ * become min/max heights in `lh` units on top of it; past `maxRows` the
47943
+ * content scrolls.
47944
+ *
47945
+ * TextareaCharCount is the counter that goes with it, and the caller places
47946
+ * it: under the box, in a form footer, next to a label — fed with the same
47947
+ * value/signal as the textarea. The textarea draws no counter of its own.
48023
47948
  *
47949
+ * Styled as a `.navi_input` box (border, background, focus ring, readonly and
47950
+ * disabled fades, variants): one look for everything one types into. The
47951
+ * shared sheet is registered here too — a page may render a Textarea without
47952
+ * any Input.
48024
47953
  */
48025
47954
  const css$I = /* css */`
47955
+ .navi_textarea {
47956
+ textarea {
47957
+ min-height: calc(var(--textarea-min-rows, 1.5) * 1lh);
47958
+ /* Above maxRows the box stops growing and the content scrolls. The
47959
+ 99999 fallback means "no cap" without needing a conditional rule. */
47960
+ max-height: calc(var(--textarea-max-rows, 99999) * 1lh);
47961
+ field-sizing: content;
47962
+ /* Explicit, never normal, for two independent reasons.
47963
+ minRows/maxRows are lengths in lh, and with line-height normal the lh
47964
+ unit resolves to a theoretical value that does not match the real
47965
+ rendered line — the box then jumps by a few pixels the moment the first
47966
+ character replaces the theory with a real line.
47967
+ And a line box under "normal" takes the height of the tallest font it
47968
+ holds, so the one line carrying an emoji stands taller than the ones
47969
+ around it — and a tighter line would cut the top off the glyph.
47970
+ The number is the page's own (--navi-line-height, 1.25), snapped to
47971
+ the pixel like every control's: a message typed here and the same
47972
+ message displayed afterwards sit on the same line, emoji included.
47973
+ Bound to the token rather than inherited so it can never come back as
47974
+ "normal" from a container that sets one. See docs/typography.md. */
47975
+ line-height: var(--navi-control-line-height);
47976
+ /* The control grows itself; resizable below hands the handle back. */
47977
+ resize: none;
47978
+ overflow: auto;
47979
+ /* A placeholder must be readable in full before anything is typed: a
47980
+ field that opens already scrolled reads as a field that already has
47981
+ text in it. The lines it wraps to are counted (see
47982
+ usePlaceholderHeight) because they only exist once laid out, and they
47983
+ only raise the floor while the placeholder is what is being shown —
47984
+ what is typed sizes the box on its own. A count of lines rather than
47985
+ a height, so the floor is on the same grid as minRows. */
47986
+ &:placeholder-shown {
47987
+ min-height: max(
47988
+ calc(var(--textarea-min-rows, 1.5) * 1lh),
47989
+ calc(var(--x-textarea-placeholder-rows, 0) * 1lh)
47990
+ );
47991
+ }
47992
+ }
47993
+ &[data-resizable] {
47994
+ .navi_control_input {
47995
+ height: calc(var(--textarea-min-rows, 1.5) * 1lh);
47996
+ /* The two are exclusive: with field-sizing content the browser removes
47997
+ the resize handle (the size follows the content, there is nothing to
47998
+ drag). resizable means the hand takes over — fixed sizing, starting
47999
+ at minRows, and the drag writes its own inline height from there. */
48000
+ field-sizing: fixed;
48001
+ resize: vertical;
48002
+ }
48003
+ }
48004
+ }
48005
+ .navi_textarea_char_count {
48006
+ color: color-mix(in srgb, currentColor 60%, transparent);
48007
+ font-size: 0.75em;
48008
+ user-select: none;
48009
+ }
48010
+ `;
48011
+
48012
+ // minRows/maxRows are read by the CSS above, so they travel the way every
48013
+ // other style of this box travels: a prop mapped to the custom property it
48014
+ // stands for, written by Box itself (see its styleCSSVars).
48015
+ const TextareaStyleCSSVars = {
48016
+ ...InputStyleCSSVars,
48017
+ minRows: "--textarea-min-rows",
48018
+ maxRows: "--textarea-max-rows"
48019
+ };
48020
+
48021
+ /**
48022
+ * @type {import("ignore:preact").FunctionComponent<{
48023
+ * value?: string,
48024
+ * defaultValue?: string,
48025
+ * signal?: import("@preact/signals").Signal<string>,
48026
+ * name?: string,
48027
+ * minRows?: number,
48028
+ * maxRows?: number,
48029
+ * resizable?: boolean,
48030
+ * maxLength?: number,
48031
+ * width?: string,
48032
+ * [key: string]: any,
48033
+ * }>}
48034
+ * @param {number} [minRows=1.5] Height the empty control starts at, in lines.
48035
+ * The default shows half of a second line: enough to read "multiline" at a
48036
+ * glance without the height of a full extra row.
48037
+ * @param {number} [maxRows] Lines after which the control stops growing and
48038
+ * scrolls instead. Without it the control grows with its content.
48039
+ * @param {boolean} [resizable] Give the browser's vertical resize handle back.
48040
+ * An exchange, not an addition: the hand takes over from the automatic
48041
+ * growth, so the control stops following what is typed and stays at the
48042
+ * height it was last dragged to (starting at `minRows`).
48043
+ * @param {number} [maxLength] The character limit, validated at submit. Pair
48044
+ * with `maxLengthGuard` to block typing past it, and render a
48045
+ * TextareaCharCount to show it.
48046
+ * @param {string} [width="35ch"] The control's width. Fixed on purpose: with
48047
+ * field-sizing the width would otherwise follow the longest line, and a box
48048
+ * that widens while one types is a box one chases.
48049
+ */
48050
+ const Textarea = ({
48051
+ // Destructured, never deleted off the props object: Preact reuses the same
48052
+ // props object when an internal state update re-renders the component, so a
48053
+ // delete would make these props vanish from the second render on (the box
48054
+ // then jumps back to its default size at the first keystroke).
48055
+ resizable,
48056
+ width = "35ch",
48057
+ ...props
48058
+ }) => {
48059
+ import.meta.css = [inputCss + css$I, "@jsenv/navi/src/control/input/textarea.jsx"];
48060
+ const defaultRef = useRef(null);
48061
+ props.ref = props.ref || defaultRef;
48062
+ usePlaceholderHeight(props.ref, props.placeholder);
48063
+ const [rootProps, hostProps, childrenWrapperProps] = useControlProps(props, {
48064
+ controlType: "input"
48065
+ });
48066
+ const {
48067
+ basePseudoState,
48068
+ children
48069
+ } = hostProps;
48070
+ // Children go through ControlChildrenWrapper below; inside the <textarea>
48071
+ // element they would become its text content.
48072
+ delete hostProps.children;
48073
+ const loading = basePseudoState[":-navi-loading"];
48074
+ delete rootProps.width;
48075
+ hostProps.width = width;
48076
+ return jsxs(Box, {
48077
+ as: "span",
48078
+ inline: true,
48079
+ flex: true,
48080
+ baseClassName: "navi_input navi_textarea",
48081
+ ...rootProps,
48082
+ basePseudoState: basePseudoState,
48083
+ "data-resizable": resizable ? "" : undefined,
48084
+ styleCSSVars: TextareaStyleCSSVars,
48085
+ pseudoStateSelector: ".navi_control_input",
48086
+ pseudoClasses: InputPseudoClasses,
48087
+ pseudoElements: InputPseudoElements,
48088
+ "data-callout-anchor": ".navi_control_input",
48089
+ children: [jsx(LoadingOutline, {
48090
+ loading: loading,
48091
+ color: "var(--loader-color)",
48092
+ inset: -1
48093
+ }), jsx(RealTextarea, {
48094
+ ...hostProps
48095
+ }), jsx(ControlChildrenWrapper, {
48096
+ ...childrenWrapperProps,
48097
+ children: children
48098
+ })]
48099
+ });
48100
+ };
48101
+
48102
+ /**
48103
+ * The counter that goes with a Textarea: "50/200" — how many characters are
48104
+ * typed over how many the limit allows, the way Material writes it (just the
48105
+ * count when there is no `maxLength`). Where it goes is the caller's call,
48106
+ * which is why it is a separate component rather than something the textarea
48107
+ * draws: put it under the box, in a form footer, next to a label, and feed it
48108
+ * the same value or signal as the textarea.
48109
+ *
48110
+ * @type {import("ignore:preact").FunctionComponent<{
48111
+ * value?: string,
48112
+ * signal?: import("@preact/signals").Signal<string>,
48113
+ * maxLength?: number,
48114
+ * [key: string]: any,
48115
+ * }>}
48116
+ * @param {string} [value] The text being counted. Say `signal` instead for a
48117
+ * two-way bound textarea: reading it here subscribes the count to it.
48118
+ * @param {number} [maxLength] The limit, shown after the count ("50/200").
48119
+ * Without it the count stands alone.
48120
+ */
48121
+ const TextareaCharCount = ({
48122
+ value,
48123
+ signal,
48124
+ maxLength,
48125
+ ...rest
48126
+ }) => {
48127
+ import.meta.css = [css$I, "@jsenv/navi/src/control/input/textarea.jsx"];
48128
+ const resolvedValue = signal ? signal.value : value;
48129
+ const length = typeof resolvedValue === "string" ? resolvedValue.length : 0;
48130
+ return jsx(Box, {
48131
+ as: "span",
48132
+ baseClassName: "navi_textarea_char_count",
48133
+ ...rest,
48134
+ children: maxLength === undefined ? length : `${length}/${maxLength}`
48135
+ });
48136
+ };
48137
+
48138
+ // `field-sizing: content` sizes the box from the value, and an empty field has
48139
+ // none — the placeholder is text the browser refuses to make room for. So the
48140
+ // lines it wraps to are counted and published as --x-textarea-placeholder-rows
48141
+ // for the CSS above to use as a floor.
48142
+ const usePlaceholderHeight = (ref, placeholder) => {
48143
+ useLayoutEffect(() => {
48144
+ const textareaEl = ref.current;
48145
+ if (!placeholder) {
48146
+ textareaEl.style.removeProperty("--x-textarea-placeholder-rows");
48147
+ return null;
48148
+ }
48149
+ let widthMeasured;
48150
+ const measure = () => {
48151
+ // What is typed sizes the box itself; the placeholder is not displayed
48152
+ // then, and scrollHeight would report the value's height instead.
48153
+ if (textareaEl.value !== "") {
48154
+ return;
48155
+ }
48156
+ const {
48157
+ paddingTop,
48158
+ paddingBottom,
48159
+ lineHeight
48160
+ } = getComputedStyle(textareaEl);
48161
+ // Read with no floor under the box: scrollHeight can never report less
48162
+ // than the height the element already has, so a min-height still in
48163
+ // place — minRows, or the previous measure — would be read back as
48164
+ // lines of placeholder (a one-line placeholder over a 1.5-row floor
48165
+ // would count as two), and the box could only ever grow, never shrink
48166
+ // back on a wider viewport.
48167
+ textareaEl.style.setProperty("min-height", "0px");
48168
+ const contentHeight = textareaEl.scrollHeight - parseFloat(paddingTop) - parseFloat(paddingBottom);
48169
+ textareaEl.style.removeProperty("min-height");
48170
+ // scrollHeight is a whole number of pixels where a line need not be: a
48171
+ // height handed back as is would put the floor a fraction above the
48172
+ // line grid, and the empty box and the one showing its placeholder
48173
+ // would not be the same height. A count of lines has no fraction.
48174
+ const rows = Math.round(contentHeight / parseFloat(lineHeight));
48175
+ widthMeasured = textareaEl.clientWidth;
48176
+ textareaEl.style.setProperty("--x-textarea-placeholder-rows", String(rows));
48177
+ };
48178
+ measure();
48179
+ // Measuring writes the variable that sets this element's own height, and
48180
+ // mutating layout from inside a resize callback is what makes the browser
48181
+ // report "ResizeObserver loop completed with undelivered notifications".
48182
+ // So the write waits for the frame that resize produced.
48183
+ let measureFrame = null;
48184
+ const requestMeasure = () => {
48185
+ if (measureFrame !== null) {
48186
+ return;
48187
+ }
48188
+ measureFrame = requestAnimationFrame(() => {
48189
+ measureFrame = null;
48190
+ measure();
48191
+ });
48192
+ };
48193
+ // The placeholder wraps against the available width, so a new width is a
48194
+ // new number of lines. Height changes are ignored: this measure is what
48195
+ // causes them, and reacting to them would be reacting to ourselves.
48196
+ const resizeObserver = new ResizeObserver(() => {
48197
+ if (textareaEl.clientWidth !== widthMeasured) {
48198
+ requestMeasure();
48199
+ }
48200
+ });
48201
+ resizeObserver.observe(textareaEl);
48202
+ // The width may have changed while the field held a value, when measuring
48203
+ // was impossible — emptying it is when the placeholder comes back.
48204
+ const onInput = () => {
48205
+ if (textareaEl.value === "") {
48206
+ measure();
48207
+ }
48208
+ };
48209
+ textareaEl.addEventListener("input", onInput);
48210
+ return () => {
48211
+ if (measureFrame !== null) {
48212
+ cancelAnimationFrame(measureFrame);
48213
+ }
48214
+ resizeObserver.disconnect();
48215
+ textareaEl.removeEventListener("input", onInput);
48216
+ };
48217
+ }, [placeholder]);
48218
+ };
48219
+ const RealTextarea = ({
48220
+ maxLength,
48221
+ ...domProps
48222
+ }) => {
48223
+ const autoSelectReadOnlyProps = useAutoSelectReadOnly(domProps);
48224
+ return jsx(Box, {
48225
+ ...domProps,
48226
+ as: "textarea",
48227
+ baseClassName: "navi_control_input",
48228
+ ...autoSelectReadOnlyProps,
48229
+ // Native maxLength stays off, like RealInput in input_textual.jsx: the
48230
+ // maxLengthGuard handles live blocking, the constraint validates at
48231
+ // submit, and navi-max-length keeps the value readable from the DOM.
48232
+ "navi-max-length": maxLength
48233
+ });
48234
+ };
48235
+
48236
+ installImportMetaCssBuild(import.meta);/**
48237
+ * Edition in place: a value is read where it is written, and the field editing
48238
+ * it is drawn exactly on that spot.
48239
+ *
48240
+ * The read text stays in the DOM the whole time and the field is laid over it,
48241
+ * in absolute. That is what keeps the layout still: the text is what gives the
48242
+ * box its size (a table cell especially, whose width belongs to the whole
48243
+ * column), so taking it away would resize the box the moment one starts
48244
+ * editing. The field is there at all times too — inert and transparent until
48245
+ * edition starts — so appearing costs no mount.
48246
+ *
48247
+ * Being absolute, the field lands on the closest positioned ancestor. What it
48248
+ * takes for it to land ON the text and not merely near it is the contract
48249
+ * described on `Editable` below.
48250
+ */
48251
+ const css$H = /* css */`
48026
48252
  .navi_editable_wrapper {
48027
48253
  --inset-top: 0px;
48028
48254
  --inset-right: 0px;
@@ -48034,12 +48260,30 @@ const css$I = /* css */`
48034
48260
  right: var(--inset-right);
48035
48261
  bottom: var(--inset-bottom);
48036
48262
  left: var(--inset-left);
48263
+ font-size: inherit;
48264
+ font-family: inherit;
48265
+ line-height: inherit;
48266
+ border-radius: inherit;
48037
48267
  opacity: 0;
48038
48268
  pointer-events: none;
48039
48269
 
48040
- input {
48270
+ /* The field takes the place of the text, so the text is what decides how
48271
+ it is drawn — a control's own font (its size and family come from
48272
+ --navi-control-*) would make the value change size the moment one edits
48273
+ it. Unlayered, so it wins over the control's own sheet. */
48274
+ .navi_input {
48275
+ font-size: inherit;
48276
+ font-family: inherit;
48277
+ border-radius: inherit;
48278
+ }
48279
+ input,
48280
+ textarea {
48041
48281
  font-weight: inherit;
48282
+ font-size: inherit;
48283
+ font-family: inherit;
48042
48284
  text-align: inherit;
48285
+ line-height: inherit;
48286
+ border-radius: inherit;
48043
48287
  }
48044
48288
 
48045
48289
  &[data-editing] {
@@ -48048,6 +48292,15 @@ const css$I = /* css */`
48048
48292
  }
48049
48293
  }
48050
48294
  `;
48295
+
48296
+ /**
48297
+ * The state of "this thing is being edited", to hand to `Editable`.
48298
+ *
48299
+ * `startEditing` takes the event that asked for edition; `Editable` reads
48300
+ * `event.detail.initialValue` from it and types it into the field as a first
48301
+ * keystroke, which is how a table starts editing on a letter key instead of
48302
+ * losing that letter.
48303
+ */
48051
48304
  const useEditionController = () => {
48052
48305
  const [editing, editingSetter] = useState(null);
48053
48306
  const startEditing = useCallback(event => {
@@ -48070,8 +48323,108 @@ const useEditionController = () => {
48070
48323
  editionJustEnded
48071
48324
  };
48072
48325
  };
48326
+
48327
+ /**
48328
+ * The value written in place, with the field that edits it drawn exactly over
48329
+ * it: same first character, same font, same box.
48330
+ *
48331
+ * `Editable` renders the text (`children`, or `value` in a `<span>`) and, next
48332
+ * to it, an absolutely positioned field. Landing that field on the text is a
48333
+ * contract with the element around it:
48334
+ *
48335
+ * 1. **That element must be positioned** (`position: relative`) and must be the
48336
+ * one drawing the text. The field is absolute: it goes to the closest
48337
+ * positioned ancestor, so `Editable` belongs where the text is, never lifted
48338
+ * out of it.
48339
+ * 2. **The text must start at the same place in both.** The field is pulled out
48340
+ * over the parent's border (its insets are the parent's border sizes,
48341
+ * negated) so it covers the parent's border box; from there, the field's own
48342
+ * border + padding must add up to the parent's border + padding, or the
48343
+ * first character moves by the difference. The way to say it is to repeat
48344
+ * both on `Editable` — `paddingLeft`, `paddingTop`, `borderWidth`, plus
48345
+ * `borderRadius` so the corners fall on the parent's. A parent that paints
48346
+ * no border of its own (a table cell drawing its grid with a pseudo-element)
48347
+ * needs `borderWidth="0"` here, otherwise the control's default border eats
48348
+ * into the text.
48349
+ * 3. **The field must fill that box**: `width="100%"` (or `expandX`), and
48350
+ * `height="100%"` when the text sits in a box taller than a line. Horizontal
48351
+ * placement comes from the inherited `text-align`, and an inherited
48352
+ * alignment only means something in a box spanning the whole width.
48353
+ *
48354
+ * The font is not part of the contract: family, size, weight, line height and
48355
+ * alignment are inherited from the text, so the value keeps its own appearance
48356
+ * while being edited. Declaring one here is how a value changes size the moment
48357
+ * one edits it.
48358
+ *
48359
+ * For reference, both ways of holding up that end: `Table`'s editable cells do
48360
+ * it in CSS (the `[data-editing] input` rules in `control/table/table_css.js`),
48361
+ * the demo `control/demos/action/8_editable_demo.html` does it with props.
48362
+ *
48363
+ * @type {import("ignore:preact").FunctionComponent<{
48364
+ * children?: import("ignore:preact").ComponentChildren,
48365
+ * action: Function | object,
48366
+ * editing?: { event?: Event } | null,
48367
+ * onEditEnd?: (detail: { success?: boolean, cancelled?: boolean, event: Event }) => void,
48368
+ * name?: string,
48369
+ * value?: any,
48370
+ * valueSignal?: import("@preact/signals").Signal,
48371
+ * constraints?: any,
48372
+ * type?: string,
48373
+ * multiline?: boolean,
48374
+ * minRows?: number,
48375
+ * maxRows?: number,
48376
+ * required?: boolean,
48377
+ * readOnly?: boolean,
48378
+ * min?: number | string,
48379
+ * max?: number | string,
48380
+ * step?: number | string,
48381
+ * minLength?: number,
48382
+ * maxLength?: number,
48383
+ * pattern?: string,
48384
+ * wrapperProps?: object,
48385
+ * autoFocusSelect?: boolean,
48386
+ * width?: string | number,
48387
+ * height?: string | number,
48388
+ * [key: string]: any,
48389
+ * }>}
48390
+ *
48391
+ * @param {Function|object} action
48392
+ * What saves the value; it receives what was typed. Edition ends on its
48393
+ * success, and a failure keeps the field open with what the user wrote.
48394
+ *
48395
+ * @param {{ event?: Event }|null} [editing]
48396
+ * Whether the field is open, and what asked for it — the object
48397
+ * `useEditionController` holds. Anything falsy leaves the text alone.
48398
+ *
48399
+ * @param {Function} [onEditEnd]
48400
+ * Called once edition is over, whichever way it went: `success` after the
48401
+ * action, `cancelled` on Escape, on a blur that changed nothing, or on a blur
48402
+ * leaving an invalid value.
48403
+ *
48404
+ * @param {import("@preact/signals").Signal} [valueSignal]
48405
+ * The value as a signal, when what is typed must be readable live by the rest
48406
+ * of the page. It is written as the user types and restored to what it was on
48407
+ * cancel; `value` alone is enough for the ordinary case.
48408
+ *
48409
+ * @param {boolean} [multiline]
48410
+ * Swaps the field for a `Textarea` — the same edition in place, for a value
48411
+ * written on several lines (a message, a description). Enter then makes a
48412
+ * line instead of validating: what is typed is saved when the field is left,
48413
+ * and Escape gives up on it. `minRows` / `maxRows` bound its height; the props
48414
+ * only an `<input>` understands (`type`, `min`, `max`, `step`, `pattern`) are
48415
+ * dropped.
48416
+ *
48417
+ * @param {boolean} [autoFocusSelect=true]
48418
+ * Selects the whole value when the field opens, so typing replaces it. Turn
48419
+ * it off to have the caret land in a value one comes to amend.
48420
+ *
48421
+ * @param {object} [wrapperProps]
48422
+ * Props for the absolutely positioned wrapper around the field, for the rare
48423
+ * case where its own box needs a say (a stacking context, an inset to nudge).
48424
+ * Every other unknown prop goes to the field itself, styling props included.
48425
+ */
48073
48426
  const Editable = props => {
48074
- import.meta.css = [css$I, "@jsenv/navi/src/control/edition/editable.jsx"];
48427
+ import.meta.css = [css$H, "@jsenv/navi/src/control/edition/editable.jsx"];
48075
48428
  let {
48076
48429
  children,
48077
48430
  action,
@@ -48082,6 +48435,9 @@ const Editable = props => {
48082
48435
  onEditEnd,
48083
48436
  constraints,
48084
48437
  type,
48438
+ multiline,
48439
+ minRows,
48440
+ maxRows,
48085
48441
  required,
48086
48442
  readOnly,
48087
48443
  min,
@@ -48127,29 +48483,24 @@ const Editable = props => {
48127
48483
  }
48128
48484
  }
48129
48485
  }, [editing]);
48130
- const input = jsx(Input, {
48131
- ref: ref,
48486
+ const controlProps = {
48487
+ ref,
48132
48488
  ...rest,
48133
- type: type,
48134
- name: name,
48135
- value: value,
48136
- valueSignal: valueSignal,
48489
+ name,
48490
+ value,
48491
+ valueSignal,
48137
48492
  autoFocus: editing,
48138
48493
  autoFocusVisible: true,
48139
- autoFocusSelect: autoFocusSelect,
48494
+ autoFocusSelect,
48140
48495
  cancelOnEscape: true,
48141
48496
  cancelOnBlurInvalid: true,
48142
- constraints: constraints,
48143
- required: required,
48144
- readOnly: readOnly,
48145
- min: min,
48146
- max: max,
48147
- step: step,
48148
- minLength: minLength,
48149
- maxLength: maxLength,
48150
- pattern: pattern,
48151
- width: width,
48152
- height: height,
48497
+ constraints,
48498
+ required,
48499
+ readOnly,
48500
+ minLength,
48501
+ maxLength,
48502
+ width,
48503
+ height,
48153
48504
  onCancel: e => {
48154
48505
  if (valueSignal) {
48155
48506
  valueSignal.value = valueWhenEditStartRef.current;
@@ -48186,6 +48537,21 @@ const Editable = props => {
48186
48537
  event: e
48187
48538
  });
48188
48539
  }
48540
+ };
48541
+ // What is typed on several lines goes into a textarea; the props that only
48542
+ // mean something to an <input> (its type and the bounds that come with it)
48543
+ // stay on that side.
48544
+ const control = multiline ? jsx(Textarea, {
48545
+ ...controlProps,
48546
+ minRows: minRows,
48547
+ maxRows: maxRows
48548
+ }) : jsx(Input, {
48549
+ ...controlProps,
48550
+ type: type,
48551
+ min: min,
48552
+ max: max,
48553
+ step: step,
48554
+ pattern: pattern
48189
48555
  });
48190
48556
  const wrapperRef = useRef();
48191
48557
  useLayoutEffect(() => {
@@ -48213,7 +48579,7 @@ const Editable = props => {
48213
48579
  // - is ignored by screen readers
48214
48580
  inert: editing ? undefined : "",
48215
48581
  "data-editing": editing ? "" : undefined,
48216
- children: input
48582
+ children: control
48217
48583
  })]
48218
48584
  });
48219
48585
  };
@@ -48620,7 +48986,7 @@ installImportMetaCssBuild(import.meta);/**
48620
48986
  * meet are drawn once instead of twice, and only the outer corners stay
48621
48987
  * rounded. See docs/control_group.md.
48622
48988
  */
48623
- const css$H = /* css */`
48989
+ const css$G = /* css */`
48624
48990
  .navi_group {
48625
48991
  --group-border-width: var(--navi-control-border-width);
48626
48992
 
@@ -48755,7 +49121,7 @@ const Group = ({
48755
49121
  vertical = row,
48756
49122
  ...props
48757
49123
  }) => {
48758
- import.meta.css = [css$H, "@jsenv/navi/src/control/group.jsx"];
49124
+ import.meta.css = [css$G, "@jsenv/navi/src/control/group.jsx"];
48759
49125
  return jsx(Box, {
48760
49126
  baseClassName: "navi_group",
48761
49127
  "data-vertical": vertical ? "" : undefined
@@ -48954,7 +49320,7 @@ installImportMetaCssBuild(import.meta);/**
48954
49320
  * So: nothing scrollable between the cap and the slides (a shared [data-body]
48955
49321
  * around them IS a scroller, see box.jsx), and `overflow="auto"` on each Slide.
48956
49322
  */
48957
- const css$G = /* css */`
49323
+ const css$F = /* css */`
48958
49324
  /* Where the picture stands relative to the slide that is current, in boxes
48959
49325
  (see paintTravelProgress). Declared, so that it is a NUMBER the browser can
48960
49326
  interpolate: the trait an indicator draws has to travel with the slides,
@@ -49448,7 +49814,7 @@ const SlideContainer = ({
49448
49814
  children,
49449
49815
  ...rest
49450
49816
  }) => {
49451
- import.meta.css = [css$G, "@jsenv/navi/src/layout/slide_container.jsx"];
49817
+ import.meta.css = [css$F, "@jsenv/navi/src/layout/slide_container.jsx"];
49452
49818
  const debugFocus = useDebugFocus();
49453
49819
  const trackRef = useRef();
49454
49820
  // The box itself: it is what takes the keyboard when what is on screen holds
@@ -53452,7 +53818,7 @@ installImportMetaCssBuild(import.meta);/**
53452
53818
  * reaches the real container.
53453
53819
  */
53454
53820
  let openLocalDialogCount = 0;
53455
- const css$F = /* css */`
53821
+ const css$E = /* css */`
53456
53822
  @layer navi {
53457
53823
  .navi_dialog {
53458
53824
  /* Min gap between the dialog and the edges of its container. Written
@@ -54040,7 +54406,7 @@ const css$F = /* css */`
54040
54406
  * @param {import("ignore:preact").ComponentChildren} props.children
54041
54407
  */
54042
54408
  const Dialog = props => {
54043
- import.meta.css = [css$F, "@jsenv/navi/src/layout/dialog.jsx"];
54409
+ import.meta.css = [css$E, "@jsenv/navi/src/layout/dialog.jsx"];
54044
54410
  if (props.openController) {
54045
54411
  return jsx(ControlledDialog, {
54046
54412
  ...props
@@ -55036,7 +55402,7 @@ installImportMetaCssBuild(import.meta);/**
55036
55402
  * and applied.
55037
55403
  */
55038
55404
  let openLocalPopoverCount = 0;
55039
- const css$E = /* css */`
55405
+ const css$D = /* css */`
55040
55406
  @layer navi {
55041
55407
  .navi_popover {
55042
55408
  /* soft: user-configurable preferred max-height. Kept as a *default*
@@ -55496,7 +55862,7 @@ const css$E = /* css */`
55496
55862
  * @param {import("ignore:preact").ComponentChildren} props.children
55497
55863
  */
55498
55864
  const Popover = props => {
55499
- import.meta.css = [css$E, "@jsenv/navi/src/layout/popover.jsx"];
55865
+ import.meta.css = [css$D, "@jsenv/navi/src/layout/popover.jsx"];
55500
55866
  if (props.openController) {
55501
55867
  return jsx(ControlledPopover, {
55502
55868
  ...props
@@ -56535,7 +56901,7 @@ installImportMetaCssBuild(import.meta);/**
56535
56901
  * and only under its own `sizeFromAnchor`) pass through untouched via
56536
56902
  * `...rest` to whichever of Popover/Dialog actually renders.
56537
56903
  */
56538
- const css$D = /* css */`
56904
+ const css$C = /* css */`
56539
56905
  @layer navi {
56540
56906
  .navi_popup {
56541
56907
  --popup-border-radius: var(--navi-popup-border-radius);
@@ -56652,7 +57018,7 @@ const css$D = /* css */`
56652
57018
  * @param {import("ignore:preact").ComponentChildren} props.children
56653
57019
  */
56654
57020
  const Popup = props => {
56655
- import.meta.css = [css$D, "@jsenv/navi/src/layout/popup.jsx"];
57021
+ import.meta.css = [css$C, "@jsenv/navi/src/layout/popup.jsx"];
56656
57022
  const {
56657
57023
  mode: modeProp,
56658
57024
  maxWidth,
@@ -56713,7 +57079,7 @@ const Popup = props => {
56713
57079
  });
56714
57080
  };
56715
57081
 
56716
- installImportMetaCssBuild(import.meta);const css$C = /* css */`
57082
+ installImportMetaCssBuild(import.meta);const css$B = /* css */`
56717
57083
  .navi_picker {
56718
57084
  /* Sizing ceilings (maxmax), background, box-shadow, outline, padding,
56719
57085
  overflow... are already handled correctly by Popup/Popover/Dialog
@@ -56867,7 +57233,7 @@ installImportMetaCssBuild(import.meta);const css$C = /* css */`
56867
57233
  }
56868
57234
  `;
56869
57235
  const PickerCustomResolver = props => {
56870
- import.meta.css = [css$C, "@jsenv/navi/src/control/picker/picker_custom.jsx"];
57236
+ import.meta.css = [css$B, "@jsenv/navi/src/control/picker/picker_custom.jsx"];
56871
57237
  if (props.children === undefined) {
56872
57238
  return jsx(PickerNative, {
56873
57239
  ...props
@@ -57727,7 +58093,7 @@ installImportMetaCssBuild(import.meta);/**
57727
58093
  * looking again. Anything else that closes the popup (the cancel button,
57728
58094
  * Escape, a click outside) is no.
57729
58095
  */
57730
- const css$B = /* css */`
58096
+ const css$A = /* css */`
57731
58097
  .navi_picker_confirm_body {
57732
58098
  display: flex;
57733
58099
  min-width: 180px;
@@ -57744,7 +58110,7 @@ const css$B = /* css */`
57744
58110
  }
57745
58111
  `;
57746
58112
  const PickerConfirmResolver = props => {
57747
- import.meta.css = [css$B, "@jsenv/navi/src/control/picker/picker_confirm.jsx"];
58113
+ import.meta.css = [css$A, "@jsenv/navi/src/control/picker/picker_confirm.jsx"];
57748
58114
  const Next = useNextResolver();
57749
58115
  if (props.type !== "confirm") {
57750
58116
  return jsx(Next, {
@@ -58052,7 +58418,7 @@ const LoadingIndicator = ({
58052
58418
  });
58053
58419
  };
58054
58420
 
58055
- installImportMetaCssBuild(import.meta);const css$A = /* css */`
58421
+ installImportMetaCssBuild(import.meta);const css$z = /* css */`
58056
58422
  @layer navi {
58057
58423
  .navi_separator {
58058
58424
  --size: 1px;
@@ -58130,7 +58496,7 @@ const Separator = ({
58130
58496
  style,
58131
58497
  ...props
58132
58498
  }) => {
58133
- import.meta.css = [css$A, "@jsenv/navi/src/layout/separator.jsx"];
58499
+ import.meta.css = [css$z, "@jsenv/navi/src/layout/separator.jsx"];
58134
58500
  return jsx(Box, {
58135
58501
  as: vertical ? "span" : "hr",
58136
58502
  ...props,
@@ -58623,7 +58989,7 @@ const ListItemFooter = props => {
58623
58989
  });
58624
58990
  };
58625
58991
 
58626
- installImportMetaCssBuild(import.meta);const css$z = /* css */`
58992
+ installImportMetaCssBuild(import.meta);const css$y = /* css */`
58627
58993
  @layer navi {
58628
58994
  .navi_list_container[navi-selectable] {
58629
58995
  /* Focus outline */
@@ -58676,6 +59042,10 @@ installImportMetaCssBuild(import.meta);const css$z = /* css */`
58676
59042
  position: relative;
58677
59043
  font-size: var(--navi-control-font-size);
58678
59044
  font-family: var(--navi-control-font-family);
59045
+ /* A selectable item is a control: it stands in a row with pickers and
59046
+ inputs (a horizontal list of choices), so it takes the control line
59047
+ with the control font — the same number of pixels tall as them. */
59048
+ line-height: var(--navi-control-line-height);
58679
59049
  -webkit-tap-highlight-color: var(--navi-control-tap-highlight-color);
58680
59050
  }
58681
59051
  }
@@ -58838,7 +59208,7 @@ const ListSelectableResolver = props => {
58838
59208
  };
58839
59209
  const ListSelectable = props => {
58840
59210
  const Next = useNextResolver();
58841
- import.meta.css = [css$z, "@jsenv/navi/src/control/list/list_selectable.jsx"];
59211
+ import.meta.css = [css$y, "@jsenv/navi/src/control/list/list_selectable.jsx"];
58842
59212
  // we allow ourselves to auto-generate a name
58843
59213
  const defaultName = useId();
58844
59214
  props.name = props.name || `listbox_${defaultName}`;
@@ -59481,7 +59851,7 @@ const ListVirtualContext = createContext(null);
59481
59851
  // that returning a component of one's own — instead of a bare <List.Item> —
59482
59852
  // works the same way.
59483
59853
  const ListRowContext = createContext(null);
59484
- const css$y = /* css */`
59854
+ const css$x = /* css */`
59485
59855
  @layer navi {
59486
59856
  .navi_list_container {
59487
59857
  --list-outline-width: 1px;
@@ -60030,7 +60400,7 @@ const css$y = /* css */`
60030
60400
  gap: 8px;
60031
60401
  color: light-dark(#b91c1c, #fca5a5);
60032
60402
  font-size: 0.9em;
60033
- line-height: 1.4;
60403
+ line-height: var(--navi-line-height);
60034
60404
  background: light-dark(#fef2f2, rgba(127, 29, 29, 0.25));
60035
60405
  border: 1px solid light-dark(#fecaca, rgba(248, 113, 113, 0.4));
60036
60406
  border-radius: 6px;
@@ -60038,7 +60408,7 @@ const css$y = /* css */`
60038
60408
  .navi_list_error_icon {
60039
60409
  flex: none;
60040
60410
  font-size: 1em;
60041
- line-height: 1.4;
60411
+ line-height: var(--navi-line-height);
60042
60412
  }
60043
60413
  /* Same rule as [data-scrollable] in box.jsx, said again for this scroller:
60044
60414
  what an item holds IS against the edge of the scroll container — the list
@@ -60180,7 +60550,7 @@ const css$y = /* css */`
60180
60550
  }
60181
60551
  `;
60182
60552
  const ListUI = props => {
60183
- import.meta.css = [css$y, "@jsenv/navi/src/control/list/list.jsx"];
60553
+ import.meta.css = [css$x, "@jsenv/navi/src/control/list/list.jsx"];
60184
60554
  const {
60185
60555
  ref,
60186
60556
  renderBudget: renderBudgetProp = RENDER_BUDGET_DEFAULT,
@@ -60216,6 +60586,7 @@ const ListUI = props => {
60216
60586
  overflow,
60217
60587
  overflowX,
60218
60588
  overflowY,
60589
+ virtual,
60219
60590
  ...rest
60220
60591
  } = props;
60221
60592
  // Accept a string (e.g. from an HTML attribute: renderBudget="50") the
@@ -60266,20 +60637,8 @@ const ListUI = props => {
60266
60637
  onListVisibleItemsChange?.(tracker.visibleItemsSignal.peek());
60267
60638
  }
60268
60639
  });
60269
- // A new pass every time the list's children are walked: they are about to
60270
- // say again, in order, which rows of the collection they stand for. Given
60271
- // back the very vnodes it already holds, preact skips them — the list
60272
- // re-rendered on its own and nothing is said again, so nothing is asked
60273
- // again either (see openPass).
60274
- const virtualRef = useRef(null);
60275
- if (!virtualRef.current) {
60276
- virtualRef.current = createListVirtual();
60277
- }
60278
- const virtual = virtualRef.current;
60279
- const childrenPreviousRef = useRef(undefined);
60280
- const childrenWalked = childrenPreviousRef.current !== children;
60281
- childrenPreviousRef.current = children;
60282
- virtual.openPass(renderBudget, scrolled ?? defaultScrolled, childrenWalked);
60640
+ virtual.renderBudget = renderBudget;
60641
+ virtual.scrolled = scrolled ?? defaultScrolled;
60283
60642
  const {
60284
60643
  virtualItemSizeSignal,
60285
60644
  renderWindow,
@@ -60495,6 +60854,24 @@ const ListFirstResolver = props => {
60495
60854
  props.ref = props.ref || refDefault;
60496
60855
  const idDefault = useId();
60497
60856
  props.id = props.id || idDefault;
60857
+ // A new pass every time the caller walks the children: they are about to
60858
+ // say again, in order, which rows of the collection they stand for. Decided
60859
+ // here, on the children as the caller gave them. Further down the chain a
60860
+ // resolver re-rendering on its own hands the rows down inside a vnode of its
60861
+ // own (ListSelectable wraps them in the group's contexts) — but the rows are
60862
+ // the very vnodes preact already holds, and it skips them: nothing is said
60863
+ // again, so nothing may be asked again either (see createListVirtual).
60864
+ const virtualRef = useRef(null);
60865
+ if (!virtualRef.current) {
60866
+ virtualRef.current = createListVirtual();
60867
+ }
60868
+ const virtual = virtualRef.current;
60869
+ const childrenPreviousRef = useRef(undefined);
60870
+ if (childrenPreviousRef.current !== props.children) {
60871
+ childrenPreviousRef.current = props.children;
60872
+ virtual.openPass();
60873
+ }
60874
+ props.virtual = virtual;
60498
60875
  return jsx(Next, {
60499
60876
  ...props
60500
60877
  });
@@ -62508,19 +62885,13 @@ const ListItemUI = props => {
62508
62885
  const listItemVnode = jsx(ListItemReal, {
62509
62886
  ...props
62510
62887
  });
62511
- // For separator decision, we need to know "am I the first visible item?".
62512
- // We deliberately do NOT use tracker's visibleIndex here because, during a
62513
- // reorder render pass (e.g. items resorted by search score), other items
62514
- // still have stale keyToExplicitOrder values the binary search reads
62515
- // those stale values and computes wrong indices. The result is that no
62516
- // item gets visibleIndex === 0 and a spurious <hr> appears at the top.
62517
- //
62518
- // Instead we use the parent-provided index, which is race-free:
62519
- // - global list: props.index === 0 means "first by explicit order"
62520
- // (parent passes sequential indices starting at 0; filtered items
62521
- // are already pushed to the end by useSearchText)
62522
- // - inside a group: each group has its own item tracker and group
62523
- // items don't reorder, so groupVisibleIndex is reliable
62888
+ // "Am I the first visible item?" is answered by the place the list handed
62889
+ // out (virtual.take above), not by the tracker's visibleIndex: during a
62890
+ // reorder render pass (items resorted by search score) the other items still
62891
+ // carry stale keyToExplicitOrder values, the binary search reads them, no
62892
+ // item comes out at 0 and a spurious separator appears at the top. Inside a
62893
+ // group, each group has its own tracker and its items do not reorder, so
62894
+ // groupVisibleIndex is reliable.
62524
62895
  const isFirstInList = groupVisibleIndex === null ? index === 0 : groupVisibleIndex === 0;
62525
62896
  if (!separator || isFirstInList) {
62526
62897
  return listItemVnode;
@@ -62834,9 +63205,10 @@ List.Item = ListItem;
62834
63205
  // A child knows how many rows it stands for but not what was declared before
62835
63206
  // it, so the list hands out the places as its children render — in order, which
62836
63207
  // is the only thing needed to place them. A child re-rendering ON ITS OWN (its
62837
- // own state changed, the list did not render) keeps the place it was given:
62838
- // nothing before it moved. Hence the pass — only a render of the list itself
62839
- // opens a new one, and within a pass a child takes its place exactly once.
63208
+ // own state changed, the list was not given its children again) keeps the
63209
+ // place it was given: nothing before it moved. Hence the pass — only the
63210
+ // caller walking the children again opens a new one (see ListFirstResolver),
63211
+ // and within a pass a child takes its place exactly once.
62840
63212
  const createListVirtual = () => {
62841
63213
  const totalSignal = signal(0);
62842
63214
  // Bumped whenever a run takes in rows. The list itself has to hear about it:
@@ -62872,22 +63244,12 @@ const createListVirtual = () => {
62872
63244
  horizontal: false,
62873
63245
  virtualItemSizeSignal: null,
62874
63246
  renderSkeleton: undefined,
62875
- openPass: (renderBudget, scrolled, childrenWalked) => {
62876
- virtual.renderBudget = renderBudget;
62877
- virtual.scrolled = scrolled;
62878
- // Places are handed out again only when the children are actually walked
62879
- // again. A list re-rendering on its own the render window moved, its
62880
- // scroller resolved — hands preact the very children vnodes it already
62881
- // holds, and preact skips them: nobody says where they sit, so nobody
62882
- // may lose where they sat. Reopening the pass there would empty the
62883
- // places without anyone refilling them, and the next row to render on
62884
- // its own (an item being selected, a run following the window) would be
62885
- // handed the first place — drawing itself as the top of the list, its
62886
- // separator gone, and the one after it wearing a separator it should not
62887
- // have.
62888
- if (!childrenWalked) {
62889
- return;
62890
- }
63247
+ // Reopening the pass when the rows are not about to render again would
63248
+ // empty the places without anyone refilling them: the next row to render
63249
+ // on its own (an item being selected, a run following the window) would be
63250
+ // handed the first place drawing itself as the top of the list, its
63251
+ // separator gone, and the one after it wearing a separator it should not.
63252
+ openPass: () => {
62891
63253
  passId++;
62892
63254
  nextIndex = 0;
62893
63255
  },
@@ -64021,7 +64383,7 @@ const createBadgeRegistry = () => {
64021
64383
  };
64022
64384
  };
64023
64385
 
64024
- installImportMetaCssBuild(import.meta);const css$x = /* css */`
64386
+ installImportMetaCssBuild(import.meta);const css$w = /* css */`
64025
64387
  @layer navi {
64026
64388
  }
64027
64389
  .navi_badge {
@@ -64153,7 +64515,7 @@ const BadgeUI = ({
64153
64515
  className,
64154
64516
  ...props
64155
64517
  }) => {
64156
- import.meta.css = [css$x, "@jsenv/navi/src/text/badge.jsx"];
64518
+ import.meta.css = [css$w, "@jsenv/navi/src/text/badge.jsx"];
64157
64519
  const defaultRef = useRef();
64158
64520
  props.ref = props.ref || defaultRef;
64159
64521
  const {
@@ -64221,7 +64583,7 @@ const BadgeButtonUI = props => {
64221
64583
  };
64222
64584
  Badge.Button = BadgeButton;
64223
64585
 
64224
- installImportMetaCssBuild(import.meta);const css$w = /* css */`
64586
+ installImportMetaCssBuild(import.meta);const css$v = /* css */`
64225
64587
  @layer navi {
64226
64588
  }
64227
64589
  .navi_badge_list {
@@ -64362,7 +64724,7 @@ const useBadgeRegistry = (children, enabled) => {
64362
64724
  * component grants (a <Picker>, see max_lines_context.js).
64363
64725
  */
64364
64726
  const BadgeList = props => {
64365
- import.meta.css = [css$w, "@jsenv/navi/src/text/badge_list.jsx"];
64727
+ import.meta.css = [css$v, "@jsenv/navi/src/text/badge_list.jsx"];
64366
64728
  const {
64367
64729
  maxLines,
64368
64730
  max,
@@ -64705,7 +65067,7 @@ const BadgeListContent = ({
64705
65067
  });
64706
65068
  };
64707
65069
 
64708
- installImportMetaCssBuild(import.meta);const css$v = /* css */`
65070
+ installImportMetaCssBuild(import.meta);const css$u = /* css */`
64709
65071
  .navi_color {
64710
65072
  display: block;
64711
65073
  aspect-ratio: 1/1;
@@ -64736,7 +65098,7 @@ const Color = ({
64736
65098
  children,
64737
65099
  ...rest
64738
65100
  }) => {
64739
- import.meta.css = [css$v, "@jsenv/navi/src/text/color.jsx"];
65101
+ import.meta.css = [css$u, "@jsenv/navi/src/text/color.jsx"];
64740
65102
  const color = children || undefined;
64741
65103
  return jsx(Box, {
64742
65104
  as: "span",
@@ -65053,7 +65415,7 @@ const PickerDateUI = props => {
65053
65415
  if (!placeholder) {
65054
65416
  return jsx(Time, {
65055
65417
  type: "date",
65056
- color: "var(--picker-placeholder-color",
65418
+ color: "var(--picker-placeholder-color)",
65057
65419
  capitalize: true,
65058
65420
  ...props
65059
65421
  });
@@ -65085,7 +65447,7 @@ const PickerMonthUI = props => {
65085
65447
  if (!placeholder) {
65086
65448
  return jsx(Time, {
65087
65449
  type: "month",
65088
- color: "var(--picker-placeholder-color",
65450
+ color: "var(--picker-placeholder-color)",
65089
65451
  ...props
65090
65452
  });
65091
65453
  }
@@ -65116,7 +65478,7 @@ const PickerWeekUI = props => {
65116
65478
  if (!placeholder) {
65117
65479
  return jsx(Time, {
65118
65480
  type: "week",
65119
- color: "var(--picker-placeholder-color",
65481
+ color: "var(--picker-placeholder-color)",
65120
65482
  ...props
65121
65483
  });
65122
65484
  }
@@ -65147,7 +65509,7 @@ const PickerTimeUI = props => {
65147
65509
  if (!placeholder) {
65148
65510
  return jsx(Time, {
65149
65511
  type: "time",
65150
- color: "var(--picker-placeholder-color",
65512
+ color: "var(--picker-placeholder-color)",
65151
65513
  ...props
65152
65514
  });
65153
65515
  }
@@ -65178,7 +65540,7 @@ const PickerDurationUI = props => {
65178
65540
  if (!placeholder) {
65179
65541
  return jsx(Time, {
65180
65542
  type: "time",
65181
- color: "var(--picker-placeholder-color",
65543
+ color: "var(--picker-placeholder-color)",
65182
65544
  ...props
65183
65545
  });
65184
65546
  }
@@ -65208,7 +65570,7 @@ const PickerDatetimeUI = props => {
65208
65570
  if (!placeholder) {
65209
65571
  return jsx(Time, {
65210
65572
  type: "datetime",
65211
- color: "var(--picker-placeholder-color",
65573
+ color: "var(--picker-placeholder-color)",
65212
65574
  ...props
65213
65575
  });
65214
65576
  }
@@ -65243,7 +65605,7 @@ const PickerFileUI = () => {
65243
65605
  return String(value);
65244
65606
  };
65245
65607
 
65246
- installImportMetaCssBuild(import.meta);const css$u = /* css */`
65608
+ installImportMetaCssBuild(import.meta);const css$t = /* css */`
65247
65609
  @layer navi {
65248
65610
  .navi_picker {
65249
65611
  --picker-border-radius: var(--navi-control-border-radius);
@@ -65261,11 +65623,8 @@ installImportMetaCssBuild(import.meta);const css$u = /* css */`
65261
65623
  --picker-border-color: var(--navi-control-border-color);
65262
65624
  --picker-background-color: white;
65263
65625
  --picker-color: currentColor;
65264
- --picker-placeholder-color: color-mix(
65265
- in srgb,
65266
- currentColor 60%,
65267
- transparent
65268
- );
65626
+ --picker-placeholder-color: var(--navi-placeholder-color);
65627
+ --picker-placeholder-font-style: var(--navi-placeholder-font-style);
65269
65628
  --picker-color-dimmed: color-mix(in srgb, currentColor 60%, transparent);
65270
65629
  /* Hover */
65271
65630
  --picker-border-color-hover: color-mix(
@@ -65364,6 +65723,11 @@ installImportMetaCssBuild(import.meta);const css$u = /* css */`
65364
65723
  font-size: var(--picker-font-size);
65365
65724
  font-family: var(--picker-font-family);
65366
65725
  text-align: inherit;
65726
+ /* The control line, like the control font: a picker is drawn by hand next
65727
+ to inputs that sit on it, and its box is sized in lh — the same number
65728
+ of pixels, or a picker and an input in one row are not the same height
65729
+ and their text is not on the same row. */
65730
+ line-height: var(--navi-control-line-height);
65367
65731
  /* The frame is drawn by the box, but its radius is declared here, on the
65368
65732
  control root, like every other navi control does — so anything styling
65369
65733
  the picker from the outside (a Group squaring the corners it joins) has
@@ -65448,6 +65812,7 @@ installImportMetaCssBuild(import.meta);const css$u = /* css */`
65448
65812
 
65449
65813
  &[navi-placeholder] {
65450
65814
  color: var(--picker-placeholder-color);
65815
+ font-style: var(--picker-placeholder-font-style);
65451
65816
  }
65452
65817
 
65453
65818
  /* A <BadgeList> caps its own rows: it renders the badges that fit and a
@@ -65758,7 +66123,7 @@ installImportMetaCssBuild(import.meta);const css$u = /* css */`
65758
66123
  }
65759
66124
  `;
65760
66125
  const PickerButton = props => {
65761
- import.meta.css = [css$u, "@jsenv/navi/src/control/picker/picker.jsx"];
66126
+ import.meta.css = [css$t, "@jsenv/navi/src/control/picker/picker.jsx"];
65762
66127
  if (typeof props.maxLines === "string") {
65763
66128
  props.maxLines = parseInt(props.maxLines);
65764
66129
  }
@@ -66328,7 +66693,7 @@ installImportMetaCssBuild(import.meta);/**
66328
66693
  * refuse it on purpose, which is what keeps the focus where the travel happens
66329
66694
  * instead of moving it into a slide that is about to leave.
66330
66695
  */
66331
- const css$t = /* css */`
66696
+ const css$s = /* css */`
66332
66697
  @layer navi {
66333
66698
  .navi_picker_spin {
66334
66699
  /* A picker one steps through is still a picker: what themes every picker
@@ -66843,7 +67208,7 @@ const Spin = ({
66843
67208
  nextLabel,
66844
67209
  ...rest
66845
67210
  }) => {
66846
- import.meta.css = [css$t, "@jsenv/navi/src/control/picker/picker_spin.jsx"];
67211
+ import.meta.css = [css$s, "@jsenv/navi/src/control/picker/picker_spin.jsx"];
66847
67212
  const id = useId();
66848
67213
  // What the group around it says, when there is one: how big the whole thing
66849
67214
  // is written is said once, on the group, and every spin in it follows.
@@ -67379,7 +67744,7 @@ const renderValueDefault = value => String(value ?? "");
67379
67744
  * are passed on to the spins sitting in them.
67380
67745
  */
67381
67746
  const SpinGroup = props => {
67382
- import.meta.css = [css$t, "@jsenv/navi/src/control/picker/picker_spin.jsx"];
67747
+ import.meta.css = [css$s, "@jsenv/navi/src/control/picker/picker_spin.jsx"];
67383
67748
  const {
67384
67749
  size
67385
67750
  } = props;
@@ -67892,7 +68257,7 @@ const TimeRangeSpin = ({
67892
68257
  };
67893
68258
 
67894
68259
  installImportMetaCssBuild(import.meta);// TOFIX: select in data then reset, it reset to red/blue instead of red/blue/green
67895
- const css$s = /* css */`
68260
+ const css$r = /* css */`
67896
68261
  .navi_checkbox_group {
67897
68262
  border-style: solid;
67898
68263
 
@@ -67933,7 +68298,7 @@ const CheckboxGroup = props => {
67933
68298
  return checkboxGroup;
67934
68299
  };
67935
68300
  const CheckboxGroupInterface = props => {
67936
- import.meta.css = [css$s, "@jsenv/navi/src/control/input/checkbox_group.jsx"];
68301
+ import.meta.css = [css$r, "@jsenv/navi/src/control/input/checkbox_group.jsx"];
67937
68302
  const {
67938
68303
  ref
67939
68304
  } = props;
@@ -67964,291 +68329,6 @@ const CheckboxGroupInterface = props => {
67964
68329
  });
67965
68330
  };
67966
68331
 
67967
- installImportMetaCssBuild(import.meta);/**
67968
- * Multiline text control that grows with what is typed.
67969
- *
67970
- * Autosize is native: `field-sizing: content` lets the browser size the
67971
- * textarea from its value — no hidden mirror textarea to measure against (the
67972
- * technique libraries used before the property existed). `minRows`/`maxRows`
67973
- * become min/max heights in `lh` units on top of it; past `maxRows` the
67974
- * content scrolls.
67975
- *
67976
- * TextareaCharCount is the counter that goes with it, and the caller places
67977
- * it: under the box, in a form footer, next to a label — fed with the same
67978
- * value/signal as the textarea. The textarea draws no counter of its own.
67979
- *
67980
- * Styled as a `.navi_input` box (border, background, focus ring, readonly and
67981
- * disabled fades, variants): one look for everything one types into. The
67982
- * shared sheet is registered here too — a page may render a Textarea without
67983
- * any Input.
67984
- */
67985
- const css$r = /* css */`
67986
- .navi_input.navi_textarea {
67987
- .navi_control_input {
67988
- min-height: calc(var(--textarea-min-rows, 1.5) * 1lh);
67989
- /* Above maxRows the box stops growing and the content scrolls. The
67990
- 99999 fallback means "no cap" without needing a conditional rule. */
67991
- max-height: calc(var(--textarea-max-rows, 99999) * 1lh);
67992
- field-sizing: content;
67993
- /* Explicit, never normal, for two independent reasons.
67994
- minRows/maxRows are lengths in lh, and with line-height normal the lh
67995
- unit resolves to a theoretical value that does not match the real
67996
- rendered line — the box then jumps by a few pixels the moment the first
67997
- character replaces the theory with a real line.
67998
- And a line box under "normal" takes the height of the tallest font it
67999
- holds, so the one line carrying an emoji stands taller than the ones
68000
- around it — here, where the text is typed and no glyph can be wrapped
68001
- the way emojiAsIcon wraps one, the line height is the only lever.
68002
- 1.5 is also tall enough to contain an emoji's own box, so it is not
68003
- clipped either; a tighter value would keep the rows even and cut the
68004
- glyph. See docs/typography.md. */
68005
- line-height: 1.5;
68006
- /* The control grows itself; resizable below hands the handle back. */
68007
- resize: none;
68008
- overflow: auto;
68009
- /* A placeholder must be readable in full before anything is typed: a
68010
- field that opens already scrolled reads as a field that already has
68011
- text in it. Its wrapped height is measured (see usePlaceholderHeight)
68012
- because it only exists once laid out, and it only raises the floor
68013
- while the placeholder is what is being shown — what is typed sizes the
68014
- box on its own. */
68015
- &:placeholder-shown {
68016
- min-height: max(
68017
- calc(var(--textarea-min-rows, 1.5) * 1lh),
68018
- var(--x-textarea-placeholder-height, 0px)
68019
- );
68020
- }
68021
- }
68022
- &[data-resizable] .navi_control_input {
68023
- height: calc(var(--textarea-min-rows, 1.5) * 1lh);
68024
- /* The two are exclusive: with field-sizing content the browser removes
68025
- the resize handle (the size follows the content, there is nothing to
68026
- drag). resizable means the hand takes over — fixed sizing, starting
68027
- at minRows, and the drag writes its own inline height from there. */
68028
- field-sizing: fixed;
68029
- resize: vertical;
68030
- }
68031
- }
68032
- .navi_textarea_char_count {
68033
- color: color-mix(in srgb, currentColor 60%, transparent);
68034
- font-size: 0.75em;
68035
- user-select: none;
68036
- }
68037
- `;
68038
-
68039
- /**
68040
- * @type {import("ignore:preact").FunctionComponent<{
68041
- * value?: string,
68042
- * defaultValue?: string,
68043
- * signal?: import("@preact/signals").Signal<string>,
68044
- * name?: string,
68045
- * minRows?: number,
68046
- * maxRows?: number,
68047
- * resizable?: boolean,
68048
- * maxLength?: number,
68049
- * width?: string,
68050
- * [key: string]: any,
68051
- * }>}
68052
- * @param {number} [minRows=1.5] Height the empty control starts at, in lines.
68053
- * The default shows half of a second line: enough to read "multiline" at a
68054
- * glance without the height of a full extra row.
68055
- * @param {number} [maxRows] Lines after which the control stops growing and
68056
- * scrolls instead. Without it the control grows with its content.
68057
- * @param {boolean} [resizable] Give the browser's vertical resize handle back.
68058
- * An exchange, not an addition: the hand takes over from the automatic
68059
- * growth, so the control stops following what is typed and stays at the
68060
- * height it was last dragged to (starting at `minRows`).
68061
- * @param {number} [maxLength] The character limit, validated at submit. Pair
68062
- * with `maxLengthGuard` to block typing past it, and render a
68063
- * TextareaCharCount to show it.
68064
- * @param {string} [width="35ch"] The control's width. Fixed on purpose: with
68065
- * field-sizing the width would otherwise follow the longest line, and a box
68066
- * that widens while one types is a box one chases.
68067
- */
68068
- const Textarea = ({
68069
- // Destructured, never deleted off the props object: Preact reuses the same
68070
- // props object when an internal state update re-renders the component, so a
68071
- // delete would make these props vanish from the second render on (the box
68072
- // then jumps back to the default minRows at the first keystroke).
68073
- minRows = 1.5,
68074
- maxRows,
68075
- resizable,
68076
- width = "35ch",
68077
- ...props
68078
- }) => {
68079
- import.meta.css = [inputCss + css$r, "@jsenv/navi/src/control/input/textarea.jsx"];
68080
- const defaultRef = useRef(null);
68081
- props.ref = props.ref || defaultRef;
68082
- usePlaceholderHeight(props.ref, props.placeholder);
68083
- const [rootProps, hostProps, childrenWrapperProps] = useControlProps(props, {
68084
- controlType: "input"
68085
- });
68086
- const {
68087
- basePseudoState,
68088
- children
68089
- } = hostProps;
68090
- // Children go through ControlChildrenWrapper below; inside the <textarea>
68091
- // element they would become its text content.
68092
- delete hostProps.children;
68093
- const loading = basePseudoState[":-navi-loading"];
68094
- delete rootProps.width;
68095
- hostProps.width = width;
68096
- return jsxs(Box, {
68097
- as: "span",
68098
- inline: true,
68099
- flex: true,
68100
- baseClassName: "navi_input",
68101
- className: "navi_textarea",
68102
- ...rootProps,
68103
- basePseudoState: basePseudoState,
68104
- "data-resizable": resizable ? "" : undefined,
68105
- styleCSSVars: InputStyleCSSVars,
68106
- pseudoStateSelector: ".navi_control_input",
68107
- pseudoClasses: InputPseudoClasses,
68108
- pseudoElements: InputPseudoElements,
68109
- "data-callout-anchor": ".navi_control_input",
68110
- style: {
68111
- "--textarea-min-rows": minRows,
68112
- "--textarea-max-rows": maxRows,
68113
- ...rootProps.style
68114
- },
68115
- children: [jsx(LoadingOutline, {
68116
- loading: loading,
68117
- color: "var(--loader-color)",
68118
- inset: -1
68119
- }), jsx(RealTextarea, {
68120
- ...hostProps
68121
- }), jsx(ControlChildrenWrapper, {
68122
- ...childrenWrapperProps,
68123
- children: children
68124
- })]
68125
- });
68126
- };
68127
-
68128
- /**
68129
- * The counter that goes with a Textarea: "50/200" — how many characters are
68130
- * typed over how many the limit allows, the way Material writes it (just the
68131
- * count when there is no `maxLength`). Where it goes is the caller's call,
68132
- * which is why it is a separate component rather than something the textarea
68133
- * draws: put it under the box, in a form footer, next to a label, and feed it
68134
- * the same value or signal as the textarea.
68135
- *
68136
- * @type {import("ignore:preact").FunctionComponent<{
68137
- * value?: string,
68138
- * signal?: import("@preact/signals").Signal<string>,
68139
- * maxLength?: number,
68140
- * [key: string]: any,
68141
- * }>}
68142
- * @param {string} [value] The text being counted. Say `signal` instead for a
68143
- * two-way bound textarea: reading it here subscribes the count to it.
68144
- * @param {number} [maxLength] The limit, shown after the count ("50/200").
68145
- * Without it the count stands alone.
68146
- */
68147
- const TextareaCharCount = ({
68148
- value,
68149
- signal,
68150
- maxLength,
68151
- ...rest
68152
- }) => {
68153
- import.meta.css = [css$r, "@jsenv/navi/src/control/input/textarea.jsx"];
68154
- const resolvedValue = signal ? signal.value : value;
68155
- const length = typeof resolvedValue === "string" ? resolvedValue.length : 0;
68156
- return jsx(Box, {
68157
- as: "span",
68158
- baseClassName: "navi_textarea_char_count",
68159
- ...rest,
68160
- children: maxLength === undefined ? length : `${length}/${maxLength}`
68161
- });
68162
- };
68163
-
68164
- // `field-sizing: content` sizes the box from the value, and an empty field has
68165
- // none — the placeholder is text the browser refuses to make room for. So the
68166
- // height it wraps to is measured and published as --x-textarea-placeholder-height
68167
- // for the CSS above to use as a floor.
68168
- const usePlaceholderHeight = (ref, placeholder) => {
68169
- useLayoutEffect(() => {
68170
- const textareaEl = ref.current;
68171
- if (!placeholder) {
68172
- textareaEl.style.removeProperty("--x-textarea-placeholder-height");
68173
- return null;
68174
- }
68175
- let widthMeasured;
68176
- const measure = () => {
68177
- // What is typed sizes the box itself; the placeholder is not displayed
68178
- // then, and scrollHeight would report the value's height instead.
68179
- if (textareaEl.value !== "") {
68180
- return;
68181
- }
68182
- const {
68183
- paddingTop,
68184
- paddingBottom
68185
- } = getComputedStyle(textareaEl);
68186
- // Cleared before reading: scrollHeight can never report less than the
68187
- // height already applied, so measuring on top of a previous measure could
68188
- // only ever grow the box, never let it shrink back on a wider viewport.
68189
- textareaEl.style.setProperty("--x-textarea-placeholder-height", "0px");
68190
- const contentHeight = textareaEl.scrollHeight - parseFloat(paddingTop) - parseFloat(paddingBottom);
68191
- widthMeasured = textareaEl.clientWidth;
68192
- textareaEl.style.setProperty("--x-textarea-placeholder-height", `${contentHeight}px`);
68193
- };
68194
- measure();
68195
- // Measuring writes the variable that sets this element's own height, and
68196
- // mutating layout from inside a resize callback is what makes the browser
68197
- // report "ResizeObserver loop completed with undelivered notifications".
68198
- // So the write waits for the frame that resize produced.
68199
- let measureFrame = null;
68200
- const requestMeasure = () => {
68201
- if (measureFrame !== null) {
68202
- return;
68203
- }
68204
- measureFrame = requestAnimationFrame(() => {
68205
- measureFrame = null;
68206
- measure();
68207
- });
68208
- };
68209
- // The placeholder wraps against the available width, so a new width is a
68210
- // new number of lines. Height changes are ignored: this measure is what
68211
- // causes them, and reacting to them would be reacting to ourselves.
68212
- const resizeObserver = new ResizeObserver(() => {
68213
- if (textareaEl.clientWidth !== widthMeasured) {
68214
- requestMeasure();
68215
- }
68216
- });
68217
- resizeObserver.observe(textareaEl);
68218
- // The width may have changed while the field held a value, when measuring
68219
- // was impossible — emptying it is when the placeholder comes back.
68220
- const onInput = () => {
68221
- if (textareaEl.value === "") {
68222
- measure();
68223
- }
68224
- };
68225
- textareaEl.addEventListener("input", onInput);
68226
- return () => {
68227
- if (measureFrame !== null) {
68228
- cancelAnimationFrame(measureFrame);
68229
- }
68230
- resizeObserver.disconnect();
68231
- textareaEl.removeEventListener("input", onInput);
68232
- };
68233
- }, [placeholder]);
68234
- };
68235
- const RealTextarea = ({
68236
- maxLength,
68237
- ...domProps
68238
- }) => {
68239
- const autoSelectReadOnlyProps = useAutoSelectReadOnly(domProps);
68240
- return jsx(Box, {
68241
- ...domProps,
68242
- as: "textarea",
68243
- baseClassName: "navi_control_input",
68244
- ...autoSelectReadOnlyProps,
68245
- // Native maxLength stays off, like RealInput in input_textual.jsx: the
68246
- // maxLengthGuard handles live blocking, the constraint validates at
68247
- // submit, and navi-max-length keeps the value readable from the DOM.
68248
- "navi-max-length": maxLength
68249
- });
68250
- };
68251
-
68252
68332
  const Unit = ({
68253
68333
  unit,
68254
68334
  plural,
@@ -68988,9 +69068,10 @@ const css$o = /* css */`
68988
69068
  /* Room for the chevron, which sits over the padding rather than beside
68989
69069
  the control — anything beside it would be a click that misses. */
68990
69070
  padding-right: calc(var(--x-padding-right) + 1em);
68991
- /* A form control keeps a line of its own whatever the page is written in,
68992
- and lh units elsewhere in the box are resolved against a real number. */
68993
- line-height: normal;
69071
+ /* Same line as every other field (see --navi-line-height): a select and
69072
+ an input side by side must be the same height, and lh units elsewhere
69073
+ in the box need a real number to resolve against. */
69074
+ line-height: var(--navi-control-line-height);
68994
69075
  /* The closed control is drawn by us so it matches the other fields; the
68995
69076
  list it opens is untouched and stays the system's. */
68996
69077
  appearance: none;
@@ -76302,7 +76383,10 @@ installImportMetaCssBuild(import.meta);const css$c = /* css */`
76302
76383
  --x-color-contrasting: var(--navi-color-white);
76303
76384
  --x-color: var(--color, var(--x-color-contrasting));
76304
76385
  --badge-count-padding-x-default: 0.5em;
76305
- --badge-count-padding-y-default: 0.2em;
76386
+ /* Ink to edge: ~6px above and below the digits at the default size, for
76387
+ ~7px on the sides — a little less than the sides, which is what reads
76388
+ as balanced on a pill; 0.2em read as squashed. */
76389
+ --badge-count-padding-y-default: 0.3em;
76306
76390
 
76307
76391
  /* Each side resolves the most specific value it was given, from the side
76308
76392
  itself down to the axis, the shorthand, then the default. */
@@ -76339,6 +76423,11 @@ installImportMetaCssBuild(import.meta);const css$c = /* css */`
76339
76423
  color: var(--x-color);
76340
76424
  font-size: var(--font-size);
76341
76425
  font-variant-numeric: tabular-nums;
76426
+ /* Its own line, relative to its own font: inherited from a control it
76427
+ would arrive as that control's pixels (a button's 17px), and a badge
76428
+ drawn bigger or smaller than the button's text would get a line box
76429
+ that does not match its glyph — a digit off its circle's center. */
76430
+ line-height: var(--navi-line-height);
76342
76431
  vertical-align: inherit;
76343
76432
 
76344
76433
  &[data-accent-needs-dark-fg] {
@@ -76362,7 +76451,6 @@ installImportMetaCssBuild(import.meta);const css$c = /* css */`
76362
76451
  padding-right: var(--x-badge-count-padding-right);
76363
76452
  padding-bottom: var(--x-badge-count-padding-bottom);
76364
76453
  padding-left: var(--x-badge-count-padding-left);
76365
- line-height: normal;
76366
76454
  background: var(--x-background);
76367
76455
  background-color: var(--x-background-color);
76368
76456
  border-radius: 1em;
@@ -76417,7 +76505,10 @@ installImportMetaCssBuild(import.meta);const css$c = /* css */`
76417
76505
  --x-number-font-size: unset;
76418
76506
  }
76419
76507
  &[data-two-chars] {
76420
- --x-radius: 2em;
76508
+ /* 1.8em of the badge font is 1.26em of the text's: inside the line
76509
+ (1.25), so a circle beside a button's label does not make that
76510
+ button taller than its neighbours. 2em did, by a pixel and a half. */
76511
+ --x-radius: 1.8em;
76421
76512
  --x-number-font-size: unset;
76422
76513
  }
76423
76514
  &[data-three-chars] {
@@ -76451,7 +76542,11 @@ const BadgeCount = ({
76451
76542
  integer,
76452
76543
  lang,
76453
76544
  loading,
76454
- textAnchor = "center",
76545
+ // On the capitals rather than on the line box: a count sits beside a word,
76546
+ // and the eye centers it on the letters, not on the leading around them —
76547
+ // the line box center is above that, by half the leading plus what the
76548
+ // font keeps above its capitals (see TextAnchor).
76549
+ textAnchor = "char-center",
76455
76550
  lineLayout,
76456
76551
  ...props
76457
76552
  }) => {
@@ -77084,9 +77179,6 @@ const MessageBox = ({
77084
77179
  padding = "sm",
77085
77180
  icon,
77086
77181
  leftStripe,
77087
- // A message is free text: an emoji is expected in it, and must not push
77088
- // the first line down next to the icon and the close button.
77089
- emojiAsIcon = true,
77090
77182
  children,
77091
77183
  onClose,
77092
77184
  ...rest
@@ -77130,7 +77222,6 @@ const MessageBox = ({
77130
77222
  aspectRatio: "auto",
77131
77223
  children: icon
77132
77224
  }), jsx(Text, {
77133
- emojiAsIcon: emojiAsIcon,
77134
77225
  children: children
77135
77226
  }), onClose &&
77136
77227
  // A column as tall as the first line of the message, pinned to the
@@ -79267,5 +79358,5 @@ const UserSvg = () => jsx("svg", {
79267
79358
  })
79268
79359
  });
79269
79360
 
79270
- export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, BadgeList, Binder, Box, Button, ButtonCopyToClipboard, CalloutStatusIcon, Caption, CardLayout, CheckSvg, CheckboxGroup, CloseSvg, Code, Col, Colgroup, Color, ConstructionSvg, ControlGroup, DaySpin, Details, Dialog, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, Expandable, EyeClosedSvg, EyeSvg, Field, FixedBar, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, InfoSvg, Input, InputDuration, Interpolate, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, LinkCurrentSvg, List, ListItem, ListItemGroup, ListItems, Loading, LoadingDotsSvg, LoadingIndicator, LoadingIndicatorFluid, LoadingOutline, MessageBox, Meter, Nav, NaviDebug, NumberSpin, Paragraph, Picker, Popover, Popup, Quantity, RadioGroup, Route, RouteTransitionArea, RouteTravel, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, Select, SelectableInput, SelectionContext, Separator, SettingsSvg, SidePanel, Slide, SlideContainer, Spin, SpinGroup, SplitButton, StarSvg, Step, StepList, SummaryMarker, Svg, Table, TableCell, Tbody, Text, TextBox, Textarea, TextareaCharCount, Thead, Time, TimeRangeSpin, TimeRangeWheel, TimeSpin, TimeWheel, Title, Tr, UITransition, Unit, UserSvg, ViewportLayout, Wheel, WheelGroup, WheelItem, actionRunEffect, anyMatchingRouteSignal, applySearch, arraySignalMembership, canNavBackSignal, canNavForwardSignal, coarsePointerSignal, compareTwoJsValues, constraintFromValidityRule, createAction, createAvailableConstraint, createI18n, createRequestCanceller, createSearch, createSelectionKeyboardShortcuts, createSlot, defineInteractionDetector, defineRouteDefaultTransition, defineRouteTransition, detectHorizontalOverflow, enableDebugActions, enableDebugOnDocumentLoading, ensureDocumentStartViewTransition, errorIsDisplayed, filterTableSelection, formatDatetime, formatDay, formatDayRelative, formatMonth, formatNumber, formatTime, formatTimeRelative, getNowHours, getNowHoursRoundedToStep, interpolateText, isCellSelected, isColumnSelected, isRowSelected, isScrolling, isToday, languagesSignal, localStorageSignal, markErrorAsDisplayedBy, moveArrayItemByIndex, navBack, navForward, navIntegratedVia, navTo, naviI18n, openCallout, rawUrlPart, registerGlobalConstraint, reload, renderEmojiAsIcon, rerunActions, resource, route, routeAction, scrollActivitySignal, setBaseUrl, setPreferredLanguage, setSupportedLanguages, setUrlTargetOptions, setupRoutes, smallTouchScreenSignal, stateSignal, stopLoad, stringifyTableSelectionValue, swapArrayItemByIndex, syncOwnedResourceToSignals, syncResourceToSignals, triggerNaviCommand, updateActions, useActionStatus, useArraySignalMembership, useAsyncData, useCalloutElement, useCalloutRequestClose, useCanNavBack, useCanNavForward, useCancelPrevious, useCellGridFromRows, useConstraintValidityState, useDependenciesDiff, useDisplayedLayoutEffect, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useInputGroup, useKeyboardShortcuts, useNavState, useOrderedColumns, usePopupMode, useRouteStatus, useRunOnMount, useSearchText, useSelectableElement, useSelectionController, useSignalSync, useSlideValue, useStateArray, useTitleLevel, useUrlSearchParam, useUrlTargetId, valueInLocalStorage, windowWidthSignal };
79361
+ export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, BadgeList, Binder, Box, Button, ButtonCopyToClipboard, CalloutStatusIcon, Caption, CardLayout, CheckSvg, CheckboxGroup, CloseSvg, Code, Col, Colgroup, Color, ConstructionSvg, ControlGroup, DaySpin, Details, Dialog, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, Expandable, EyeClosedSvg, EyeSvg, Field, FixedBar, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, InfoSvg, Input, InputDuration, Interpolate, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, LinkCurrentSvg, List, ListItem, ListItemGroup, ListItems, Loading, LoadingDotsSvg, LoadingIndicator, LoadingIndicatorFluid, LoadingOutline, MessageBox, Meter, Nav, NaviDebug, NumberSpin, Paragraph, Picker, Popover, Popup, Quantity, RadioGroup, Route, RouteTransitionArea, RouteTravel, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, Select, SelectableInput, SelectionContext, Separator, SettingsSvg, SidePanel, Slide, SlideContainer, Spin, SpinGroup, SplitButton, StarSvg, Step, StepList, SummaryMarker, Svg, Table, TableCell, Tbody, Text, TextBox, Textarea, TextareaCharCount, Thead, Time, TimeRangeSpin, TimeRangeWheel, TimeSpin, TimeWheel, Title, Tr, UITransition, Unit, UserSvg, ViewportLayout, Wheel, WheelGroup, WheelItem, actionRunEffect, anyMatchingRouteSignal, applySearch, arraySignalMembership, canNavBackSignal, canNavForwardSignal, coarsePointerSignal, compareTwoJsValues, constraintFromValidityRule, createAction, createAvailableConstraint, createI18n, createRequestCanceller, createSearch, createSelectionKeyboardShortcuts, createSlot, defineInteractionDetector, defineRouteDefaultTransition, defineRouteTransition, detectHorizontalOverflow, enableDebugActions, enableDebugOnDocumentLoading, ensureDocumentStartViewTransition, errorIsDisplayed, filterTableSelection, formatDatetime, formatDay, formatDayRelative, formatMonth, formatNumber, formatTime, formatTimeRelative, getNowHours, getNowHoursRoundedToStep, interpolateText, isCellSelected, isColumnSelected, isRowSelected, isScrolling, isToday, languagesSignal, localStorageSignal, markErrorAsDisplayedBy, moveArrayItemByIndex, navBack, navForward, navIntegratedVia, navTo, naviI18n, openCallout, rawUrlPart, registerGlobalConstraint, reload, rerunActions, resource, route, routeAction, scrollActivitySignal, setBaseUrl, setPreferredLanguage, setSupportedLanguages, setUrlTargetOptions, setupRoutes, smallTouchScreenSignal, stateSignal, stopLoad, stringifyTableSelectionValue, swapArrayItemByIndex, syncOwnedResourceToSignals, syncResourceToSignals, triggerNaviCommand, updateActions, useActionStatus, useArraySignalMembership, useAsyncData, useCalloutElement, useCalloutRequestClose, useCanNavBack, useCanNavForward, useCancelPrevious, useCellGridFromRows, useConstraintValidityState, useDependenciesDiff, useDisplayedLayoutEffect, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useInputGroup, useKeyboardShortcuts, useNavState, useOrderedColumns, usePopupMode, useRouteStatus, useRunOnMount, useSearchText, useSelectableElement, useSelectionController, useSignalSync, useSlideValue, useStateArray, useTitleLevel, useUrlSearchParam, useUrlTargetId, valueInLocalStorage, windowWidthSignal };
79271
79362
  //# sourceMappingURL=jsenv_navi.js.map