@jsenv/navi 0.16.59 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12083,10 +12083,11 @@ const renderActionableComponent = (props, {
12083
12083
  }) => {
12084
12084
  const {
12085
12085
  action,
12086
+ liveAction,
12086
12087
  shortcuts
12087
12088
  } = props;
12088
12089
  const formContext = useContext(FormContext);
12089
- const hasActionProps = Boolean(action || shortcuts && shortcuts.length > 0);
12090
+ const hasActionProps = Boolean(action || liveAction || shortcuts && shortcuts.length > 0);
12090
12091
  const considerInsideForm = Boolean(formContext);
12091
12092
  if (hasActionProps && WithAction) {
12092
12093
  if (considerInsideForm && WithActionInsideForm) {
@@ -15571,7 +15572,82 @@ const MAX_CONSTRAINT = {
15571
15572
  },
15572
15573
  };
15573
15574
 
15574
- const listenInputChange = (input, callback) => {
15575
+ const listenInputValue = (
15576
+ input,
15577
+ callback,
15578
+ { waitForChange = false } = {},
15579
+ ) => {
15580
+ if (waitForChange) {
15581
+ return listenInputValueChange(input, callback);
15582
+ }
15583
+ const [teardown, addTeardown] = createPubSub();
15584
+ let currentValue = input.value;
15585
+
15586
+ let timeout;
15587
+ const onAsyncEvent = (e) => {
15588
+ timeout = setTimeout(() => {
15589
+ onEvent(e);
15590
+ }, 0);
15591
+ };
15592
+ const onEvent = (e) => {
15593
+ clearTimeout(timeout);
15594
+ const value = input.value;
15595
+ if (value === currentValue) {
15596
+ return;
15597
+ }
15598
+ currentValue = value;
15599
+ callback(e);
15600
+ };
15601
+
15602
+ // Standard user input (typing)
15603
+ input.addEventListener("input", onEvent);
15604
+ addTeardown(() => {
15605
+ input.removeEventListener("input", onEvent);
15606
+ });
15607
+
15608
+ // Autocomplete, programmatic changes, form restoration
15609
+ input.addEventListener("change", onEvent);
15610
+ addTeardown(() => {
15611
+ input.removeEventListener("change", onEvent);
15612
+ });
15613
+
15614
+ // Form reset - need to check the form
15615
+ const form = input.form;
15616
+ if (form) {
15617
+ // Form reset happens asynchronously, check value after reset completes
15618
+ form.addEventListener("reset", onAsyncEvent);
15619
+ addTeardown(() => {
15620
+ form.removeEventListener("reset", onAsyncEvent);
15621
+ });
15622
+ }
15623
+
15624
+ // Paste events (some browsers need special handling)
15625
+ input.addEventListener("paste", onEvent);
15626
+ addTeardown(() => {
15627
+ input.removeEventListener("paste", onEvent);
15628
+ });
15629
+
15630
+ // Focus events to catch programmatic changes that don't fire other events
15631
+ // (like when value is set before user interaction)
15632
+ input.addEventListener("focus", onEvent);
15633
+ addTeardown(() => {
15634
+ input.removeEventListener("focus", onEvent);
15635
+ });
15636
+
15637
+ // "navi_delete_content" behaves like an async event
15638
+ // a bit like form reset because
15639
+ // our action will be updated async after the component re-renders
15640
+ // and we need to wait that to happen to properly call action with the right value
15641
+ input.addEventListener("navi_delete_content", onAsyncEvent);
15642
+ addTeardown(() => {
15643
+ input.removeEventListener("navi_delete_content", onAsyncEvent);
15644
+ });
15645
+ return () => {
15646
+ teardown();
15647
+ };
15648
+ };
15649
+
15650
+ const listenInputValueChange = (input, callback) => {
15575
15651
  const [teardown, addTeardown] = createPubSub();
15576
15652
 
15577
15653
  let valueAtInteraction;
@@ -16331,22 +16407,28 @@ const installCustomConstraintValidation = (
16331
16407
  });
16332
16408
  }
16333
16409
 
16334
- request_on_input_change: {
16410
+ request_on_input_value_change: {
16335
16411
  const isInput =
16336
16412
  element.tagName === "INPUT" || element.tagName === "TEXTAREA";
16337
16413
  if (!isInput) {
16338
- break request_on_input_change;
16414
+ break request_on_input_value_change;
16339
16415
  }
16340
- const stop = listenInputChange(element, (e) => {
16341
- const elementWithAction = closestElementWithAction(element);
16342
- if (!elementWithAction) {
16343
- return;
16344
- }
16345
- dispatchActionRequestedCustomEvent(elementWithAction, {
16346
- event: e,
16347
- requester: element,
16348
- });
16349
- });
16416
+ const elementWithAction = closestElementWithAction(element);
16417
+ if (!elementWithAction) {
16418
+ break request_on_input_value_change;
16419
+ }
16420
+ const stop = listenInputValue(
16421
+ element,
16422
+ (e) => {
16423
+ dispatchActionRequestedCustomEvent(elementWithAction, {
16424
+ event: e,
16425
+ requester: element,
16426
+ });
16427
+ },
16428
+ {
16429
+ waitForChange: !element.closest("[data-live-action]"),
16430
+ },
16431
+ );
16350
16432
  addTeardown(() => {
16351
16433
  stop();
16352
16434
  });
@@ -18718,8 +18800,8 @@ const useUIStateController = (
18718
18800
  ) => {
18719
18801
  const parentUIStateController = useContext(ParentUIStateControllerContext);
18720
18802
  const formContext = useContext(FormContext);
18721
- const { id, name, onUIStateChange, action } = props;
18722
- const uncontrolled = !formContext && !action;
18803
+ const { id, name, onUIStateChange, action, liveAction } = props;
18804
+ const uncontrolled = !formContext && !action && !liveAction;
18723
18805
  const [navState, setNavState] = useNavState$1(id);
18724
18806
 
18725
18807
  const uiStateControllerRef = useRef();
@@ -19246,6 +19328,7 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
19246
19328
  --button-padding-left,
19247
19329
  var(--button-padding-x, var(--button-padding))
19248
19330
  );
19331
+ padding: var(--button-padding, unset);
19249
19332
  align-items: inherit;
19250
19333
  justify-content: inherit;
19251
19334
  color: var(--x-button-color);
@@ -22593,25 +22676,45 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
22593
22676
  }
22594
22677
  }
22595
22678
 
22596
- .navi_start_icon_label {
22679
+ .navi_input_start_icon {
22597
22680
  position: absolute;
22598
22681
  top: 0;
22599
22682
  bottom: 0;
22600
22683
  left: 0.25em;
22601
22684
  }
22602
- .navi_end_icon_label {
22685
+ .navi_input_end_button {
22603
22686
  position: absolute;
22604
22687
  top: 0;
22605
22688
  right: 0.25em;
22606
22689
  bottom: 0;
22690
+ display: inline-flex;
22691
+ margin: 0;
22692
+ padding: 0;
22693
+ justify-content: center;
22694
+ background: none;
22695
+ border: none;
22607
22696
  opacity: 0;
22608
22697
  pointer-events: none;
22609
22698
  }
22610
22699
  &[data-has-value] {
22611
- .navi_end_icon_label {
22700
+ .navi_input_end_button {
22612
22701
  opacity: 1;
22702
+ cursor: pointer;
22613
22703
  pointer-events: auto;
22614
22704
  }
22705
+
22706
+ &[data-readonly] {
22707
+ .navi_input_end_button {
22708
+ opacity: 0;
22709
+ pointer-events: none;
22710
+ }
22711
+ }
22712
+ &[data-disabled] {
22713
+ .navi_input_end_button {
22714
+ opacity: 0;
22715
+ pointer-events: none;
22716
+ }
22717
+ }
22615
22718
  }
22616
22719
 
22617
22720
  &[data-start-icon] {
@@ -22709,38 +22812,7 @@ Object.assign(PSEUDO_CLASSES, {
22709
22812
  ":navi-has-value": {
22710
22813
  attribute: "data-has-value",
22711
22814
  setup: (el, callback) => {
22712
- const onValueChange = () => {
22713
- callback();
22714
- };
22715
-
22716
- // Standard user input (typing)
22717
- el.addEventListener("input", onValueChange);
22718
- // Autocomplete, programmatic changes, form restoration
22719
- el.addEventListener("change", onValueChange);
22720
- // Form reset - need to check the form
22721
- const form = el.form;
22722
- const onFormReset = () => {
22723
- // Form reset happens asynchronously, check value after reset completes
22724
- setTimeout(onValueChange, 0);
22725
- };
22726
- if (form) {
22727
- form.addEventListener("reset", onFormReset);
22728
- }
22729
-
22730
- // Paste events (some browsers need special handling)
22731
- el.addEventListener("paste", onValueChange);
22732
- // Focus events to catch programmatic changes that don't fire other events
22733
- // (like when value is set before user interaction)
22734
- el.addEventListener("focus", onValueChange);
22735
- return () => {
22736
- el.removeEventListener("input", onValueChange);
22737
- el.removeEventListener("change", onValueChange);
22738
- el.removeEventListener("paste", onValueChange);
22739
- el.removeEventListener("focus", onValueChange);
22740
- if (form) {
22741
- form.removeEventListener("reset", onFormReset);
22742
- }
22743
- };
22815
+ return listenInputValue(el, callback);
22744
22816
  },
22745
22817
  test: el => {
22746
22818
  if (el.value === "") {
@@ -22859,28 +22931,39 @@ const InputTextualBasic = props => {
22859
22931
  loading: innerLoading,
22860
22932
  color: "var(--loader-color)",
22861
22933
  inset: -1
22862
- }), innerIcon && jsx(Icon, {
22863
- as: "label",
22934
+ }), innerIcon && jsx(Label, {
22864
22935
  htmlFor: innerId,
22865
- className: "navi_start_icon_label",
22936
+ disabled: innerDisabled,
22937
+ readOnly: innerReadOnly,
22938
+ className: "navi_input_start_icon",
22939
+ box: true,
22866
22940
  alignY: "center",
22867
- color: "rgba(28, 43, 52, 0.5)",
22868
- children: innerIcon
22869
- }), renderInputMemoized, cancelButton && jsx(Icon, {
22870
- as: "label",
22941
+ children: jsx(Icon, {
22942
+ color: "rgba(28, 43, 52, 0.5)",
22943
+ children: innerIcon
22944
+ })
22945
+ }), renderInputMemoized, cancelButton && jsx("label", {
22871
22946
  htmlFor: innerId,
22872
- className: "navi_end_icon_label",
22873
- alignY: "center",
22874
- color: "rgba(28, 43, 52, 0.5)",
22875
- onMousedown: e => {
22876
- e.preventDefault(); // keep focus on the button
22947
+ "data-readonly": innerReadOnly ? "" : undefined,
22948
+ "data-disabled": innerDisabled ? "" : undefined,
22949
+ className: "navi_input_end_button",
22950
+ onMouseDown: e => {
22951
+ e.preventDefault(); // keep focus in the input
22877
22952
  },
22878
22953
  onClick: () => {
22954
+ if (innerReadOnly || innerDisabled) {
22955
+ return;
22956
+ }
22879
22957
  uiStateController.setUIState("", {
22880
22958
  trigger: "cancel_button"
22881
22959
  });
22960
+ ref.current.value = "";
22961
+ ref.current.dispatchEvent(new Event("navi_delete_content"));
22882
22962
  },
22883
- children: jsx(CloseSvg, {})
22963
+ children: jsx(Icon, {
22964
+ color: "rgba(28, 43, 52, 0.5)",
22965
+ children: jsx(CloseSvg, {})
22966
+ })
22884
22967
  })]
22885
22968
  });
22886
22969
  };
@@ -22888,6 +22971,7 @@ const InputTextualWithAction = props => {
22888
22971
  const uiState = useContext(UIStateContext);
22889
22972
  const {
22890
22973
  action,
22974
+ liveAction,
22891
22975
  loading,
22892
22976
  onCancel,
22893
22977
  onActionPrevented,
@@ -22901,7 +22985,7 @@ const InputTextualWithAction = props => {
22901
22985
  } = props;
22902
22986
  const defaultRef = useRef();
22903
22987
  const ref = props.ref || defaultRef;
22904
- const [boundAction] = useActionBoundToOneParam(action, uiState);
22988
+ const [boundAction] = useActionBoundToOneParam(liveAction || action, uiState);
22905
22989
  const {
22906
22990
  loading: actionLoading
22907
22991
  } = useActionStatus(boundAction);
@@ -22942,6 +23026,7 @@ const InputTextualWithAction = props => {
22942
23026
  });
22943
23027
  return jsx(InputTextualBasic, {
22944
23028
  "data-action": boundAction.name,
23029
+ "data-live-action": liveAction ? "" : undefined,
22945
23030
  ...rest,
22946
23031
  ref: ref,
22947
23032
  loading: loading || actionLoading
@@ -27495,63 +27580,73 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
27495
27580
  @layer navi {
27496
27581
  }
27497
27582
  .navi_badge_count {
27498
- --x-size: 1.5em;
27499
- --x-border-radius: var(--border-radius);
27500
- --x-number-font-size: var(--font-size);
27583
+ --x-background: var(--background);
27584
+ --x-background-color: var(--background-color);
27501
27585
  position: relative;
27502
27586
  display: inline-block;
27503
-
27504
27587
  color: var(--color, var(--x-color-contrasting));
27505
27588
  font-size: var(--font-size);
27506
27589
  vertical-align: middle;
27507
- border-radius: var(--x-border-radius);
27508
- }
27509
- .navi_count_badge_overflow {
27510
- position: relative;
27511
- top: -0.1em;
27512
- }
27513
- /* Ellipse */
27514
- .navi_badge_count[data-ellipse] {
27515
- padding-right: 0.4em;
27516
- padding-left: 0.4em;
27517
- background: var(--background);
27518
- background-color: var(--background-color, var(--background));
27519
- border-radius: 1em;
27520
- }
27521
- /* Circle */
27522
- .navi_badge_count[data-circle] {
27523
- width: var(--x-size);
27524
- height: var(--x-size);
27525
- }
27526
- .navi_badge_count_frame {
27527
- position: absolute;
27528
- top: 50%;
27529
- left: 0;
27530
- width: 100%;
27531
- height: 100%;
27532
- background: var(--background);
27533
- background-color: var(--background-color, var(--background));
27534
- border-radius: inherit;
27535
- transform: translateY(-50%);
27536
- }
27537
- .navi_badge_count_text {
27538
- position: absolute;
27539
- top: 50%;
27540
- left: 50%;
27541
- font-size: var(--x-number-font-size, inherit);
27542
- transform: translate(-50%, -50%);
27543
- }
27544
- .navi_badge_count[data-single-char] {
27545
- --x-border-radius: 100%;
27546
- --x-number-font-size: unset;
27547
- }
27548
- .navi_badge_count[data-two-chars] {
27549
- --x-border-radius: 100%;
27550
- --x-number-font-size: 0.8em;
27551
- }
27552
- .navi_badge_count[data-three-chars] {
27553
- --x-border-radius: 100%;
27554
- --x-number-font-size: 0.6em;
27590
+
27591
+ .navi_count_badge_overflow {
27592
+ position: relative;
27593
+ top: -0.1em;
27594
+ }
27595
+
27596
+ /* Ellipse */
27597
+ &[data-ellipse] {
27598
+ padding-right: 0.4em;
27599
+ padding-left: 0.4em;
27600
+ background: var(--x-background);
27601
+ background-color: var(--x-background-color, var(--x-background));
27602
+ border-radius: 1em;
27603
+ &[data-loading] {
27604
+ --x-background: transparent;
27605
+ }
27606
+ }
27607
+
27608
+ /* Circle */
27609
+ &[data-circle] {
27610
+ --x-size: 1.5em;
27611
+ --x-border-radius: var(--border-radius);
27612
+ --x-number-font-size: var(--font-size);
27613
+
27614
+ width: var(--x-size);
27615
+ height: var(--x-size);
27616
+ border-radius: var(--x-border-radius);
27617
+ &[data-single-char] {
27618
+ --x-border-radius: 100%;
27619
+ --x-number-font-size: unset;
27620
+ }
27621
+ &[data-two-chars] {
27622
+ --x-border-radius: 100%;
27623
+ --x-number-font-size: 0.8em;
27624
+ }
27625
+ &[data-three-chars] {
27626
+ --x-border-radius: 100%;
27627
+ --x-number-font-size: 0.6em;
27628
+ }
27629
+
27630
+ .navi_badge_count_frame {
27631
+ position: absolute;
27632
+ top: 50%;
27633
+ left: 0;
27634
+ width: 100%;
27635
+ height: 100%;
27636
+ background: var(--x-background);
27637
+ background-color: var(--x-background-color, var(--x-background));
27638
+ border-radius: inherit;
27639
+ transform: translateY(-50%);
27640
+ }
27641
+
27642
+ .navi_badge_count_text {
27643
+ position: absolute;
27644
+ top: 50%;
27645
+ left: 50%;
27646
+ font-size: var(--x-number-font-size, inherit);
27647
+ transform: translate(-50%, -50%);
27648
+ }
27649
+ }
27555
27650
  }
27556
27651
  `;
27557
27652
  const BadgeStyleCSSVars = {
@@ -27572,11 +27667,11 @@ const BadgeCountOverflow = () => jsx("span", {
27572
27667
  const MAX_CHAR_AS_CIRCLE = 3;
27573
27668
  const BadgeCount = ({
27574
27669
  children,
27575
- max = 99,
27576
27670
  maxElement = jsx(BadgeCountOverflow, {}),
27577
27671
  // When you use max="none" (or max > 99) it might be a good idea to force ellipse
27578
27672
  // so that visually the interface do not suddently switch from circle to ellipse depending on the count
27579
27673
  ellipse,
27674
+ max = ellipse ? Infinity : 99,
27580
27675
  ...props
27581
27676
  }) => {
27582
27677
  const defaultRef = useRef();
@@ -27660,26 +27755,32 @@ const BadgeCountCircle = ({
27660
27755
  };
27661
27756
  const BadgeCountEllipse = ({
27662
27757
  ref,
27758
+ loading,
27663
27759
  children,
27664
27760
  hasOverflow,
27665
27761
  ...props
27666
27762
  }) => {
27667
- return jsxs(Text, {
27763
+ return jsx(Text, {
27668
27764
  ref: ref,
27669
27765
  className: "navi_badge_count",
27670
27766
  bold: true,
27671
27767
  "data-ellipse": "",
27672
27768
  "data-value-overflow": hasOverflow ? "" : undefined,
27769
+ "data-loading": loading ? "" : undefined,
27673
27770
  ...props,
27674
27771
  styleCSSVars: BadgeStyleCSSVars,
27675
27772
  spacing: "pre",
27676
- children: [jsx("span", {
27677
- style: "user-select: none",
27678
- children: "\u200B"
27679
- }), children, jsx("span", {
27680
- style: "user-select: none",
27681
- children: "\u200B"
27682
- })]
27773
+ children: loading ? jsx(Icon, {
27774
+ children: jsx(LoadingDots, {})
27775
+ }) : jsxs(Fragment, {
27776
+ children: [jsx("span", {
27777
+ style: "user-select: none",
27778
+ children: "\u200B"
27779
+ }), children, jsx("span", {
27780
+ style: "user-select: none",
27781
+ children: "\u200B"
27782
+ })]
27783
+ })
27683
27784
  });
27684
27785
  };
27685
27786