@jsenv/navi 0.29.443 → 0.29.445

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.
@@ -6281,6 +6281,10 @@ const CONTROL_PROP_SET = new Set([
6281
6281
  "charGuard",
6282
6282
  "maxLengthGuard",
6283
6283
 
6284
+ // A checkbox that is a yes/no: `true` checked, `false` unchecked, instead of
6285
+ // HTML's value-or-nothing (see resolveEmptyUIState).
6286
+ "boolean",
6287
+
6284
6288
  // This control answers for itself: it registers into no group, no form and no
6285
6289
  // picker around it (see useUIStateController). It belongs here rather than
6286
6290
  // being deleted off `props` where it is read: props is the vnode's own object
@@ -42149,6 +42153,14 @@ const useUIStateController = (
42149
42153
  return ownUIState || parentUIState;
42150
42154
  })
42151
42155
  : ownUIStateSignal;
42156
+ // What the control is worth, which its action is bound to: what it
42157
+ // holds, or its own empty when it holds nothing. Apart from
42158
+ // uiStateSignal because unchecked is `undefined` there (the DOM reads any
42159
+ // other value as checked), while a yes/no unchecked is worth `false`.
42160
+ const emptyUIStateSignal = signal(emptyUIState);
42161
+ const valueSignal = computed(() =>
42162
+ uiStateOrEmpty(uiStateSignal.value, emptyUIStateSignal.value),
42163
+ );
42152
42164
 
42153
42165
  // The two-way half of a bound `signal` prop: setting it re-renders and
42154
42166
  // re-syncs via state_prop_change, but with the same value → guarded as a
@@ -42197,6 +42209,7 @@ const useUIStateController = (
42197
42209
  // travel command ended up carrying a picker's selection: see
42198
42210
  // resolveCommandValue in commands.js.
42199
42211
  ownUIStateSignal,
42212
+ valueSignal,
42200
42213
  value: controlInfo.value,
42201
42214
  // The suggestion this control started on — what tells a field showing
42202
42215
  // its default from one carrying an answer (see isUIStateHeld).
@@ -42256,7 +42269,11 @@ const useUIStateController = (
42256
42269
  }
42257
42270
  s.uiActionInternal?.(currentUIState, e);
42258
42271
  if (s.uiAction) {
42259
- s.uiAction(currentUIState, e);
42272
+ const value = uiStateOrEmpty(
42273
+ currentUIState,
42274
+ controller.emptyUIState,
42275
+ );
42276
+ s.uiAction(value, e);
42260
42277
  }
42261
42278
  if (skipCommand) ; else {
42262
42279
  const command = controller.controlHostProps.command;
@@ -42743,6 +42760,7 @@ const useUIStateController = (
42743
42760
  return {
42744
42761
  controller,
42745
42762
  parentUiStateSignalHolder,
42763
+ emptyUIStateSignal,
42746
42764
  ...liveValues(),
42747
42765
  };
42748
42766
  },
@@ -42864,6 +42882,7 @@ const useUIStateController = (
42864
42882
  );
42865
42883
  scope.parentUiStateSignalHolder.value =
42866
42884
  parentUIStateController?.uiStateSignal ?? null;
42885
+ scope.emptyUIStateSignal.value = emptyUIState;
42867
42886
 
42868
42887
  const { controller } = scope;
42869
42888
  const controllerRef = controller.ref;
@@ -43074,14 +43093,11 @@ const keptAndNamedChildUIStates = (children, fallbackState, kept) => {
43074
43093
  const readNamedChildUIStates = (children, { warnNameless }) => {
43075
43094
  const values = {};
43076
43095
  for (const child of children) {
43077
- const { name, emptyUIState } = child;
43096
+ const { name } = child;
43078
43097
  // A control holding nothing writes its own empty, not a hole: the key is in
43079
43098
  // the object either way, and what is read from it keeps the shape the
43080
43099
  // reader was promised (see resolveEmptyUIState).
43081
- const uiState =
43082
- child.uiState === undefined && emptyUIState !== undefined
43083
- ? emptyUIState
43084
- : child.uiState;
43100
+ const uiState = uiStateOrEmpty(child.uiState, child.emptyUIState);
43085
43101
  if (!name) {
43086
43102
  if (isNamelessGrouping(child, uiState)) {
43087
43103
  Object.assign(
@@ -44413,11 +44429,10 @@ const useUIFacadeStateController = (props, realUIStateController) => {
44413
44429
  // whose last item was unselected holds [], the same thing clearing it
44414
44430
  // leaves — not a value that changes type on its owner the moment it
44415
44431
  // empties.
44416
- const { emptyUIState } = s.realUIStateController;
44417
- const uiStateToAdopt =
44418
- child.uiState === undefined && emptyUIState !== undefined
44419
- ? emptyUIState
44420
- : child.uiState;
44432
+ const uiStateToAdopt = uiStateOrEmpty(
44433
+ child.uiState,
44434
+ s.realUIStateController.emptyUIState,
44435
+ );
44421
44436
  s.realUIStateController.setUIState(uiStateToAdopt, propagateUpEvent);
44422
44437
  updatingRef.current = false;
44423
44438
  },
@@ -44657,8 +44672,9 @@ const resolveEmptyUIState = (props, controlType) => {
44657
44672
  if (controlType === "input" && props.type === "checkbox") {
44658
44673
  // A checkbox is a member of a set, the way HTML has it: checked it sends
44659
44674
  // its value ("on" by default), unchecked it sends nothing at all. Only one
44660
- // holding `true` is a yes/no, and a yes/no nobody said yes to is `false`.
44661
- return props.value === true ? false : undefined;
44675
+ // declared `boolean` is a yes/no, and a yes/no nobody said yes to is
44676
+ // `false`.
44677
+ return props.boolean ? false : undefined;
44662
44678
  }
44663
44679
  const stateShape = props["navi-state-shape"];
44664
44680
  if (stateShape === "array") {
@@ -44670,6 +44686,15 @@ const resolveEmptyUIState = (props, controlType) => {
44670
44686
  return undefined;
44671
44687
  };
44672
44688
 
44689
+ // What a control holding `uiState` is worth: its own empty when it holds
44690
+ // nothing, so the reader gets the shape it was promised.
44691
+ const uiStateOrEmpty = (uiState, emptyUIState) => {
44692
+ if (uiState === undefined && emptyUIState !== undefined) {
44693
+ return emptyUIState;
44694
+ }
44695
+ return uiState;
44696
+ };
44697
+
44673
44698
  // What a cleared control shows: its own empty, kept in the shape it was holding.
44674
44699
  const resolveClearedUIState = (controller) => {
44675
44700
  const { controlType, props } = controller;
@@ -44904,7 +44929,7 @@ const useControlProps = (props, {
44904
44929
  // of a form would re-render at each change of any of its fields.
44905
44930
  // eslint-disable-next-line no-unused-expressions
44906
44931
  uiStateController.ownUIStateSignal.value;
44907
- const boundAction = useAction(props.action, uiStateController.uiStateSignal);
44932
+ const boundAction = useAction(props.action, uiStateController.valueSignal);
44908
44933
  const [controlRootProps, controlHostProps] = useInteractiveProps(props, {
44909
44934
  uiStateController,
44910
44935
  boundAction,
@@ -55440,7 +55465,8 @@ a.navi_button {
55440
55465
 
55441
55466
  &[data-icon] {
55442
55467
  --button-padding: 0;
55443
- display: inline-flex;
55468
+ justify-content: center;
55469
+ align-items: center;
55444
55470
  }
55445
55471
 
55446
55472
  &[data-cta] {
@@ -55538,7 +55564,12 @@ const ButtonUI = props => {
55538
55564
  });
55539
55565
  return jsxs(Box, {
55540
55566
  inline: true,
55541
- block: true,
55567
+ block: true
55568
+ // An icon is laid out as a box rather than on a line of text that is not
55569
+ // there, which is also what makes alignX/alignY place it. Before the
55570
+ // caller's props, so their own flex direction wins.
55571
+ ,
55572
+ flex: icon ? "x" : undefined,
55542
55573
  ...buttonControlRootProps,
55543
55574
  ...buttonControlHostProps,
55544
55575
  // eslint-disable-next-line react/no-children-prop
@@ -56981,7 +57012,12 @@ const css$M = /* css */`@layer navi {
56981
57012
  const InputCheckbox = props => {
56982
57013
  const defaultRef = useRef();
56983
57014
  props.ref = props.ref || defaultRef;
56984
- props.value = props.value === undefined ? "on" : props.value;
57015
+ useRef(false);
57016
+ if (props.boolean) {
57017
+ props.value = true;
57018
+ } else if (props.value === undefined) {
57019
+ props.value = "on";
57020
+ }
56985
57021
  if (props.headless) {
56986
57022
  return jsx(InputCheckboxHeadless, {
56987
57023
  ...props,
@@ -79762,7 +79798,6 @@ const css$w = /* css */`@layer navi;
79762
79798
  background: var(--x-background);
79763
79799
  background-color: var(--x-background-color);
79764
79800
  border-radius: 1em;
79765
- align-items: stretch;
79766
79801
  display: inline;
79767
79802
  position: relative;
79768
79803
 
@@ -79772,13 +79807,25 @@ const css$w = /* css */`@layer navi;
79772
79807
 
79773
79808
  &[data-text-overflow] {
79774
79809
  display: inline;
79810
+ }
79775
79811
 
79776
- & .navi_text_overflow_wrapper {
79777
- gap: 0;
79778
- }
79812
+ &[data-has-button] {
79813
+ align-items: center;
79814
+ display: inline-flex;
79815
+ }
79816
+
79817
+ & .navi_badge_label {
79818
+ text-overflow: ellipsis;
79819
+ text-box: trim-both cap alphabetic;
79820
+ overflow-clip-margin: padding-box calc((1lh - 1em) / 2);
79821
+ align-self: baseline;
79822
+ min-width: 0;
79823
+ overflow-x: clip;
79779
79824
  }
79780
79825
 
79781
79826
  & [role="button"] {
79827
+ box-sizing: content-box;
79828
+ height: 1cap;
79782
79829
  margin-top: calc(-1 * var(--x-badge-padding-top));
79783
79830
  margin-bottom: calc(-1 * var(--x-badge-padding-bottom));
79784
79831
  padding-top: var(--x-badge-padding-top);
@@ -79788,6 +79835,7 @@ const css$w = /* css */`@layer navi;
79788
79835
  cursor: pointer;
79789
79836
  pointer-events: auto;
79790
79837
  user-select: none;
79838
+ flex-shrink: 0;
79791
79839
  align-items: center;
79792
79840
  display: inline-flex;
79793
79841
 
@@ -79836,6 +79884,16 @@ const BadgeUI = ({
79836
79884
  ref
79837
79885
  } = props;
79838
79886
  useAccentColorAttributes(ref, null);
79887
+ const childArray = toChildArray(children);
79888
+ if (childArray.some(isBadgeButton)) {
79889
+ props = {
79890
+ "inline": true,
79891
+ "flex": true,
79892
+ ...props,
79893
+ "data-has-button": ""
79894
+ };
79895
+ children = wrapLabels(childArray);
79896
+ }
79839
79897
  return jsx(Text, {
79840
79898
  className: withPropsClassName("navi_badge", className),
79841
79899
  bold: true,
@@ -79896,6 +79954,35 @@ const BadgeButtonUI = props => {
79896
79954
  });
79897
79955
  };
79898
79956
  Badge.Button = BadgeButton;
79957
+ const isBadgeButton = child => {
79958
+ return isValidElement(child) && child.type === BadgeButton;
79959
+ };
79960
+ // Each run of children between buttons becomes one label: a single flex item
79961
+ // holding its text, so the trim reaches a bare string and the text keeps its
79962
+ // own baseline alignment inside.
79963
+ const wrapLabels = childArray => {
79964
+ const items = [];
79965
+ let run = [];
79966
+ const closeRun = () => {
79967
+ if (run.length > 0) {
79968
+ items.push(jsx("span", {
79969
+ className: "navi_badge_label",
79970
+ children: run
79971
+ }));
79972
+ run = [];
79973
+ }
79974
+ };
79975
+ for (const child of childArray) {
79976
+ if (isBadgeButton(child)) {
79977
+ closeRun();
79978
+ items.push(child);
79979
+ } else {
79980
+ run.push(child);
79981
+ }
79982
+ }
79983
+ closeRun();
79984
+ return items;
79985
+ };
79899
79986
 
79900
79987
  installImportMetaCssBuild(import.meta);
79901
79988
  const css$v = /* css */`@layer navi;
@@ -80635,8 +80722,6 @@ const PickerChip = ({
80635
80722
  ...rest
80636
80723
  }) => {
80637
80724
  return jsxs(Badge, {
80638
- inline: true,
80639
- flex: true,
80640
80725
  ...rest,
80641
80726
  children: [children, jsx(Badge.Button, {
80642
80727
  selfInteractions: "click",
@@ -84782,14 +84867,7 @@ const SplitButton = props => {
84782
84867
  children: [jsx(Button, {
84783
84868
  icon: true,
84784
84869
  paddingX: "s",
84785
- expandY: true
84786
- // flex + align: the icon is the whole content, so it is laid out as
84787
- // its own box in the middle of the button. Left to flow inline it
84788
- // would sit on the baseline of a line of text that is not there,
84789
- // with the descender space still kept below it.
84790
- ,
84791
- flex: true,
84792
- align: "center",
84870
+ expandY: true,
84793
84871
  "aria-label": menuLabel,
84794
84872
  command: "--navi-open",
84795
84873
  commandFor: menuId,
@@ -91900,8 +91978,6 @@ const ButtonCopyToClipboard = ({
91900
91978
  children: "Copié !"
91901
91979
  }), jsx(Button, {
91902
91980
  className: "navi_copy_button",
91903
- flex: "y",
91904
- alignY: "center",
91905
91981
  icon: true,
91906
91982
  revealOnInteraction: true,
91907
91983
  square: true,
@@ -92790,8 +92866,6 @@ const MessageBox = ({
92790
92866
  action: onClose,
92791
92867
  icon: true,
92792
92868
  border: "none",
92793
- alignX: "center",
92794
- alignY: "center",
92795
92869
  style: {
92796
92870
  ":hover": {
92797
92871
  backgroundColor: "rgba(0, 0, 0, 0.1)"