@jsenv/navi 0.16.60 → 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