@jsenv/navi 0.16.60 → 0.17.1

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.
@@ -4858,6 +4858,15 @@ window.addEventListener("unhandledrejection", (event) => {
4858
4858
  }
4859
4859
  });
4860
4860
 
4861
+ const useCancelPrevious = () => {
4862
+ const cancellerRef = useRef();
4863
+ if (!cancellerRef.current) {
4864
+ cancellerRef.current = createRequestCanceller();
4865
+ }
4866
+ const canceller = cancellerRef.current;
4867
+ return canceller;
4868
+ };
4869
+
4861
4870
  /**
4862
4871
  * Merges a component's base className with className received from props.
4863
4872
  *
@@ -15571,7 +15580,114 @@ const MAX_CONSTRAINT = {
15571
15580
  },
15572
15581
  };
15573
15582
 
15574
- const listenInputChange = (input, callback) => {
15583
+ const listenInputValue = (
15584
+ input,
15585
+ callback,
15586
+ { waitForChange = false, debounce = 0 } = {},
15587
+ ) => {
15588
+ if (waitForChange) {
15589
+ return listenInputValueChange(input, callback);
15590
+ }
15591
+ const [teardown, addTeardown] = createPubSub();
15592
+ let currentValue = input.value;
15593
+ let timeout;
15594
+ let debounceTimeout;
15595
+
15596
+ const onAsyncEvent = (e) => {
15597
+ timeout = setTimeout(() => {
15598
+ onEvent(e);
15599
+ }, 0);
15600
+ };
15601
+
15602
+ let onEvent;
15603
+ if (debounce) {
15604
+ onEvent = (e, { skipDebounce } = {}) => {
15605
+ clearTimeout(timeout);
15606
+ clearTimeout(debounceTimeout);
15607
+
15608
+ const value = input.value;
15609
+ if (value === currentValue) {
15610
+ return;
15611
+ }
15612
+
15613
+ if (skipDebounce) {
15614
+ currentValue = value;
15615
+ callback(e);
15616
+ } else {
15617
+ debounceTimeout = setTimeout(() => {
15618
+ currentValue = value;
15619
+ callback(e);
15620
+ }, debounce);
15621
+ }
15622
+ };
15623
+ // no need to wait for change events (blur, enter key)
15624
+ // we consider this as strong interactions requesting an immediate response
15625
+ const stop = listenInputValueChange(input, (e) => {
15626
+ onEvent(e, { skipDebounce: true });
15627
+ });
15628
+ addTeardown(() => {
15629
+ stop();
15630
+ });
15631
+ } else {
15632
+ onEvent = (e) => {
15633
+ clearTimeout(timeout);
15634
+ const value = input.value;
15635
+ if (value === currentValue) {
15636
+ return;
15637
+ }
15638
+ currentValue = value;
15639
+ callback(e);
15640
+ };
15641
+ // Autocomplete, programmatic changes, form restoration
15642
+ input.addEventListener("change", onEvent);
15643
+ addTeardown(() => {
15644
+ input.removeEventListener("change", onEvent);
15645
+ });
15646
+ }
15647
+
15648
+ // Standard user input (typing)
15649
+ input.addEventListener("input", onEvent);
15650
+ addTeardown(() => {
15651
+ input.removeEventListener("input", onEvent);
15652
+ });
15653
+
15654
+ // Form reset - need to check the form
15655
+ const form = input.form;
15656
+ if (form) {
15657
+ // Form reset happens asynchronously, check value after reset completes
15658
+ form.addEventListener("reset", onAsyncEvent);
15659
+ addTeardown(() => {
15660
+ form.removeEventListener("reset", onAsyncEvent);
15661
+ });
15662
+ }
15663
+
15664
+ // Paste events (some browsers need special handling)
15665
+ input.addEventListener("paste", onEvent);
15666
+ addTeardown(() => {
15667
+ input.removeEventListener("paste", onEvent);
15668
+ });
15669
+
15670
+ // Focus events to catch programmatic changes that don't fire other events
15671
+ // (like when value is set before user interaction)
15672
+ input.addEventListener("focus", onEvent);
15673
+ addTeardown(() => {
15674
+ input.removeEventListener("focus", onEvent);
15675
+ });
15676
+
15677
+ // "navi_delete_content" behaves like an async event
15678
+ // a bit like form reset because
15679
+ // our action will be updated async after the component re-renders
15680
+ // and we need to wait that to happen to properly call action with the right value
15681
+ input.addEventListener("navi_delete_content", onAsyncEvent);
15682
+ addTeardown(() => {
15683
+ input.removeEventListener("navi_delete_content", onAsyncEvent);
15684
+ });
15685
+ return () => {
15686
+ teardown();
15687
+ };
15688
+ };
15689
+
15690
+ const listenInputValueChange = (input, callback) => {
15575
15691
  const [teardown, addTeardown] = createPubSub();
15576
15692
 
15577
15693
  let valueAtInteraction;
@@ -16331,22 +16447,38 @@ const installCustomConstraintValidation = (
16331
16447
  });
16332
16448
  }
16333
16449
 
16334
- request_on_input_change: {
16450
+ request_on_input_value_change: {
16335
16451
  const isInput =
16336
16452
  element.tagName === "INPUT" || element.tagName === "TEXTAREA";
16337
16453
  if (!isInput) {
16338
- break request_on_input_change;
16454
+ break request_on_input_value_change;
16339
16455
  }
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
- });
16456
+ const elementWithAction = closestElementWithAction(element);
16457
+ if (!elementWithAction) {
16458
+ break request_on_input_value_change;
16459
+ }
16460
+ const closestElementWithActionAttr = element.closest("[data-action]");
16461
+ const stop = listenInputValue(
16462
+ element,
16463
+ (e) => {
16464
+ dispatchActionRequestedCustomEvent(elementWithAction, {
16465
+ event: e,
16466
+ requester: element,
16467
+ });
16468
+ },
16469
+ {
16470
+ waitForChange: closestElementWithActionAttr.hasAttribute(
16471
+ "data-action-after-change",
16472
+ ),
16473
+ debounce: closestElementWithActionAttr.hasAttribute(
16474
+ "data-action-debounce",
16475
+ )
16476
+ ? parseFloat(
16477
+ closestElementWithActionAttr.getAttribute("data-action-debounce"),
16478
+ )
16479
+ : undefined,
16480
+ },
16481
+ );
16350
16482
  addTeardown(() => {
16351
16483
  stop();
16352
16484
  });
@@ -19246,6 +19378,7 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
19246
19378
  --button-padding-left,
19247
19379
  var(--button-padding-x, var(--button-padding))
19248
19380
  );
19381
+ padding: var(--button-padding, unset);
19249
19382
  align-items: inherit;
19250
19383
  justify-content: inherit;
19251
19384
  color: var(--x-button-color);
@@ -22593,25 +22726,45 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
22593
22726
  }
22594
22727
  }
22595
22728
 
22596
- .navi_start_icon_label {
22729
+ .navi_input_start_icon {
22597
22730
  position: absolute;
22598
22731
  top: 0;
22599
22732
  bottom: 0;
22600
22733
  left: 0.25em;
22601
22734
  }
22602
- .navi_end_icon_label {
22735
+ .navi_input_end_button {
22603
22736
  position: absolute;
22604
22737
  top: 0;
22605
22738
  right: 0.25em;
22606
22739
  bottom: 0;
22740
+ display: inline-flex;
22741
+ margin: 0;
22742
+ padding: 0;
22743
+ justify-content: center;
22744
+ background: none;
22745
+ border: none;
22607
22746
  opacity: 0;
22608
22747
  pointer-events: none;
22609
22748
  }
22610
22749
  &[data-has-value] {
22611
- .navi_end_icon_label {
22750
+ .navi_input_end_button {
22612
22751
  opacity: 1;
22752
+ cursor: pointer;
22613
22753
  pointer-events: auto;
22614
22754
  }
22755
+
22756
+ &[data-readonly] {
22757
+ .navi_input_end_button {
22758
+ opacity: 0;
22759
+ pointer-events: none;
22760
+ }
22761
+ }
22762
+ &[data-disabled] {
22763
+ .navi_input_end_button {
22764
+ opacity: 0;
22765
+ pointer-events: none;
22766
+ }
22767
+ }
22615
22768
  }
22616
22769
 
22617
22770
  &[data-start-icon] {
@@ -22709,38 +22862,7 @@ Object.assign(PSEUDO_CLASSES, {
22709
22862
  ":navi-has-value": {
22710
22863
  attribute: "data-has-value",
22711
22864
  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
- };
22865
+ return listenInputValue(el, callback);
22744
22866
  },
22745
22867
  test: el => {
22746
22868
  if (el.value === "") {
@@ -22859,28 +22981,39 @@ const InputTextualBasic = props => {
22859
22981
  loading: innerLoading,
22860
22982
  color: "var(--loader-color)",
22861
22983
  inset: -1
22862
- }), innerIcon && jsx(Icon, {
22863
- as: "label",
22984
+ }), innerIcon && jsx(Label, {
22864
22985
  htmlFor: innerId,
22865
- className: "navi_start_icon_label",
22986
+ disabled: innerDisabled,
22987
+ readOnly: innerReadOnly,
22988
+ className: "navi_input_start_icon",
22989
+ box: true,
22866
22990
  alignY: "center",
22867
- color: "rgba(28, 43, 52, 0.5)",
22868
- children: innerIcon
22869
- }), renderInputMemoized, cancelButton && jsx(Icon, {
22870
- as: "label",
22991
+ children: jsx(Icon, {
22992
+ color: "rgba(28, 43, 52, 0.5)",
22993
+ children: innerIcon
22994
+ })
22995
+ }), renderInputMemoized, cancelButton && jsx("label", {
22871
22996
  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
22997
+ "data-readonly": innerReadOnly ? "" : undefined,
22998
+ "data-disabled": innerDisabled ? "" : undefined,
22999
+ className: "navi_input_end_button",
23000
+ onMouseDown: e => {
23001
+ e.preventDefault(); // keep focus in the input
22877
23002
  },
22878
23003
  onClick: () => {
23004
+ if (innerReadOnly || innerDisabled) {
23005
+ return;
23006
+ }
22879
23007
  uiStateController.setUIState("", {
22880
23008
  trigger: "cancel_button"
22881
23009
  });
23010
+ ref.current.value = "";
23011
+ ref.current.dispatchEvent(new Event("navi_delete_content"));
22882
23012
  },
22883
- children: jsx(CloseSvg, {})
23013
+ children: jsx(Icon, {
23014
+ color: "rgba(28, 43, 52, 0.5)",
23015
+ children: jsx(CloseSvg, {})
23016
+ })
22884
23017
  })]
22885
23018
  });
22886
23019
  };
@@ -22888,6 +23021,8 @@ const InputTextualWithAction = props => {
22888
23021
  const uiState = useContext(UIStateContext);
22889
23022
  const {
22890
23023
  action,
23024
+ actionDebounce,
23025
+ actionAfterChange,
22891
23026
  loading,
22892
23027
  onCancel,
22893
23028
  onActionPrevented,
@@ -22942,6 +23077,8 @@ const InputTextualWithAction = props => {
22942
23077
  });
22943
23078
  return jsx(InputTextualBasic, {
22944
23079
  "data-action": boundAction.name,
23080
+ "data-action-debounce": actionDebounce,
23081
+ "data-action-after-change": actionAfterChange ? "" : undefined,
22945
23082
  ...rest,
22946
23083
  ref: ref,
22947
23084
  loading: loading || actionLoading
@@ -28780,5 +28917,5 @@ const UserSvg = () => jsx("svg", {
28780
28917
  })
28781
28918
  });
28782
28919
 
28783
- export { ActionRenderer, ActiveKeyboardShortcuts, Address, BadgeCount, Box, Button, ButtonCopyToClipboard, Caption, CheckSvg, Checkbox, CheckboxList, Code, Col, Colgroup, ConstructionSvg, Details, DialogLayout, Editable, ErrorBoundaryContext, ExclamationSvg, EyeClosedSvg, EyeSvg, Form, Group, HeartSvg, HomeSvg, Icon, Image, Input, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, MessageBox, Paragraph, Radio, RadioList, Route, RouteLink, Routes, RowNumberCol, RowNumberTableCell, SINGLE_SPACE_CONSTRAINT, SVGMaskOverlay, SearchSvg, Select, SelectionContext, Separator, SettingsSvg, StarSvg, SummaryMarker, Svg, Tab, TabList, Table, TableCell, Tbody, Text, Thead, Title, Tr, UITransition, UserSvg, ViewportLayout, actionIntegratedVia, addCustomMessage, clearAllRoutes, compareTwoJsValues, createAction, createAvailableConstraint, createRequestCanceller, createSelectionKeyboardShortcuts, enableDebugActions, enableDebugOnDocumentLoading, forwardActionRequested, installCustomConstraintValidation, isCellSelected, isColumnSelected, isRowSelected, localStorageSignal, navBack, navForward, navTo, openCallout, rawUrlPart, reload, removeCustomMessage, requestAction, rerunActions, resource, setBaseUrl, setupRoutes, stateSignal, stopLoad, stringifyTableSelectionValue, updateActions, useActionData, useActionStatus, useArraySignalMembership, useCalloutClose, useCellsAndColumns, useConstraintValidityState, useDependenciesDiff, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useKeyboardShortcuts, useMatchingRouteInfo, useNavState$1 as useNavState, useRouteStatus, useRunOnMount, useSelectableElement, useSelectionController, useSignalSync, useStateArray, useTitleLevel, useUrlSearchParam, valueInLocalStorage };
28920
+ export { ActionRenderer, ActiveKeyboardShortcuts, Address, BadgeCount, Box, Button, ButtonCopyToClipboard, Caption, CheckSvg, Checkbox, CheckboxList, Code, Col, Colgroup, ConstructionSvg, Details, DialogLayout, Editable, ErrorBoundaryContext, ExclamationSvg, EyeClosedSvg, EyeSvg, Form, Group, HeartSvg, HomeSvg, Icon, Image, Input, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, MessageBox, Paragraph, Radio, RadioList, Route, RouteLink, Routes, RowNumberCol, RowNumberTableCell, SINGLE_SPACE_CONSTRAINT, SVGMaskOverlay, SearchSvg, Select, SelectionContext, Separator, SettingsSvg, StarSvg, SummaryMarker, Svg, Tab, TabList, Table, TableCell, Tbody, Text, Thead, Title, Tr, UITransition, UserSvg, ViewportLayout, actionIntegratedVia, addCustomMessage, clearAllRoutes, compareTwoJsValues, createAction, createAvailableConstraint, createRequestCanceller, createSelectionKeyboardShortcuts, enableDebugActions, enableDebugOnDocumentLoading, forwardActionRequested, installCustomConstraintValidation, isCellSelected, isColumnSelected, isRowSelected, localStorageSignal, navBack, navForward, navTo, openCallout, rawUrlPart, reload, removeCustomMessage, requestAction, rerunActions, resource, setBaseUrl, setupRoutes, stateSignal, stopLoad, stringifyTableSelectionValue, updateActions, useActionData, useActionStatus, useArraySignalMembership, useCalloutClose, useCancelPrevious, useCellsAndColumns, useConstraintValidityState, useDependenciesDiff, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useKeyboardShortcuts, useMatchingRouteInfo, useNavState$1 as useNavState, useRouteStatus, useRunOnMount, useSelectableElement, useSelectionController, useSignalSync, useStateArray, useTitleLevel, useUrlSearchParam, valueInLocalStorage };
28784
28921
  //# sourceMappingURL=jsenv_navi.js.map