@jsenv/navi 0.29.152 → 0.29.154

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.
@@ -5354,6 +5354,26 @@ const extractMessageAndRemainingProps = (props) => {
5354
5354
  return [ownMessages, remaining];
5355
5355
  };
5356
5356
 
5357
+ /**
5358
+ * The one-line form of a constraint message, or `undefined` when it has none.
5359
+ *
5360
+ * A message is not always a sentence: it can be an `Error`, or a whole element
5361
+ * (an `errorMapping` returning JSX to put a link inside the callout). Those
5362
+ * still render fine in the callout, but they have no one-line form, and
5363
+ * anything that needs one — a `title` attribute, a caller drawing its own
5364
+ * summary — must be given nothing rather than `String(message)`, which writes
5365
+ * "[object Object]" on the screen.
5366
+ */
5367
+ const getMessageString = (message) => {
5368
+ if (typeof message === "string") {
5369
+ return message;
5370
+ }
5371
+ if (Error.isError(message)) {
5372
+ return message.message;
5373
+ }
5374
+ return undefined;
5375
+ };
5376
+
5357
5377
  const getConstraintMessage = (
5358
5378
  controller,
5359
5379
  constraint,
@@ -12147,7 +12167,9 @@ const createControlValidation = (
12147
12167
  typeof checkResult === "string"
12148
12168
  ? { message: checkResult }
12149
12169
  : checkResult;
12150
- constraintValidityInfo.messageString = constraintValidityInfo.message;
12170
+ constraintValidityInfo.messageString = getMessageString(
12171
+ constraintValidityInfo.message,
12172
+ );
12151
12173
  debugUIState(
12152
12174
  `${elementSig} constraint "${constraint.name}" failed -> ${constraintValidityInfo.message}`,
12153
12175
  );
@@ -12189,10 +12211,15 @@ const createControlValidation = (
12189
12211
  if (titleLess) {
12190
12212
  const element = controller.ref.current;
12191
12213
  if (element) {
12192
- element.setAttribute(
12193
- "title",
12194
- activeFailedConstraintInfo.messageString,
12195
- );
12214
+ const { messageString } = activeFailedConstraintInfo;
12215
+ if (messageString) {
12216
+ element.setAttribute("title", messageString);
12217
+ } else {
12218
+ // The message has no one-line form (an element, typically): the
12219
+ // callout shows it whole, and the tooltip stays empty rather than
12220
+ // saying "[object Object]".
12221
+ element.removeAttribute("title");
12222
+ }
12196
12223
  }
12197
12224
  }
12198
12225
  } else {
@@ -12589,6 +12616,13 @@ const useExecuteAction = (
12589
12616
  let message;
12590
12617
  if (errorMapping) {
12591
12618
  const errorMappingResult = errorMapping(error);
12619
+ // Anything the chain below does not recognize — nothing returned, above
12620
+ // all — leaves `message` undefined and the control displays nothing. That
12621
+ // is the contract, and both halves of it are useful:
12622
+ // - returning nothing says "don't show this one", per error, where
12623
+ // actionErrorEffect="none" says it once for the whole control;
12624
+ // - not handling an error means returning it untouched (`return error`),
12625
+ // and it is then shown exactly as if there were no mapping at all.
12592
12626
  if (typeof errorMappingResult === "string") {
12593
12627
  message = errorMappingResult;
12594
12628
  } else if (Error.isError(errorMappingResult)) {
@@ -61126,12 +61160,14 @@ const SKELETON_LIST_ITEM_CLASS = "navi_list_item_skeleton";
61126
61160
  // inside it (see ListItems): a run draws the rows it frames and holds the room
61127
61161
  // of the others.
61128
61162
  const RenderWindowContext = createContext(null);
61129
- // Carries List's own `columns` prop (a grid-template-columns value, e.g.
61163
+ // Carries List's own `itemColumns` prop (a grid-template-columns value, e.g.
61130
61164
  // "1fr auto auto") down to each ListItem/filler/fallback so they can render
61131
61165
  // as a subgrid row instead of a flex row — see ListItem's own use of this
61132
- // context, and List's own `columns` doc, for the full rationale (table-like
61166
+ // context, and List's own `itemColumns` doc, for the full rationale (table-like
61133
61167
  // column sizing that stays correct across a virtualized, windowed item set).
61134
- const ListColumnsContext = createContext(null);
61168
+ // List's own `columns` never reaches here: it lays the items themselves into
61169
+ // the track's columns, and an item then occupies a cell like anything else.
61170
+ const ListItemColumnsContext = createContext(null);
61135
61171
  // Carries the separator element/function down to each ListItem so separators
61136
61172
  // are only rendered between items that actually mount (post-filter, post-window).
61137
61173
  const SeparatorContext = createContext(null);
@@ -61217,6 +61253,8 @@ const css$x = /* css */`@layer navi {
61217
61253
  --x-list-scroll-spacing-bottom: calc(var(--list-footer-height, 0px) + var(--list-scroll-padding-bottom, 0px));
61218
61254
  --x-list-scroll-spacing-left: calc(var(--list-header-width, 0px) + var(--list-scroll-padding-left, 0px));
61219
61255
  --x-list-scroll-spacing-right: calc(var(--list-footer-width, 0px) + var(--list-scroll-padding-right, 0px));
61256
+ min-width: 0;
61257
+ max-width: var(--list-max-width, 100%);
61220
61258
  background-color: var(--x-list-background-color);
61221
61259
  border: var(--x-list-border-width) solid var(--x-list-border-color);
61222
61260
  border-top-left-radius: var(--x-corner-top-left-radius, var(--x-list-border-radius));
@@ -61224,8 +61262,6 @@ const css$x = /* css */`@layer navi {
61224
61262
  border-bottom-right-radius: var(--x-corner-bottom-right-radius, var(--x-list-border-radius));
61225
61263
  border-bottom-left-radius: var(--x-corner-bottom-left-radius, var(--x-list-border-radius));
61226
61264
  flex-direction: column;
61227
- min-width: 0;
61228
- max-width: 100%;
61229
61265
  transition: opacity .2s;
61230
61266
  display: flex;
61231
61267
  overflow: hidden;
@@ -61251,7 +61287,6 @@ const css$x = /* css */`@layer navi {
61251
61287
  min-width: inherit;
61252
61288
  max-width: var(--list-max-width, inherit);
61253
61289
  max-height: var(--list-max-height, inherit);
61254
- flex-wrap: inherit;
61255
61290
  overflow-anchor: none;
61256
61291
  overscroll-behavior: inherit;
61257
61292
  scrollbar-width: inherit;
@@ -61313,7 +61348,6 @@ const css$x = /* css */`@layer navi {
61313
61348
 
61314
61349
  .navi_list {
61315
61350
  box-sizing: border-box;
61316
- flex-wrap: inherit;
61317
61351
  outline: none;
61318
61352
  margin: 0;
61319
61353
  padding: 0;
@@ -61596,6 +61630,7 @@ const ListUI = props => {
61596
61630
  hoverWhileScrolling = false,
61597
61631
  lockSize,
61598
61632
  columns,
61633
+ itemColumns,
61599
61634
  searchText,
61600
61635
  searchNoMatchMode = "remove",
61601
61636
  loading,
@@ -61830,6 +61865,7 @@ const ListUI = props => {
61830
61865
  styleCSSVars: LIST_STYLE_CSS_VARS,
61831
61866
  pseudoClasses: LIST_PSEUDO_CLASSES,
61832
61867
  hasChildUsingForwardedProps: true,
61868
+ childPropSet: LIST_TRACK_PROP_SET,
61833
61869
  onnavi_request_scroll: e => {
61834
61870
  if (!Object.hasOwn(e.detail, "id")) {
61835
61871
  console.warn(`navi_request_scroll event is missing the "id" property in its detail.`, e);
@@ -61859,6 +61895,7 @@ const ListUI = props => {
61859
61895
  horizontal: horizontal,
61860
61896
  spacing: spacing,
61861
61897
  columns: columns,
61898
+ itemColumns: itemColumns,
61862
61899
  tracker: tracker,
61863
61900
  renderWindow: renderWindow,
61864
61901
  virtual: virtual,
@@ -61924,12 +61961,47 @@ const ListFirstResolver = props => {
61924
61961
  * horizontal?: boolean,
61925
61962
  * spacing?: string,
61926
61963
  * columns?: string,
61964
+ * itemColumns?: string,
61965
+ * alignX?: string,
61966
+ * alignY?: string,
61967
+ * flexWrap?: boolean,
61927
61968
  * expandX?: boolean,
61928
61969
  * expandY?: boolean,
61929
61970
  * expand?: boolean,
61930
61971
  * children?: import("ignore:preact").ComponentChildren,
61931
61972
  * [key: string]: any,
61932
61973
  * }>}
61974
+ * @param {string} [props.columns]
61975
+ * The list's own columns: a `grid-template-columns` value the ITEMS are laid
61976
+ * into — a sheet of icons (`repeat(auto-fill, minmax(2.5rem, 1fr))`), a row of
61977
+ * choices (`repeat(3, minmax(0, 1fr))`). Each item takes one cell, and an item
61978
+ * meant to take a whole line says so for itself
61979
+ * (`style={{ gridColumn: "1 / -1" }}`). Several items to a line is a shape the
61980
+ * list cannot virtualize — its render window and the room it holds for the
61981
+ * rows it does not draw both count one item per line — so it is for a set of
61982
+ * items the caller renders whole, not for a `<List.Items>` collection.
61983
+ * @param {string} [props.itemColumns]
61984
+ * The columns inside an ITEM: a `grid-template-columns` value each item fills
61985
+ * with its own children, a table whose cells line up down the list. Every item
61986
+ * becomes a subgrid row spanning all the columns, so a column is as wide as
61987
+ * the widest cell in it among the rows actually in the DOM — real column
61988
+ * sizing that stays right as the window moves. Rows of a table, then, where
61989
+ * `columns` above is a grid of items; the two cannot both be set.
61990
+ * @param {string} [props.alignX]
61991
+ * Where the items sit across the track — `alignX="center"` centres a
61992
+ * horizontal list's row of items inside a list wider than they are. Together
61993
+ * with `alignY`, `align` and `flexWrap`, this reaches the `<ul>` holding the
61994
+ * items rather than the frame drawn around it: the frame's only child is the
61995
+ * scroll box, which fills it and has nothing to arrange.
61996
+ * @param {boolean} [props.flexWrap]
61997
+ * Lets a horizontal list's items fall to the next line instead of running
61998
+ * past the edge — a row of choices under a `maxWidth`, say. Same caveat as
61999
+ * `columns` above: a line holding several items is not virtualizable.
62000
+ * @param {string} [props.overflow]
62001
+ * `"visible"` lets the items paint outside the list — a check in a row's
62002
+ * corner, a badge crossing the edge. A list clips by default, which is what
62003
+ * its rounded corners and its scroll box need, and the two cannot both be
62004
+ * true: asking for visible gives up the clipping, corners included.
61933
62005
  * @param {boolean} [props.itemTransition]
61934
62006
  * Names each row, so a change the application wraps in
61935
62007
  * `document.startViewTransition` is seen row by row — rows moving to their new
@@ -62053,6 +62125,7 @@ const ListContent = ({
62053
62125
  horizontal,
62054
62126
  spacing,
62055
62127
  columns,
62128
+ itemColumns,
62056
62129
  tracker,
62057
62130
  renderWindow,
62058
62131
  virtual,
@@ -62098,6 +62171,7 @@ const ListContent = ({
62098
62171
  horizontal: horizontal,
62099
62172
  spacing: spacing,
62100
62173
  columns: columns,
62174
+ itemColumns: itemColumns,
62101
62175
  ...listProps,
62102
62176
  tracker: tracker,
62103
62177
  renderWindow: renderWindow,
@@ -62109,6 +62183,12 @@ const ListContent = ({
62109
62183
  })
62110
62184
  });
62111
62185
  };
62186
+ // Where the items sit is a question about the track (the <ul>), not about the
62187
+ // frame around it: the frame's only child is the scroll box, which fills it and
62188
+ // has nothing to arrange. Box moves these out of the container's own props and
62189
+ // into BoxForwardedPropsContext, which ListContent reads and hands to the <ul>
62190
+ // — the same route `horizontal`, `spacing` and the column props take by hand.
62191
+ const LIST_TRACK_PROP_SET = new Set(["align", "alignX", "alignY", "flexWrap"]);
62112
62192
  const LIST_STYLE_CSS_VARS = {
62113
62193
  maxHeight: "--list-max-height",
62114
62194
  maxWidth: "--list-max-width",
@@ -63580,6 +63660,7 @@ const UnorderedList = ({
63580
63660
  horizontal,
63581
63661
  spacing,
63582
63662
  columns,
63663
+ itemColumns,
63583
63664
  children,
63584
63665
  ...rest
63585
63666
  }) => {
@@ -63588,11 +63669,19 @@ const UnorderedList = ({
63588
63669
  // A loading state drawing nothing keeps the message: an announced count of 0
63589
63670
  // already tells us the list is empty (see ListUI's loadingPlaceholderShown).
63590
63671
  const suppressFallback = loadingPlaceholderShown || Boolean(error);
63672
+
63673
+ // One track, one template: the two column props are the same
63674
+ // grid-template-columns seen from either end, and only differ in what an
63675
+ // item does with it (see useItemColumnsOverrideProps).
63676
+ if (columns && itemColumns) {
63677
+ console.warn(`List: "columns" and "itemColumns" cannot both be set — they define the same track. "columns" lays the items into the columns; "itemColumns" gives each item the columns its own children fill.`);
63678
+ }
63679
+ const trackColumns = columns || itemColumns;
63591
63680
  return jsxs(Box, {
63592
63681
  as: "ul",
63593
- flex: columns ? undefined : horizontal ? "x" : "y",
63594
- grid: columns ? true : undefined,
63595
- gridTemplateColumns: columns,
63682
+ flex: trackColumns ? undefined : horizontal ? "x" : "y",
63683
+ grid: trackColumns ? true : undefined,
63684
+ gridTemplateColumns: trackColumns,
63596
63685
  ...rest,
63597
63686
  spacing: spacing,
63598
63687
  baseClassName: "navi_list",
@@ -63614,8 +63703,8 @@ const UnorderedList = ({
63614
63703
  value: virtual,
63615
63704
  children: jsx(ListRowContext.Provider, {
63616
63705
  value: null,
63617
- children: jsx(ListColumnsContext.Provider, {
63618
- value: columns || null,
63706
+ children: jsx(ListItemColumnsContext.Provider, {
63707
+ value: columns ? null : itemColumns || null,
63619
63708
  children: jsx(ListDeclaredChildren, {
63620
63709
  children: children
63621
63710
  })
@@ -63684,8 +63773,8 @@ const VirtualFiller = ({
63684
63773
  });
63685
63774
  };
63686
63775
 
63687
- // List's own `columns` prop (see ListColumnsContext) turns a list item into
63688
- // a subgrid row instead of a flex row: its own children become direct grid
63776
+ // List's own `itemColumns` prop (see ListItemColumnsContext) turns a list item
63777
+ // into a subgrid row instead of a flex row: its own children become direct grid
63689
63778
  // items of List's own <ul>, so column widths are computed from whichever
63690
63779
  // rows are actually in the DOM (the currently-windowed items plus the
63691
63780
  // always-mounted header/footer) — real grid/table column sizing, not a
@@ -63695,11 +63784,10 @@ const VirtualFiller = ({
63695
63784
  // this they'd silently stay flex rows and break column alignment against
63696
63785
  // the rest of the grid). `flex` is force-cleared here because Box picks
63697
63786
  // flex over grid when both are set (see box.jsx's own boxFlow resolution),
63698
- // so a caller-provided `flex` prop (leftover from a non-columns usage)
63699
- // would otherwise silently win over this.
63700
- const useListItemColumnsOverrideProps = callerStyle => {
63701
- const columns = useContext(ListColumnsContext);
63702
- if (!columns) {
63787
+ // so a caller-provided `flex` prop would otherwise silently win over this.
63788
+ const useItemColumnsOverrideProps = callerStyle => {
63789
+ const itemColumns = useContext(ListItemColumnsContext);
63790
+ if (!itemColumns) {
63703
63791
  return undefined;
63704
63792
  }
63705
63793
  return {
@@ -63763,11 +63851,11 @@ const ListItemPresentationResolver = props => {
63763
63851
  });
63764
63852
  };
63765
63853
  const ListItemPresentation = props => {
63766
- const columnsOverrideProps = useListItemColumnsOverrideProps(props.style);
63854
+ const itemColumnsOverrideProps = useItemColumnsOverrideProps(props.style);
63767
63855
  return jsx(Box, {
63768
63856
  as: "li",
63769
63857
  ...props,
63770
- ...columnsOverrideProps
63858
+ ...itemColumnsOverrideProps
63771
63859
  });
63772
63860
  };
63773
63861
  // A <List.Item skeleton> — a non-interactive placeholder row shown while a list
@@ -63797,14 +63885,14 @@ const ListItemSkeleton = props => {
63797
63885
  paddingY = "s",
63798
63886
  ...rest
63799
63887
  } = props;
63800
- const columnsOverrideProps = useListItemColumnsOverrideProps(rest.style);
63888
+ const itemColumnsOverrideProps = useItemColumnsOverrideProps(rest.style);
63801
63889
  return jsx(Box, {
63802
63890
  as: "li",
63803
63891
  role: "presentation",
63804
63892
  "aria-hidden": "true",
63805
63893
  paddingY: paddingY,
63806
63894
  ...rest,
63807
- ...columnsOverrideProps,
63895
+ ...itemColumnsOverrideProps,
63808
63896
  baseClassName: `navi_list_item ${SKELETON_LIST_ITEM_CLASS}`,
63809
63897
  children: children ?? jsx(Text, {
63810
63898
  loading: true
@@ -63965,7 +64053,7 @@ const ListItemReal = props => {
63965
64053
  // CSS Highlight API: mark matching text ranges from matchInfo.matchRanges,
63966
64054
  // if any (there is no standalone highlight prop — see ListItem's own doc).
63967
64055
  useSearchHighlight(ref, matchInfo?.matchRanges, [children, hidden]);
63968
- const columnsOverrideProps = useListItemColumnsOverrideProps(rest.style);
64056
+ const itemColumnsOverrideProps = useItemColumnsOverrideProps(rest.style);
63969
64057
  // <List itemTransition>: the row carries the name it is to be paired by, and
63970
64058
  // the stylesheet turns it into a view-transition-name where a browser can
63971
64059
  // draw it inside the list (see the @supports block in the css above).
@@ -64034,7 +64122,7 @@ const ListItemReal = props => {
64034
64122
  id: id,
64035
64123
  "navi-list-item-real": "",
64036
64124
  ...rest,
64037
- ...columnsOverrideProps,
64125
+ ...itemColumnsOverrideProps,
64038
64126
  index: undefined,
64039
64127
  selected: undefined
64040
64128
  // We use aria-hidden and not hidden because hidden would be forced to