@jsenv/navi 0.12.29 → 0.12.31

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.
@@ -1,8 +1,8 @@
1
1
  import { installImportMetaCss } from "./jsenv_navi_side_effects.js";
2
- import { createIterableWeakSet, createPubSub, createValueEffect, createStyleController, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, allowWheelThrough, resolveCSSColor, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, activeElementSignal, canInterceptKeys, createGroupTransitionController, getElementSignature, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, stringifyStyle, mergeOneStyle, mergeTwoStyles, normalizeStyles, resolveCSSSize, findBefore, findAfter, hasCSSSizeUnit, initFocusGroup, elementIsFocusable, pickLightOrDark, resolveColorLuminance, dragAfterThreshold, getScrollContainer, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement } from "@jsenv/dom";
2
+ import { createIterableWeakSet, createPubSub, createValueEffect, createStyleController, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, allowWheelThrough, resolveCSSColor, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, activeElementSignal, canInterceptKeys, createGroupTransitionController, getElementSignature, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, stringifyStyle, mergeOneStyle, mergeTwoStyles, normalizeStyles, resolveCSSSize, findBefore, findAfter, hasCSSSizeUnit, pickLightOrDark, resolveColorLuminance, initFocusGroup, elementIsFocusable, dragAfterThreshold, getScrollContainer, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement } from "@jsenv/dom";
3
3
  import { prefixFirstAndIndentRemainingLines } from "@jsenv/humanize";
4
4
  import { effect, signal, computed, batch, useSignal } from "@preact/signals";
5
- import { useEffect, useRef, useCallback, useContext, useState, useLayoutEffect, useMemo, useErrorBoundary, useImperativeHandle, useId } from "preact/hooks";
5
+ import { useEffect, useRef, useCallback, useContext, useState, useLayoutEffect, useMemo, useImperativeHandle, useErrorBoundary, useId } from "preact/hooks";
6
6
  import { createContext, toChildArray, createRef, cloneElement } from "preact";
7
7
  import { jsxs, jsx, Fragment } from "preact/jsx-runtime";
8
8
  import { createPortal, forwardRef } from "preact/compat";
@@ -7690,7 +7690,9 @@ const createRoute = (urlPatternInput) => {
7690
7690
  // always remove the wildcard part for URL building since it's optional
7691
7691
  if (relativeUrl.endsWith("/*?")) {
7692
7692
  // Always remove the optional wildcard part for URL building
7693
- relativeUrl = relativeUrl.replace(/\/\*\?$/, "");
7693
+ relativeUrl = relativeUrl.slice(0, -"/*?".length);
7694
+ } else if (relativeUrl.endsWith("{/}?*")) {
7695
+ relativeUrl = relativeUrl.slice(0, -"{/}?*".length);
7694
7696
  } else {
7695
7697
  // For required wildcards (/*) or other patterns, replace normally
7696
7698
  let wildcardIndex = 0;
@@ -9678,26 +9680,16 @@ const useUITransitionContentId = value => {
9678
9680
  }, []);
9679
9681
  };
9680
9682
 
9681
- const renderSignal = signal(null);
9682
- const forceRender = () => {
9683
- renderSignal.value = {}; // force re-render
9684
- };
9683
+ // import { signal } from "@preact/signals";
9684
+
9685
9685
 
9686
9686
  const useForceRender = () => {
9687
- // eslint-disable-next-line no-unused-expressions
9688
- renderSignal.value;
9689
- return forceRender;
9687
+ const [, setState] = useState(null);
9688
+ return () => {
9689
+ setState({});
9690
+ };
9690
9691
  };
9691
9692
 
9692
- // import { useState } from "preact/hooks";
9693
-
9694
- // export const useForceRender = () => {
9695
- // const [, setState] = useState(null);
9696
- // return () => {
9697
- // setState({});
9698
- // };
9699
- // };
9700
-
9701
9693
  /**
9702
9694
  *
9703
9695
  * . Refactor les actions pour qu'elles utilisent use. Ce qui va ouvrir la voie pour
@@ -9728,12 +9720,9 @@ const Routes = ({
9728
9720
  element = RootElement,
9729
9721
  children
9730
9722
  }) => {
9731
- return jsx(SlotContext.Provider, {
9732
- value: null,
9733
- children: jsx(Route, {
9734
- element: element,
9735
- children: children
9736
- })
9723
+ return jsx(Route, {
9724
+ element: element,
9725
+ children: children
9737
9726
  });
9738
9727
  };
9739
9728
  const useActiveRouteInfo = () => useContext(RouteInfoContext);
@@ -9823,6 +9812,7 @@ const initRouteObserver = ({
9823
9812
  onActiveInfoChange,
9824
9813
  registerChildRouteFromContext
9825
9814
  }) => {
9815
+ const [teardown, addTeardown] = createPubSub();
9826
9816
  const elementId = getElementSignature(element);
9827
9817
  const candidateElementIds = Array.from(candidateSet, c => getElementSignature(c.ActiveElement));
9828
9818
  if (candidateElementIds.length === 0) ; else {
@@ -9921,15 +9911,18 @@ const initRouteObserver = ({
9921
9911
  publishCompositeStatus();
9922
9912
  };
9923
9913
  if (route) {
9924
- route.subscribeStatus(onChange);
9914
+ addTeardown(route.subscribeStatus(onChange));
9925
9915
  }
9926
9916
  for (const candidate of candidateSet) {
9927
- candidate.route.subscribeStatus(onChange);
9917
+ addTeardown(candidate.route.subscribeStatus(onChange));
9928
9918
  }
9929
9919
  if (registerChildRouteFromContext) {
9930
9920
  registerChildRouteFromContext(ActiveElement, compositeRoute, fallback, meta);
9931
9921
  }
9932
9922
  updateActiveInfo();
9923
+ return () => {
9924
+ teardown();
9925
+ };
9933
9926
  };
9934
9927
  const RouteSlot = () => {
9935
9928
  const SlotElement = useContext(SlotContext);
@@ -13860,7 +13853,7 @@ const TextBasic = ({
13860
13853
  as: "span",
13861
13854
  baseClassName: "navi_text",
13862
13855
  ...rest,
13863
- children: rest.as === "pre" ? children : applySpacingOnTextChildren(children, spacing)
13856
+ children: rest.as === "pre" || rest.box || rest.column || rest.row ? children : applySpacingOnTextChildren(children, spacing)
13864
13857
  });
13865
13858
  };
13866
13859
 
@@ -15632,559 +15625,557 @@ const RouteLink = ({
15632
15625
  };
15633
15626
 
15634
15627
  installImportMetaCss(import.meta);import.meta.css = /* css */`
15635
- .action_error {
15636
- padding: 20px;
15637
- background: #fdd;
15638
- border: 1px solid red;
15639
- margin-top: 0;
15640
- margin-bottom: 20px;
15641
- }
15642
- `;
15643
- const renderIdleDefault = () => null;
15644
- const renderLoadingDefault = () => null;
15645
- const renderAbortedDefault = () => null;
15646
- const renderErrorDefault = error => {
15647
- let routeErrorText = error && error.message ? error.message : error;
15648
- return jsxs("p", {
15649
- className: "action_error",
15650
- children: ["An error occured: ", routeErrorText]
15651
- });
15652
- };
15653
- const renderCompletedDefault = () => null;
15654
- const ActionRenderer = ({
15655
- action,
15656
- children,
15657
- disabled
15658
- }) => {
15659
- const {
15660
- idle: renderIdle = renderIdleDefault,
15661
- loading: renderLoading = renderLoadingDefault,
15662
- aborted: renderAborted = renderAbortedDefault,
15663
- error: renderError = renderErrorDefault,
15664
- completed: renderCompleted,
15665
- always: renderAlways
15666
- } = typeof children === "function" ? {
15667
- completed: children
15668
- } : children || {};
15669
- if (disabled) {
15670
- return null;
15628
+ .navi_tablist {
15629
+ display: flex;
15630
+ justify-content: space-between;
15631
+ overflow-x: auto;
15632
+ overflow-y: hidden;
15671
15633
  }
15672
- if (action === undefined) {
15673
- throw new Error("ActionRenderer requires an action to render, but none was provided.");
15634
+
15635
+ .navi_tablist > ul {
15636
+ display: flex;
15637
+ margin: 0;
15638
+ padding: 0;
15639
+ align-items: center;
15640
+ gap: 0.5rem;
15641
+ list-style: none;
15674
15642
  }
15675
- const {
15676
- idle,
15677
- loading,
15678
- aborted,
15679
- error,
15680
- data
15681
- } = useActionStatus(action);
15682
- const UIRenderedPromise = useUIRenderedPromise(action);
15683
- const [errorBoundary, resetErrorBoundary] = useErrorBoundary();
15684
15643
 
15685
- // Mark this action as bound to UI components (has renderers)
15686
- // This tells the action system that errors should be caught and stored
15687
- // in the action's error state rather than bubbling up
15688
- useLayoutEffect(() => {
15689
- if (action) {
15690
- const {
15691
- ui
15692
- } = getActionPrivateProperties(action);
15693
- ui.hasRenderers = true;
15694
- }
15695
- }, [action]);
15696
- useLayoutEffect(() => {
15697
- resetErrorBoundary();
15698
- }, [action, loading, idle, resetErrorBoundary]);
15699
- useLayoutEffect(() => {
15700
- UIRenderedPromise.resolve();
15701
- return () => {
15702
- actionUIRenderedPromiseWeakMap.delete(action);
15703
- };
15704
- }, [action]);
15644
+ .navi_tablist > ul > li {
15645
+ position: relative;
15646
+ display: inline-flex;
15647
+ }
15705
15648
 
15706
- // If renderAlways is provided, it wins and handles all rendering
15707
- if (renderAlways) {
15708
- return renderAlways({
15709
- idle,
15710
- loading,
15711
- aborted,
15712
- error,
15713
- data
15714
- });
15649
+ .navi_tab {
15650
+ display: flex;
15651
+ flex-direction: column;
15652
+ white-space: nowrap;
15715
15653
  }
15716
- if (idle) {
15717
- return renderIdle(action);
15654
+
15655
+ .navi_tab_content {
15656
+ display: flex;
15657
+ padding: 0 0.5rem;
15658
+ text-decoration: none;
15659
+ line-height: 30px;
15660
+ border-radius: 6px;
15661
+ transition: background 0.12s ease-out;
15718
15662
  }
15719
- if (errorBoundary) {
15720
- return renderError(errorBoundary, "ui_error", action);
15663
+
15664
+ .navi_tab:hover .navi_tab_content {
15665
+ color: #010409;
15666
+ background: #dae0e7;
15721
15667
  }
15722
- if (error) {
15723
- return renderError(error, "action_error", action);
15668
+
15669
+ .navi_tab .active_marker {
15670
+ z-index: 1;
15671
+ display: flex;
15672
+ width: 100%;
15673
+ height: 2px;
15674
+ margin-top: 5px;
15675
+ background: transparent;
15676
+ border-radius: 0.1px;
15724
15677
  }
15725
- if (aborted) {
15726
- return renderAborted(action);
15678
+
15679
+ /* Hidden bold clone to reserve space for bold width without affecting height */
15680
+ .navi_tab_content_bold_clone {
15681
+ display: block; /* in-flow so it contributes to width */
15682
+ height: 0; /* zero height so it doesn't change layout height */
15683
+ font-weight: 600; /* force bold to compute max width */
15684
+ visibility: hidden; /* not visible */
15685
+ pointer-events: none; /* inert */
15686
+ overflow: hidden; /* avoid any accidental height */
15727
15687
  }
15728
- let renderCompletedSafe;
15729
- if (renderCompleted) {
15730
- renderCompletedSafe = renderCompleted;
15731
- } else {
15732
- const {
15733
- ui
15734
- } = getActionPrivateProperties(action);
15735
- if (ui.renderCompleted) {
15736
- renderCompletedSafe = ui.renderCompleted;
15737
- } else {
15738
- renderCompletedSafe = renderCompletedDefault;
15739
- }
15688
+
15689
+ .navi_tab[aria-selected="true"] .active_marker {
15690
+ background: rgb(205, 52, 37);
15740
15691
  }
15741
- if (loading) {
15742
- if (action.canDisplayOldData && data !== undefined) {
15743
- return renderCompletedSafe(data, action);
15744
- }
15745
- return renderLoading(action);
15692
+
15693
+ .navi_tab[aria-selected="true"] .navi_tab_content {
15694
+ font-weight: 600;
15746
15695
  }
15747
- return renderCompletedSafe(data, action);
15696
+ `;
15697
+ const TabList = ({
15698
+ children,
15699
+ ...props
15700
+ }) => {
15701
+ return jsx(Box, {
15702
+ as: "nav",
15703
+ baseClassName: "navi_tablist",
15704
+ role: "tablist",
15705
+ ...props,
15706
+ children: jsx("ul", {
15707
+ role: "list",
15708
+ children: children.map(child => {
15709
+ return jsx("li", {
15710
+ children: child
15711
+ }, child.props.key);
15712
+ })
15713
+ })
15714
+ });
15748
15715
  };
15749
- const defaultPromise = Promise.resolve();
15750
- defaultPromise.resolve = () => {};
15751
- const actionUIRenderedPromiseWeakMap = new WeakMap();
15752
- const useUIRenderedPromise = action => {
15753
- if (!action) {
15754
- return defaultPromise;
15755
- }
15756
- const actionUIRenderedPromise = actionUIRenderedPromiseWeakMap.get(action);
15757
- if (actionUIRenderedPromise) {
15758
- return actionUIRenderedPromise;
15759
- }
15760
- let resolve;
15761
- const promise = new Promise(res => {
15762
- resolve = res;
15716
+ const Tab = ({
15717
+ children,
15718
+ selected,
15719
+ ...props
15720
+ }) => {
15721
+ return jsxs(Box, {
15722
+ baseClassName: "navi_tab",
15723
+ role: "tab",
15724
+ "aria-selected": selected ? "true" : "false",
15725
+ ...props,
15726
+ children: [jsx("div", {
15727
+ className: "navi_tab_content",
15728
+ children: children
15729
+ }), jsx("div", {
15730
+ className: "navi_tab_content_bold_clone",
15731
+ "aria-hidden": "true",
15732
+ children: children
15733
+ }), jsx("span", {
15734
+ className: "active_marker"
15735
+ })]
15763
15736
  });
15764
- promise.resolve = resolve;
15765
- actionUIRenderedPromiseWeakMap.set(action, promise);
15766
- return promise;
15767
15737
  };
15768
15738
 
15769
- const useFocusGroup = (
15770
- elementRef,
15771
- { enabled = true, direction, skipTab, loop, name } = {},
15772
- ) => {
15773
- useLayoutEffect(() => {
15774
- if (!enabled) {
15775
- return null;
15739
+ installImportMetaCss(import.meta);import.meta.css = /* css */`
15740
+ @layer navi {
15741
+ label {
15742
+ cursor: pointer;
15776
15743
  }
15777
- const focusGroup = initFocusGroup(elementRef.current, {
15778
- direction,
15779
- skipTab,
15780
- loop,
15781
- name,
15782
- });
15783
- return focusGroup.cleanup;
15784
- }, [direction, skipTab, loop, name]);
15785
- };
15786
15744
 
15787
- installImportMetaCss(import.meta);const rightArrowPath = "M680-480L360-160l-80-80 240-240-240-240 80-80 320 320z";
15788
- const downArrowPath = "M480-280L160-600l80-80 240 240 240-240 80 80-320 320z";
15789
- import.meta.css = /* css */`
15790
- .summary_marker {
15791
- width: 1em;
15792
- height: 1em;
15793
- line-height: 1em;
15794
- }
15795
- .summary_marker_svg .arrow {
15796
- animation-duration: 0.3s;
15797
- animation-fill-mode: forwards;
15798
- animation-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1);
15799
- }
15800
- .summary_marker_svg .arrow[data-animation-target="down"] {
15801
- animation-name: morph-to-down;
15802
- }
15803
- @keyframes morph-to-down {
15804
- from {
15805
- d: path("${rightArrowPath}");
15806
- }
15807
- to {
15808
- d: path("${downArrowPath}");
15809
- }
15810
- }
15811
- .summary_marker_svg .arrow[data-animation-target="right"] {
15812
- animation-name: morph-to-right;
15813
- }
15814
- @keyframes morph-to-right {
15815
- from {
15816
- d: path("${downArrowPath}");
15817
- }
15818
- to {
15819
- d: path("${rightArrowPath}");
15745
+ label[data-readonly],
15746
+ label[data-disabled] {
15747
+ color: rgba(0, 0, 0, 0.5);
15748
+ cursor: default;
15820
15749
  }
15821
15750
  }
15751
+ `;
15752
+ const ReportReadOnlyOnLabelContext = createContext();
15753
+ const ReportDisabledOnLabelContext = createContext();
15754
+ const Label = props => {
15755
+ const {
15756
+ readOnly,
15757
+ disabled,
15758
+ children,
15759
+ ...rest
15760
+ } = props;
15761
+ const [inputReadOnly, setInputReadOnly] = useState(false);
15762
+ const innerReadOnly = readOnly || inputReadOnly;
15763
+ const [inputDisabled, setInputDisabled] = useState(false);
15764
+ const innerDisabled = disabled || inputDisabled;
15765
+ return jsx(Box, {
15766
+ ...rest,
15767
+ as: "label",
15768
+ basePseudoState: {
15769
+ readOnly: innerReadOnly,
15770
+ disabled: innerDisabled
15771
+ },
15772
+ children: jsx(ReportReadOnlyOnLabelContext.Provider, {
15773
+ value: setInputReadOnly,
15774
+ children: jsx(ReportDisabledOnLabelContext.Provider, {
15775
+ value: setInputDisabled,
15776
+ children: children
15777
+ })
15778
+ })
15779
+ });
15780
+ };
15822
15781
 
15823
- .summary_marker_svg .foreground_circle {
15824
- stroke-dasharray: 503 1507; /* ~25% of circle perimeter */
15825
- stroke-dashoffset: 0;
15826
- animation: progress-around-circle 1.5s linear infinite;
15827
- }
15828
- @keyframes progress-around-circle {
15829
- 0% {
15830
- stroke-dashoffset: 0;
15782
+ installImportMetaCss(import.meta);import.meta.css = /* css */`
15783
+ @layer navi {
15784
+ .navi_checkbox {
15785
+ --outline-offset: 1px;
15786
+ --outline-width: 2px;
15787
+ --border-width: 1px;
15788
+ --border-radius: 2px;
15789
+ --width: 13px;
15790
+ --height: 13px;
15791
+
15792
+ --outline-color: var(--navi-focus-outline-color);
15793
+ --loader-color: var(--navi-loader-color);
15794
+ --border-color: light-dark(#767676, #8e8e93);
15795
+ --background-color: white;
15796
+ --color: light-dark(#4476ff, #3b82f6);
15797
+ --checkmark-color-light: white;
15798
+ --checkmark-color-dark: rgb(55, 55, 55);
15799
+ --checkmark-color: var(--checkmark-color-light);
15800
+
15801
+ --color-mix-light: black;
15802
+ --color-mix-dark: white;
15803
+ --color-mix: var(--color-mix-light);
15804
+
15805
+ /* Hover */
15806
+ --border-color-hover: color-mix(in srgb, var(--border-color) 60%, black);
15807
+ --border-color-hover-checked: color-mix(
15808
+ in srgb,
15809
+ var(--color) 80%,
15810
+ var(--color-mix)
15811
+ );
15812
+ --background-color-hover-checked: color-mix(
15813
+ in srgb,
15814
+ var(--color) 80%,
15815
+ var(--color-mix)
15816
+ );
15817
+ /* Readonly */
15818
+ --border-color-readonly: color-mix(
15819
+ in srgb,
15820
+ var(--border-color) 30%,
15821
+ white
15822
+ );
15823
+ --border-color-readonly-checked: #d3d3d3;
15824
+ --background-color-readonly-checked: grey;
15825
+ --checkmark-color-readonly: #eeeeee;
15826
+ /* Disabled */
15827
+ --border-color-disabled: var(--border-color-readonly);
15828
+ --background-color-disabled: rgba(248, 248, 248, 0.7);
15829
+ --checkmark-color-disabled: #eeeeee;
15830
+ --border-color-disabled-checked: #d3d3d3;
15831
+ --background-color-disabled-checked: #d3d3d3;
15831
15832
  }
15832
- 100% {
15833
- stroke-dashoffset: -2010;
15833
+
15834
+ .navi_checkbox[data-dark] {
15835
+ --color-mix: var(--color-mix-dark);
15836
+ --checkmark-color: var(--navi-checkmark-color-dark);
15834
15837
  }
15835
15838
  }
15836
15839
 
15837
- /* fading and scaling */
15838
- .summary_marker_svg .arrow {
15839
- transition: opacity 0.3s ease-in-out;
15840
- opacity: 1;
15841
- }
15842
- .summary_marker_svg .loading_container {
15843
- transition: transform 0.3s linear;
15844
- transform: scale(0.3);
15840
+ .navi_checkbox {
15841
+ position: relative;
15842
+ display: inline-flex;
15843
+ box-sizing: content-box;
15844
+ margin: 3px 3px 3px 4px;
15845
+
15846
+ --x-border-radius: var(--border-radius);
15847
+ --x-outline-offset: var(--outline-offset);
15848
+ --x-outline-width: var(--outline-width);
15849
+ --x-border-width: var(--border-width);
15850
+ --x-width: var(--width);
15851
+ --x-height: var(--height);
15852
+ --x-outline-color: var(--outline-color);
15853
+ --x-background-color: var(--background-color);
15854
+ --x-border-color: var(--border-color);
15855
+ --x-color: var(--color);
15856
+ --x-checkmark-color: var(--checkmark-color);
15845
15857
  }
15846
- .summary_marker_svg .background_circle,
15847
- .summary_marker_svg .foreground_circle {
15848
- transition: opacity 0.3s ease-in-out;
15858
+ .navi_checkbox .navi_native_field {
15859
+ position: absolute;
15860
+ inset: 0;
15861
+ margin: 0;
15862
+ padding: 0;
15863
+ border: none;
15849
15864
  opacity: 0;
15865
+ cursor: inherit;
15850
15866
  }
15851
- .summary_marker_svg[data-loading] .arrow {
15867
+ .navi_checkbox .navi_checkbox_field {
15868
+ display: inline-flex;
15869
+ box-sizing: border-box;
15870
+ width: var(--x-width);
15871
+ height: var(--x-height);
15872
+ background-color: var(--x-background-color);
15873
+ border-width: var(--x-border-width);
15874
+ border-style: solid;
15875
+ border-color: var(--x-border-color);
15876
+ border-radius: var(--x-border-radius);
15877
+ outline-width: var(--x-outline-width);
15878
+ outline-style: none;
15879
+ outline-color: var(--x-outline-color);
15880
+ outline-offset: var(--x-outline-offset);
15881
+ }
15882
+ .navi_checkbox_marker {
15883
+ width: 100%;
15884
+ height: 100%;
15852
15885
  opacity: 0;
15886
+ stroke: var(--x-checkmark-color);
15887
+ transform: scale(0.5);
15888
+ transition: all 0.15s ease;
15889
+ pointer-events: none;
15853
15890
  }
15854
- .summary_marker_svg[data-loading] .loading_container {
15891
+ .navi_checkbox[data-checked] .navi_checkbox_marker {
15892
+ opacity: 1;
15855
15893
  transform: scale(1);
15856
15894
  }
15857
- .summary_marker_svg[data-loading] .background_circle {
15858
- opacity: 0.2;
15895
+
15896
+ /* Focus */
15897
+ .navi_checkbox[data-focus-visible] .navi_checkbox_field {
15898
+ outline-style: solid;
15859
15899
  }
15860
- .summary_marker_svg[data-loading] .foreground_circle {
15861
- opacity: 1;
15900
+ /* Hover */
15901
+ .navi_checkbox[data-hover] {
15902
+ --x-border-color: var(--border-color-hover);
15903
+ }
15904
+ .navi_checkbox[data-checked][data-hover] {
15905
+ --x-border-color: var(--border-color-hover-checked);
15906
+ --x-background-color: var(--background-color-hover-checked);
15907
+ }
15908
+ /* Checked */
15909
+ .navi_checkbox[data-checked] {
15910
+ --x-background-color: var(--x-color);
15911
+ --x-border-color: var(--x-color);
15912
+ }
15913
+ /* Readonly */
15914
+ .navi_checkbox[data-readonly],
15915
+ .navi_checkbox[data-readonly][data-hover] {
15916
+ --x-border-color: var(--border-color-readonly);
15917
+ --x-background-color: var(--background-color-readonly);
15918
+ }
15919
+ .navi_checkbox[data-readonly][data-checked] {
15920
+ --x-border-color: var(--border-color-readonly-checked);
15921
+ --x-background-color: var(--background-color-readonly-checked);
15922
+ --x-checkmark-color: var(--checkmark-color-readonly);
15923
+ }
15924
+ /* Disabled */
15925
+ .navi_checkbox[data-disabled] {
15926
+ --x-border-color: var(--border-color-disabled);
15927
+ --x-background-color: var(--background-color-disabled);
15928
+ }
15929
+ .navi_checkbox[data-disabled][data-checked] {
15930
+ --x-border-color: var(--border-color-disabled-checked);
15931
+ --x-background-color: var(--background-color-disabled-checked);
15932
+ --x-checkmark-color: var(--checkmark-color-disabled);
15862
15933
  }
15863
15934
  `;
15864
- const SummaryMarker = ({
15865
- open,
15866
- loading
15867
- }) => {
15868
- const showLoading = useDebounceTrue(loading, 300);
15869
- const mountedRef = useRef(false);
15870
- const prevOpenRef = useRef(open);
15871
- useLayoutEffect(() => {
15872
- mountedRef.current = true;
15873
- return () => {
15874
- mountedRef.current = false;
15875
- };
15876
- }, []);
15877
- const shouldAnimate = mountedRef.current && prevOpenRef.current !== open;
15878
- prevOpenRef.current = open;
15879
- return jsx("span", {
15880
- className: "summary_marker",
15881
- children: jsxs("svg", {
15882
- className: "summary_marker_svg",
15883
- viewBox: "0 -960 960 960",
15884
- xmlns: "http://www.w3.org/2000/svg",
15885
- "data-loading": open ? showLoading || undefined : undefined,
15886
- children: [jsxs("g", {
15887
- className: "loading_container",
15888
- "transform-origin": "480px -480px",
15889
- children: [jsx("circle", {
15890
- className: "background_circle",
15891
- cx: "480",
15892
- cy: "-480",
15893
- r: "320",
15894
- stroke: "currentColor",
15895
- fill: "none",
15896
- strokeWidth: "60",
15897
- opacity: "0.2"
15898
- }), jsx("circle", {
15899
- className: "foreground_circle",
15900
- cx: "480",
15901
- cy: "-480",
15902
- r: "320",
15903
- stroke: "currentColor",
15904
- fill: "none",
15905
- strokeWidth: "60",
15906
- strokeLinecap: "round",
15907
- strokeDasharray: "503 1507"
15908
- })]
15909
- }), jsx("g", {
15910
- className: "arrow_container",
15911
- "transform-origin": "480px -480px",
15912
- children: jsx("path", {
15913
- className: "arrow",
15914
- fill: "currentColor",
15915
- "data-animation-target": shouldAnimate ? open ? "down" : "right" : undefined,
15916
- d: open ? downArrowPath : rightArrowPath
15917
- })
15918
- })]
15935
+ const InputCheckbox = props => {
15936
+ const {
15937
+ value = "on"
15938
+ } = props;
15939
+ const uiStateController = useUIStateController(props, "checkbox", {
15940
+ statePropName: "checked",
15941
+ defaultStatePropName: "defaultChecked",
15942
+ fallbackState: false,
15943
+ getStateFromProp: checked => checked ? value : undefined,
15944
+ getPropFromState: Boolean
15945
+ });
15946
+ const uiState = useUIState(uiStateController);
15947
+ const checkbox = renderActionableComponent(props, {
15948
+ Basic: InputCheckboxBasic,
15949
+ WithAction: InputCheckboxWithAction,
15950
+ InsideForm: InputCheckboxInsideForm
15951
+ });
15952
+ return jsx(UIStateControllerContext.Provider, {
15953
+ value: uiStateController,
15954
+ children: jsx(UIStateContext.Provider, {
15955
+ value: uiState,
15956
+ children: checkbox
15919
15957
  })
15920
15958
  });
15921
15959
  };
15922
-
15923
- installImportMetaCss(import.meta);import.meta.css = /* css */`
15924
- .navi_details {
15925
- position: relative;
15926
- z-index: 1;
15927
- display: flex;
15928
- flex-shrink: 0;
15929
- flex-direction: column;
15930
- }
15931
-
15932
- .navi_details > summary {
15933
- display: flex;
15934
- flex-shrink: 0;
15935
- flex-direction: column;
15936
- cursor: pointer;
15937
- user-select: none;
15938
- }
15939
- .summary_body {
15940
- display: flex;
15941
- width: 100%;
15942
- flex-direction: row;
15943
- align-items: center;
15944
- gap: 0.2em;
15945
- }
15946
- .summary_label {
15947
- display: flex;
15948
- padding-right: 10px;
15949
- flex: 1;
15950
- align-items: center;
15951
- gap: 0.2em;
15952
- }
15953
-
15954
- .navi_details > summary:focus {
15955
- z-index: 1;
15960
+ const CheckboxStyleCSSVars = {
15961
+ "outlineWidth": "--outline-width",
15962
+ "borderWidth": "--border-width",
15963
+ "borderRadius": "--border-radius",
15964
+ "backgroundColor": "--background-color",
15965
+ "borderColor": "--border-color",
15966
+ "color": "--color",
15967
+ ":hover": {
15968
+ backgroundColor: "--background-color-hover",
15969
+ borderColor: "--border-color-hover",
15970
+ color: "--color-hover"
15971
+ },
15972
+ ":active": {
15973
+ borderColor: "--border-color-active"
15974
+ },
15975
+ ":read-only": {
15976
+ backgroundColor: "--background-color-readonly",
15977
+ borderColor: "--border-color-readonly",
15978
+ color: "--color-readonly"
15979
+ },
15980
+ ":disabled": {
15981
+ backgroundColor: "--background-color-disabled",
15982
+ borderColor: "--border-color-disabled",
15983
+ color: "--color-disabled"
15956
15984
  }
15957
- `;
15958
- const Details = forwardRef((props, ref) => {
15959
- return renderActionableComponent(props, ref);
15960
- });
15961
- const DetailsBasic = forwardRef((props, ref) => {
15985
+ };
15986
+ const CheckboxPseudoClasses = [":hover", ":active", ":focus", ":focus-visible", ":read-only", ":disabled", ":checked", ":-navi-loading"];
15987
+ const CheckboxPseudoElements = ["::-navi-loader", "::-navi-checkmark"];
15988
+ const InputCheckboxBasic = props => {
15989
+ const contextFieldName = useContext(FieldNameContext);
15990
+ const contextReadOnly = useContext(ReadOnlyContext);
15991
+ const contextDisabled = useContext(DisabledContext);
15992
+ const contextRequired = useContext(RequiredContext);
15993
+ const contextLoading = useContext(LoadingContext);
15994
+ const loadingElement = useContext(LoadingElementContext);
15995
+ const uiStateController = useContext(UIStateControllerContext);
15996
+ const uiState = useContext(UIStateContext);
15997
+ const reportReadOnlyOnLabel = useContext(ReportReadOnlyOnLabelContext);
15998
+ const reportDisabledOnLabel = useContext(ReportDisabledOnLabelContext);
15962
15999
  const {
15963
- id,
15964
- label = "Summary",
15965
- open,
16000
+ /* eslint-disable no-unused-vars */
16001
+ type,
16002
+ defaultChecked,
16003
+ /* eslint-enable no-unused-vars */
16004
+
16005
+ name,
16006
+ readOnly,
16007
+ disabled,
16008
+ required,
15966
16009
  loading,
15967
- className,
15968
- focusGroup,
15969
- focusGroupDirection,
15970
- arrowKeyShortcuts = true,
15971
- openKeyShortcut = "ArrowRight",
15972
- closeKeyShortcut = "ArrowLeft",
15973
- onToggle,
15974
- children,
16010
+ autoFocus,
16011
+ constraints = [],
16012
+ onClick,
16013
+ onInput,
16014
+ color,
15975
16015
  ...rest
15976
16016
  } = props;
15977
- const innerRef = useRef();
15978
- useImperativeHandle(ref, () => innerRef.current);
15979
- const [navState, setNavState] = useNavState(id);
15980
- const [innerOpen, innerOpenSetter] = useState(open || navState);
15981
- useFocusGroup(innerRef, {
15982
- enabled: focusGroup,
15983
- name: typeof focusGroup === "string" ? focusGroup : undefined,
15984
- direction: focusGroupDirection
15985
- });
15986
-
15987
- /**
15988
- * Browser will dispatch "toggle" event even if we set open={true}
15989
- * When rendering the component for the first time
15990
- * We have to ensure the initial "toggle" event is ignored.
15991
- *
15992
- * If we don't do that code will think the details has changed and run logic accordingly
15993
- * For example it will try to navigate to the current url while we are already there
15994
- *
15995
- * See:
15996
- * - https://techblog.thescore.com/2024/10/08/why-we-decided-to-change-how-the-details-element-works/
15997
- * - https://github.com/whatwg/html/issues/4500
15998
- * - https://stackoverflow.com/questions/58942600/react-html-details-toggles-uncontrollably-when-starts-open
15999
- *
16000
- */
16001
-
16002
- const summaryRef = useRef(null);
16003
- useKeyboardShortcuts(innerRef, [{
16004
- key: openKeyShortcut,
16005
- enabled: arrowKeyShortcuts,
16006
- when: e => document.activeElement === summaryRef.current &&
16007
- // avoid handling openKeyShortcut twice when keydown occurs inside nested details
16008
- !e.defaultPrevented,
16009
- action: e => {
16010
- const details = innerRef.current;
16011
- if (!details.open) {
16012
- e.preventDefault();
16013
- details.open = true;
16014
- return;
16015
- }
16016
- const summary = summaryRef.current;
16017
- const firstFocusableElementInDetails = findAfter(summary, elementIsFocusable, {
16018
- root: details
16019
- });
16020
- if (!firstFocusableElementInDetails) {
16021
- return;
16022
- }
16017
+ const defaultRef = useRef();
16018
+ const ref = props.ref || defaultRef;
16019
+ const innerName = name || contextFieldName;
16020
+ const innerDisabled = disabled || contextDisabled;
16021
+ const innerRequired = required || contextRequired;
16022
+ const innerLoading = loading || contextLoading && loadingElement === ref.current;
16023
+ const innerReadOnly = readOnly || contextReadOnly || innerLoading || uiStateController.readOnly;
16024
+ reportReadOnlyOnLabel?.(innerReadOnly);
16025
+ reportDisabledOnLabel?.(innerDisabled);
16026
+ useAutoFocus(ref, autoFocus);
16027
+ useConstraints(ref, constraints);
16028
+ const checked = Boolean(uiState);
16029
+ const innerOnClick = useStableCallback(e => {
16030
+ if (innerReadOnly) {
16023
16031
  e.preventDefault();
16024
- firstFocusableElementInDetails.focus();
16025
16032
  }
16026
- }, {
16027
- key: closeKeyShortcut,
16028
- enabled: arrowKeyShortcuts,
16029
- when: () => {
16030
- const details = innerRef.current;
16031
- return details.open;
16033
+ onClick?.(e);
16034
+ });
16035
+ const innerOnInput = useStableCallback(e => {
16036
+ const checkbox = e.target;
16037
+ const checkboxIsChecked = checkbox.checked;
16038
+ uiStateController.setUIState(checkboxIsChecked, e);
16039
+ onInput?.(e);
16040
+ });
16041
+ const renderCheckbox = checkboxProps => jsx(Box, {
16042
+ ...checkboxProps,
16043
+ as: "input",
16044
+ ref: ref,
16045
+ type: "checkbox",
16046
+ name: innerName,
16047
+ checked: checked,
16048
+ required: innerRequired,
16049
+ baseClassName: "navi_native_field",
16050
+ "data-callout-arrow-x": "center",
16051
+ onClick: innerOnClick,
16052
+ onInput: innerOnInput,
16053
+ onresetuistate: e => {
16054
+ uiStateController.resetUIState(e);
16032
16055
  },
16033
- action: e => {
16034
- const details = innerRef.current;
16035
- const summary = summaryRef.current;
16036
- if (document.activeElement === summary) {
16037
- e.preventDefault();
16038
- summary.focus();
16039
- details.open = false;
16040
- } else {
16041
- e.preventDefault();
16042
- summary.focus();
16043
- }
16056
+ onsetuistate: e => {
16057
+ uiStateController.setUIState(e.detail.value, e);
16044
16058
  }
16045
- }]);
16046
- const mountedRef = useRef(false);
16047
- useEffect(() => {
16048
- mountedRef.current = true;
16049
- }, []);
16050
- return jsxs("details", {
16059
+ });
16060
+ const renderCheckboxMemoized = useCallback(renderCheckbox, [innerName, checked, innerRequired]);
16061
+ useLayoutEffect(() => {
16062
+ const naviCheckbox = ref.current;
16063
+ const lightColor = "var(--checkmark-color-light)";
16064
+ const darkColor = "var(--checkmark-color-dark)";
16065
+ const colorPicked = pickLightOrDark("var(--color)", lightColor, darkColor, naviCheckbox);
16066
+ if (colorPicked === lightColor) {
16067
+ naviCheckbox.removeAttribute("data-dark");
16068
+ } else {
16069
+ naviCheckbox.setAttribute("data-dark", "");
16070
+ }
16071
+ }, [color]);
16072
+ return jsxs(Box, {
16073
+ as: "span",
16051
16074
  ...rest,
16052
- ref: innerRef,
16053
- id: id,
16054
- className: ["navi_details", ...(className ? className.split(" ") : [])].join(" "),
16055
- onToggle: e => {
16056
- const isOpen = e.newState === "open";
16057
- if (mountedRef.current) {
16058
- if (isOpen) {
16059
- innerOpenSetter(true);
16060
- setNavState(true);
16061
- } else {
16062
- innerOpenSetter(false);
16063
- setNavState(undefined);
16064
- }
16065
- }
16066
- onToggle?.(e);
16075
+ ref: ref,
16076
+ baseClassName: "navi_checkbox",
16077
+ pseudoStateSelector: ".navi_native_field",
16078
+ styleCSSVars: CheckboxStyleCSSVars,
16079
+ pseudoClasses: CheckboxPseudoClasses,
16080
+ pseudoElements: CheckboxPseudoElements,
16081
+ basePseudoState: {
16082
+ ":read-only": innerReadOnly,
16083
+ ":disabled": innerDisabled,
16084
+ ":-navi-loading": innerLoading
16067
16085
  },
16068
- open: innerOpen,
16069
- children: [jsx("summary", {
16070
- ref: summaryRef,
16071
- children: jsxs("div", {
16072
- className: "summary_body",
16073
- children: [jsx(SummaryMarker, {
16074
- open: innerOpen,
16075
- loading: loading
16076
- }), jsx("div", {
16077
- className: "summary_label",
16078
- children: label
16079
- })]
16086
+ color: color,
16087
+ hasChildFunction: true,
16088
+ children: [jsx(LoaderBackground, {
16089
+ loading: innerLoading,
16090
+ inset: -1,
16091
+ color: "var(--loader-color)"
16092
+ }), renderCheckboxMemoized, jsx("div", {
16093
+ className: "navi_checkbox_field",
16094
+ children: jsx("svg", {
16095
+ viewBox: "0 0 12 12",
16096
+ "aria-hidden": "true",
16097
+ className: "navi_checkbox_marker",
16098
+ children: jsx("path", {
16099
+ d: "M10.5 2L4.5 9L1.5 5.5",
16100
+ fill: "none",
16101
+ strokeWidth: "2"
16102
+ })
16080
16103
  })
16081
- }), children]
16104
+ })]
16082
16105
  });
16083
- });
16084
- forwardRef((props, ref) => {
16106
+ };
16107
+ const InputCheckboxWithAction = props => {
16108
+ const uiStateController = useContext(UIStateControllerContext);
16109
+ const uiState = useContext(UIStateContext);
16085
16110
  const {
16086
16111
  action,
16087
- loading,
16088
- onToggle,
16112
+ onCancel,
16113
+ onChange,
16114
+ actionErrorEffect,
16089
16115
  onActionPrevented,
16090
16116
  onActionStart,
16117
+ onActionAbort,
16091
16118
  onActionError,
16092
16119
  onActionEnd,
16093
- children,
16120
+ loading,
16094
16121
  ...rest
16095
16122
  } = props;
16096
- const innerRef = useRef();
16097
- useImperativeHandle(ref, () => innerRef.current);
16098
- const effectiveAction = useAction(action);
16123
+ const defaultRef = useRef();
16124
+ const ref = props.ref || defaultRef;
16125
+ const [actionBoundToUIState] = useActionBoundToOneParam(action, uiState);
16099
16126
  const {
16100
16127
  loading: actionLoading
16101
- } = useActionStatus(effectiveAction);
16102
- const executeAction = useExecuteAction(innerRef, {
16103
- // the error will be displayed by actionRenderer inside <details>
16104
- errorEffect: "none"
16128
+ } = useActionStatus(actionBoundToUIState);
16129
+ const executeAction = useExecuteAction(ref, {
16130
+ errorEffect: actionErrorEffect
16105
16131
  });
16106
- useActionEvents(innerRef, {
16107
- onPrevented: onActionPrevented,
16108
- onAction: e => {
16109
- executeAction(e);
16132
+
16133
+ // In this situation updating the ui state === calling associated action
16134
+ // so cance/abort/error have to revert the ui state to the one before user interaction
16135
+ // to show back the real state of the checkbox (not the one user tried to set)
16136
+ useActionEvents(ref, {
16137
+ onCancel: (e, reason) => {
16138
+ if (reason === "blur_invalid") {
16139
+ return;
16140
+ }
16141
+ uiStateController.resetUIState(e);
16142
+ onCancel?.(e, reason);
16110
16143
  },
16144
+ onPrevented: onActionPrevented,
16145
+ onAction: executeAction,
16111
16146
  onStart: onActionStart,
16112
- onError: onActionError,
16113
- onEnd: onActionEnd
16147
+ onAbort: e => {
16148
+ uiStateController.resetUIState(e);
16149
+ onActionAbort?.(e);
16150
+ },
16151
+ onError: e => {
16152
+ uiStateController.resetUIState(e);
16153
+ onActionError?.(e);
16154
+ },
16155
+ onEnd: e => {
16156
+ onActionEnd?.(e);
16157
+ }
16114
16158
  });
16115
- return jsx(DetailsBasic, {
16159
+ return jsx(InputCheckboxBasic, {
16160
+ "data-action": actionBoundToUIState.name,
16116
16161
  ...rest,
16117
- ref: innerRef,
16162
+ ref: ref,
16118
16163
  loading: loading || actionLoading,
16119
- onToggle: toggleEvent => {
16120
- const isOpen = toggleEvent.newState === "open";
16121
- if (isOpen) {
16122
- requestAction(toggleEvent.target, effectiveAction, {
16123
- event: toggleEvent,
16124
- method: "run"
16125
- });
16126
- } else {
16127
- effectiveAction.abort();
16128
- }
16129
- onToggle?.(toggleEvent);
16130
- },
16131
- children: jsx(ActionRenderer, {
16132
- action: effectiveAction,
16133
- children: children
16134
- })
16135
- });
16136
- });
16137
-
16138
- installImportMetaCss(import.meta);import.meta.css = /* css */`
16139
- @layer navi {
16140
- label {
16141
- cursor: pointer;
16142
- }
16143
-
16144
- label[data-readonly],
16145
- label[data-disabled] {
16146
- color: rgba(0, 0, 0, 0.5);
16147
- cursor: default;
16164
+ onChange: e => {
16165
+ requestAction(e.target, actionBoundToUIState, {
16166
+ event: e
16167
+ });
16168
+ onChange?.(e);
16148
16169
  }
16149
- }
16150
- `;
16151
- const ReportReadOnlyOnLabelContext = createContext();
16152
- const ReportDisabledOnLabelContext = createContext();
16153
- const Label = props => {
16154
- const {
16155
- readOnly,
16156
- disabled,
16157
- children,
16158
- ...rest
16159
- } = props;
16160
- const [inputReadOnly, setInputReadOnly] = useState(false);
16161
- const innerReadOnly = readOnly || inputReadOnly;
16162
- const [inputDisabled, setInputDisabled] = useState(false);
16163
- const innerDisabled = disabled || inputDisabled;
16164
- return jsx(Box, {
16165
- ...rest,
16166
- as: "label",
16167
- basePseudoState: {
16168
- readOnly: innerReadOnly,
16169
- disabled: innerDisabled
16170
- },
16171
- children: jsx(ReportReadOnlyOnLabelContext.Provider, {
16172
- value: setInputReadOnly,
16173
- children: jsx(ReportDisabledOnLabelContext.Provider, {
16174
- value: setInputDisabled,
16175
- children: children
16176
- })
16177
- })
16178
16170
  });
16179
16171
  };
16172
+ const InputCheckboxInsideForm = InputCheckboxBasic;
16180
16173
 
16181
16174
  installImportMetaCss(import.meta);import.meta.css = /* css */`
16182
16175
  @layer navi {
16183
- .navi_checkbox {
16176
+ .navi_radio {
16184
16177
  --outline-offset: 1px;
16185
16178
  --outline-width: 2px;
16186
- --border-width: 1px;
16187
- --border-radius: 2px;
16188
16179
  --width: 13px;
16189
16180
  --height: 13px;
16190
16181
 
@@ -16193,12 +16184,11 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
16193
16184
  --border-color: light-dark(#767676, #8e8e93);
16194
16185
  --background-color: white;
16195
16186
  --color: light-dark(#4476ff, #3b82f6);
16196
- --checkmark-color-light: white;
16197
- --checkmark-color-dark: rgb(55, 55, 55);
16198
- --checkmark-color: var(--checkmark-color-light);
16187
+ --radiomark-color: var(--color);
16188
+ --border-color-checked: var(--color);
16199
16189
 
16200
- --color-mix-light: black;
16201
- --color-mix-dark: white;
16190
+ --color-mix-light: white;
16191
+ --color-mix-dark: black;
16202
16192
  --color-mix: var(--color-mix-light);
16203
16193
 
16204
16194
  /* Hover */
@@ -16208,7 +16198,7 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
16208
16198
  var(--color) 80%,
16209
16199
  var(--color-mix)
16210
16200
  );
16211
- --background-color-hover-checked: color-mix(
16201
+ --radiomark-color-hover: color-mix(
16212
16202
  in srgb,
16213
16203
  var(--color) 80%,
16214
16204
  var(--color-mix)
@@ -16219,30 +16209,31 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
16219
16209
  var(--border-color) 30%,
16220
16210
  white
16221
16211
  );
16212
+ --background-color-readonly: var(--background-color);
16213
+ --radiomark-color-readonly: grey;
16222
16214
  --border-color-readonly-checked: #d3d3d3;
16223
- --background-color-readonly-checked: grey;
16224
- --checkmark-color-readonly: #eeeeee;
16215
+ --background-color-readonly-checked: #d3d3d3;
16225
16216
  /* Disabled */
16226
16217
  --border-color-disabled: var(--border-color-readonly);
16227
16218
  --background-color-disabled: rgba(248, 248, 248, 0.7);
16228
- --checkmark-color-disabled: #eeeeee;
16229
- --border-color-disabled-checked: #d3d3d3;
16230
- --background-color-disabled-checked: #d3d3d3;
16219
+ --radiomark-color-disabled: #d3d3d3;
16220
+ --border-color-checked-disabled: #d3d3d3;
16221
+ --background-color-disabled-checked: var(--background-color);
16231
16222
  }
16232
16223
 
16233
- .navi_checkbox[data-dark] {
16224
+ .navi_radio[data-dark] {
16234
16225
  --color-mix: var(--color-mix-dark);
16235
- --checkmark-color: var(--navi-checkmark-color-dark);
16236
16226
  }
16237
16227
  }
16238
16228
 
16239
- .navi_checkbox {
16229
+ .navi_radio {
16240
16230
  position: relative;
16241
16231
  display: inline-flex;
16242
16232
  box-sizing: content-box;
16243
- margin: 3px 3px 3px 4px;
16233
+ margin-top: 3px;
16234
+ margin-right: 3px;
16235
+ margin-left: 5px;
16244
16236
 
16245
- --x-border-radius: var(--border-radius);
16246
16237
  --x-outline-offset: var(--outline-offset);
16247
16238
  --x-outline-width: var(--outline-width);
16248
16239
  --x-border-width: var(--border-width);
@@ -16252,111 +16243,128 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
16252
16243
  --x-background-color: var(--background-color);
16253
16244
  --x-border-color: var(--border-color);
16254
16245
  --x-color: var(--color);
16255
- --x-checkmark-color: var(--checkmark-color);
16246
+ --x-radiomark-color: var(--radiomark-color);
16256
16247
  }
16257
- .navi_checkbox .navi_native_field {
16248
+ .navi_radio .navi_native_field {
16258
16249
  position: absolute;
16259
16250
  inset: 0;
16260
16251
  margin: 0;
16261
16252
  padding: 0;
16262
- border: none;
16263
16253
  opacity: 0;
16264
16254
  cursor: inherit;
16265
16255
  }
16266
- .navi_checkbox .navi_checkbox_field {
16256
+ .navi_radio .navi_radio_field {
16267
16257
  display: inline-flex;
16268
16258
  box-sizing: border-box;
16269
16259
  width: var(--x-width);
16270
16260
  height: var(--x-height);
16271
- background-color: var(--x-background-color);
16272
- border-width: var(--x-border-width);
16273
- border-style: solid;
16274
- border-color: var(--x-border-color);
16275
- border-radius: var(--x-border-radius);
16261
+ align-items: center;
16262
+ justify-content: center;
16263
+ border-radius: 50%;
16276
16264
  outline-width: var(--x-outline-width);
16277
16265
  outline-style: none;
16278
16266
  outline-color: var(--x-outline-color);
16279
16267
  outline-offset: var(--x-outline-offset);
16280
16268
  }
16281
- .navi_checkbox_marker {
16282
- width: 100%;
16283
- height: 100%;
16284
- opacity: 0;
16285
- stroke: var(--x-checkmark-color);
16286
- transform: scale(0.5);
16287
- transition: all 0.15s ease;
16269
+ .navi_radio_field svg {
16270
+ overflow: visible;
16271
+ }
16272
+ .navi_radio_border {
16273
+ fill: var(--x-background-color);
16274
+ stroke: var(--x-border-color);
16275
+ }
16276
+ .navi_radio_marker {
16277
+ width: 100%;
16278
+ height: 100%;
16279
+ opacity: 0;
16280
+ fill: var(--x-radiomark-color);
16281
+ transform: scale(0.3);
16282
+ transform-origin: center;
16288
16283
  pointer-events: none;
16289
16284
  }
16290
- .navi_checkbox[data-checked] .navi_checkbox_marker {
16291
- opacity: 1;
16292
- transform: scale(1);
16285
+ .navi_radio_dashed_border {
16286
+ display: none;
16287
+ }
16288
+ .navi_radio[data-transition] .navi_radio_marker {
16289
+ transition: all 0.15s ease;
16290
+ }
16291
+ .navi_radio[data-transition] .navi_radio_dashed_border {
16292
+ transition: all 0.15s ease;
16293
+ }
16294
+ .navi_radio[data-transition] .navi_radio_border {
16295
+ transition: all 0.15s ease;
16293
16296
  }
16294
16297
 
16295
16298
  /* Focus */
16296
- .navi_checkbox[data-focus-visible] .navi_checkbox_field {
16299
+ .navi_radio[data-focus-visible] .navi_radio_field {
16297
16300
  outline-style: solid;
16298
16301
  }
16299
16302
  /* Hover */
16300
- .navi_checkbox[data-hover] {
16303
+ .navi_radio[data-hover] {
16301
16304
  --x-border-color: var(--border-color-hover);
16302
- }
16303
- .navi_checkbox[data-checked][data-hover] {
16304
- --x-border-color: var(--border-color-hover-checked);
16305
- --x-background-color: var(--background-color-hover-checked);
16305
+ --x-radiomark-color: var(--radiomark-color-hover);
16306
16306
  }
16307
16307
  /* Checked */
16308
- .navi_checkbox[data-checked] {
16309
- --x-background-color: var(--x-color);
16310
- --x-border-color: var(--x-color);
16308
+ .navi_radio[data-checked] {
16309
+ --x-border-color: var(--border-color-checked);
16310
+ }
16311
+ .navi_radio[data-checked] .navi_radio_marker {
16312
+ opacity: 1;
16313
+ transform: scale(1);
16314
+ }
16315
+ .navi_radio[data-hover][data-checked] {
16316
+ --x-border-color: var(--border-color-hover-checked);
16311
16317
  }
16312
16318
  /* Readonly */
16313
- .navi_checkbox[data-readonly],
16314
- .navi_checkbox[data-readonly][data-hover] {
16315
- --x-border-color: var(--border-color-readonly);
16319
+ .navi_radio[data-readonly] {
16316
16320
  --x-background-color: var(--background-color-readonly);
16321
+ --x-border-color: var(--border-color-readonly);
16322
+ --x-radiomark-color: var(--radiomark-color-readonly);
16317
16323
  }
16318
- .navi_checkbox[data-readonly][data-checked] {
16319
- --x-border-color: var(--border-color-readonly-checked);
16324
+ .navi_radio[data-readonly] .navi_radio_dashed_border {
16325
+ display: none;
16326
+ }
16327
+ .navi_radio[data-readonly][data-checked] {
16320
16328
  --x-background-color: var(--background-color-readonly-checked);
16321
- --x-checkmark-color: var(--checkmark-color-readonly);
16329
+ --x-border-color: var(--border-color-readonly-checked);
16330
+ --x-radiomark-color: var(--radiomark-color-readonly);
16322
16331
  }
16323
16332
  /* Disabled */
16324
- .navi_checkbox[data-disabled] {
16325
- --x-border-color: var(--border-color-disabled);
16333
+ .navi_radio[data-disabled] {
16326
16334
  --x-background-color: var(--background-color-disabled);
16335
+ --x-border-color: var(--border-color-disabled);
16336
+ --x-radiomark-color: var(--radiomark-color-disabled);
16327
16337
  }
16328
- .navi_checkbox[data-disabled][data-checked] {
16329
- --x-border-color: var(--border-color-disabled-checked);
16330
- --x-background-color: var(--background-color-disabled-checked);
16331
- --x-checkmark-color: var(--checkmark-color-disabled);
16338
+ .navi_radio[data-disabled][data-checked] {
16339
+ --x-border-color: var(--border-color-disabled);
16340
+ --x-radiomark-color: var(--radiomark-color-disabled);
16332
16341
  }
16333
16342
  `;
16334
- const InputCheckbox = props => {
16343
+ const InputRadio = props => {
16335
16344
  const {
16336
16345
  value = "on"
16337
16346
  } = props;
16338
- const uiStateController = useUIStateController(props, "checkbox", {
16347
+ const uiStateController = useUIStateController(props, "radio", {
16339
16348
  statePropName: "checked",
16340
- defaultStatePropName: "defaultChecked",
16341
16349
  fallbackState: false,
16342
16350
  getStateFromProp: checked => checked ? value : undefined,
16343
16351
  getPropFromState: Boolean
16344
16352
  });
16345
16353
  const uiState = useUIState(uiStateController);
16346
- const checkbox = renderActionableComponent(props, {
16347
- Basic: InputCheckboxBasic,
16348
- WithAction: InputCheckboxWithAction,
16349
- InsideForm: InputCheckboxInsideForm
16354
+ const radio = renderActionableComponent(props, {
16355
+ Basic: InputRadioBasic,
16356
+ WithAction: InputRadioWithAction,
16357
+ InsideForm: InputRadioInsideForm
16350
16358
  });
16351
16359
  return jsx(UIStateControllerContext.Provider, {
16352
16360
  value: uiStateController,
16353
16361
  children: jsx(UIStateContext.Provider, {
16354
16362
  value: uiState,
16355
- children: checkbox
16363
+ children: radio
16356
16364
  })
16357
16365
  });
16358
16366
  };
16359
- const CheckboxStyleCSSVars = {
16367
+ const RadioStyleCSSVars = {
16360
16368
  "outlineWidth": "--outline-width",
16361
16369
  "borderWidth": "--border-width",
16362
16370
  "borderRadius": "--border-radius",
@@ -16382,15 +16390,15 @@ const CheckboxStyleCSSVars = {
16382
16390
  color: "--color-disabled"
16383
16391
  }
16384
16392
  };
16385
- const CheckboxPseudoClasses = [":hover", ":active", ":focus", ":focus-visible", ":read-only", ":disabled", ":checked", ":-navi-loading"];
16386
- const CheckboxPseudoElements = ["::-navi-loader", "::-navi-checkmark"];
16387
- const InputCheckboxBasic = props => {
16388
- const contextFieldName = useContext(FieldNameContext);
16393
+ const RadioPseudoClasses = [":hover", ":active", ":focus", ":focus-visible", ":read-only", ":disabled", ":checked", ":-navi-loading"];
16394
+ const RadioPseudoElements = ["::-navi-loader", "::-navi-radiomark"];
16395
+ const InputRadioBasic = props => {
16396
+ const contextName = useContext(FieldNameContext);
16389
16397
  const contextReadOnly = useContext(ReadOnlyContext);
16390
16398
  const contextDisabled = useContext(DisabledContext);
16391
16399
  const contextRequired = useContext(RequiredContext);
16392
16400
  const contextLoading = useContext(LoadingContext);
16393
- const loadingElement = useContext(LoadingElementContext);
16401
+ const contextLoadingElement = useContext(LoadingElementContext);
16394
16402
  const uiStateController = useContext(UIStateControllerContext);
16395
16403
  const uiState = useContext(UIStateContext);
16396
16404
  const reportReadOnlyOnLabel = useContext(ReportReadOnlyOnLabelContext);
@@ -16398,7 +16406,6 @@ const InputCheckboxBasic = props => {
16398
16406
  const {
16399
16407
  /* eslint-disable no-unused-vars */
16400
16408
  type,
16401
- defaultChecked,
16402
16409
  /* eslint-enable no-unused-vars */
16403
16410
 
16404
16411
  name,
@@ -16415,35 +16422,64 @@ const InputCheckboxBasic = props => {
16415
16422
  } = props;
16416
16423
  const defaultRef = useRef();
16417
16424
  const ref = props.ref || defaultRef;
16418
- const innerName = name || contextFieldName;
16425
+ const innerName = name || contextName;
16419
16426
  const innerDisabled = disabled || contextDisabled;
16420
16427
  const innerRequired = required || contextRequired;
16421
- const innerLoading = loading || contextLoading && loadingElement === ref.current;
16428
+ const innerLoading = loading || contextLoading && contextLoadingElement === ref.current;
16422
16429
  const innerReadOnly = readOnly || contextReadOnly || innerLoading || uiStateController.readOnly;
16423
16430
  reportReadOnlyOnLabel?.(innerReadOnly);
16424
16431
  reportDisabledOnLabel?.(innerDisabled);
16425
16432
  useAutoFocus(ref, autoFocus);
16426
16433
  useConstraints(ref, constraints);
16427
16434
  const checked = Boolean(uiState);
16435
+ // we must first dispatch an event to inform all other radios they where unchecked
16436
+ // this way each other radio uiStateController knows thery are unchecked
16437
+ // we do this on "input"
16438
+ // but also when we are becoming checked from outside (hence the useLayoutEffect)
16439
+ const updateOtherRadiosInGroup = () => {
16440
+ const thisRadio = ref.current;
16441
+ const radioList = thisRadio.closest("[data-radio-list]");
16442
+ if (!radioList) {
16443
+ return;
16444
+ }
16445
+ const radioInputs = radioList.querySelectorAll(`input[type="radio"][name="${thisRadio.name}"]`);
16446
+ for (const radioInput of radioInputs) {
16447
+ if (radioInput === thisRadio) {
16448
+ continue;
16449
+ }
16450
+ radioInput.dispatchEvent(new CustomEvent("setuistate", {
16451
+ detail: false
16452
+ }));
16453
+ }
16454
+ };
16455
+ useLayoutEffect(() => {
16456
+ if (checked) {
16457
+ updateOtherRadiosInGroup();
16458
+ }
16459
+ }, [checked]);
16460
+ const innerOnInput = useStableCallback(e => {
16461
+ const radio = e.target;
16462
+ const radioIsChecked = radio.checked;
16463
+ if (radioIsChecked) {
16464
+ updateOtherRadiosInGroup();
16465
+ }
16466
+ uiStateController.setUIState(radioIsChecked, e);
16467
+ onInput?.(e);
16468
+ });
16428
16469
  const innerOnClick = useStableCallback(e => {
16429
16470
  if (innerReadOnly) {
16430
16471
  e.preventDefault();
16431
16472
  }
16432
16473
  onClick?.(e);
16433
16474
  });
16434
- const innerOnInput = useStableCallback(e => {
16435
- const checkbox = e.target;
16436
- const checkboxIsChecked = checkbox.checked;
16437
- uiStateController.setUIState(checkboxIsChecked, e);
16438
- onInput?.(e);
16439
- });
16440
- const renderCheckbox = checkboxProps => jsx(Box, {
16441
- ...checkboxProps,
16475
+ const renderRadio = radioProps => jsx(Box, {
16476
+ ...radioProps,
16442
16477
  as: "input",
16443
16478
  ref: ref,
16444
- type: "checkbox",
16479
+ type: "radio",
16445
16480
  name: innerName,
16446
16481
  checked: checked,
16482
+ disabled: innerDisabled,
16447
16483
  required: innerRequired,
16448
16484
  baseClassName: "navi_native_field",
16449
16485
  "data-callout-arrow-x": "center",
@@ -16456,27 +16492,25 @@ const InputCheckboxBasic = props => {
16456
16492
  uiStateController.setUIState(e.detail.value, e);
16457
16493
  }
16458
16494
  });
16459
- const renderCheckboxMemoized = useCallback(renderCheckbox, [innerName, checked, innerRequired]);
16495
+ const renderRadioMemoized = useCallback(renderRadio, [innerName, checked, innerRequired]);
16460
16496
  useLayoutEffect(() => {
16461
- const naviCheckbox = ref.current;
16462
- const lightColor = "var(--checkmark-color-light)";
16463
- const darkColor = "var(--checkmark-color-dark)";
16464
- const colorPicked = pickLightOrDark("var(--color)", lightColor, darkColor, naviCheckbox);
16465
- if (colorPicked === lightColor) {
16466
- naviCheckbox.removeAttribute("data-dark");
16497
+ const naviRadio = ref.current;
16498
+ const luminance = resolveColorLuminance("var(--color)", naviRadio);
16499
+ if (luminance < 0.3) {
16500
+ naviRadio.setAttribute("data-dark", "");
16467
16501
  } else {
16468
- naviCheckbox.setAttribute("data-dark", "");
16502
+ naviRadio.removeAttribute("data-dark");
16469
16503
  }
16470
16504
  }, [color]);
16471
16505
  return jsxs(Box, {
16472
16506
  as: "span",
16473
16507
  ...rest,
16474
16508
  ref: ref,
16475
- baseClassName: "navi_checkbox",
16509
+ baseClassName: "navi_radio",
16476
16510
  pseudoStateSelector: ".navi_native_field",
16477
- styleCSSVars: CheckboxStyleCSSVars,
16478
- pseudoClasses: CheckboxPseudoClasses,
16479
- pseudoElements: CheckboxPseudoElements,
16511
+ styleCSSVars: RadioStyleCSSVars,
16512
+ pseudoClasses: RadioPseudoClasses,
16513
+ pseudoElements: RadioPseudoElements,
16480
16514
  basePseudoState: {
16481
16515
  ":read-only": innerReadOnly,
16482
16516
  ":disabled": innerDisabled,
@@ -16487,289 +16521,189 @@ const InputCheckboxBasic = props => {
16487
16521
  children: [jsx(LoaderBackground, {
16488
16522
  loading: innerLoading,
16489
16523
  inset: -1,
16524
+ targetSelector: ".navi_radio_field",
16490
16525
  color: "var(--loader-color)"
16491
- }), renderCheckboxMemoized, jsx("div", {
16492
- className: "navi_checkbox_field",
16493
- children: jsx("svg", {
16526
+ }), renderRadioMemoized, jsx("span", {
16527
+ className: "navi_radio_field",
16528
+ children: jsxs("svg", {
16494
16529
  viewBox: "0 0 12 12",
16495
16530
  "aria-hidden": "true",
16496
- className: "navi_checkbox_marker",
16497
- children: jsx("path", {
16498
- d: "M10.5 2L4.5 9L1.5 5.5",
16499
- fill: "none",
16500
- strokeWidth: "2"
16501
- })
16531
+ preserveAspectRatio: "xMidYMid meet",
16532
+ children: [jsx("circle", {
16533
+ className: "navi_radio_border",
16534
+ cx: "6",
16535
+ cy: "6",
16536
+ r: "5.5",
16537
+ strokeWidth: "1"
16538
+ }), jsx("circle", {
16539
+ className: "navi_radio_dashed_border",
16540
+ cx: "6",
16541
+ cy: "6",
16542
+ r: "5.5",
16543
+ strokeWidth: "1",
16544
+ strokeDasharray: "2.16 2.16",
16545
+ strokeDashoffset: "0"
16546
+ }), jsx("circle", {
16547
+ className: "navi_radio_marker",
16548
+ cx: "6",
16549
+ cy: "6",
16550
+ r: "3.5"
16551
+ })]
16502
16552
  })
16503
16553
  })]
16504
16554
  });
16505
16555
  };
16506
- const InputCheckboxWithAction = props => {
16507
- const uiStateController = useContext(UIStateControllerContext);
16508
- const uiState = useContext(UIStateContext);
16509
- const {
16510
- action,
16511
- onCancel,
16512
- onChange,
16513
- actionErrorEffect,
16514
- onActionPrevented,
16515
- onActionStart,
16516
- onActionAbort,
16517
- onActionError,
16518
- onActionEnd,
16519
- loading,
16520
- ...rest
16521
- } = props;
16522
- const defaultRef = useRef();
16523
- const ref = props.ref || defaultRef;
16524
- const [actionBoundToUIState] = useActionBoundToOneParam(action, uiState);
16525
- const {
16526
- loading: actionLoading
16527
- } = useActionStatus(actionBoundToUIState);
16528
- const executeAction = useExecuteAction(ref, {
16529
- errorEffect: actionErrorEffect
16530
- });
16531
-
16532
- // In this situation updating the ui state === calling associated action
16533
- // so cance/abort/error have to revert the ui state to the one before user interaction
16534
- // to show back the real state of the checkbox (not the one user tried to set)
16535
- useActionEvents(ref, {
16536
- onCancel: (e, reason) => {
16537
- if (reason === "blur_invalid") {
16538
- return;
16539
- }
16540
- uiStateController.resetUIState(e);
16541
- onCancel?.(e, reason);
16542
- },
16543
- onPrevented: onActionPrevented,
16544
- onAction: executeAction,
16545
- onStart: onActionStart,
16546
- onAbort: e => {
16547
- uiStateController.resetUIState(e);
16548
- onActionAbort?.(e);
16549
- },
16550
- onError: e => {
16551
- uiStateController.resetUIState(e);
16552
- onActionError?.(e);
16553
- },
16554
- onEnd: e => {
16555
- onActionEnd?.(e);
16556
- }
16557
- });
16558
- return jsx(InputCheckboxBasic, {
16559
- "data-action": actionBoundToUIState.name,
16560
- ...rest,
16561
- ref: ref,
16562
- loading: loading || actionLoading,
16563
- onChange: e => {
16564
- requestAction(e.target, actionBoundToUIState, {
16565
- event: e
16566
- });
16567
- onChange?.(e);
16568
- }
16569
- });
16570
- };
16571
- const InputCheckboxInsideForm = InputCheckboxBasic;
16556
+ const InputRadioWithAction = () => {
16557
+ throw new Error(`<Input type="radio" /> with an action make no sense. Use <RadioList action={something} /> instead`);
16558
+ };
16559
+ const InputRadioInsideForm = InputRadio;
16572
16560
 
16573
16561
  installImportMetaCss(import.meta);import.meta.css = /* css */`
16574
16562
  @layer navi {
16575
- .navi_radio {
16576
- --outline-offset: 1px;
16577
- --outline-width: 2px;
16578
- --width: 13px;
16579
- --height: 13px;
16563
+ .navi_input {
16564
+ --border-radius: 2px;
16565
+ --border-width: 1px;
16566
+ --outline-width: 1px;
16567
+ --outer-width: calc(var(--border-width) + var(--outline-width));
16580
16568
 
16569
+ /* Default */
16581
16570
  --outline-color: var(--navi-focus-outline-color);
16582
16571
  --loader-color: var(--navi-loader-color);
16583
16572
  --border-color: light-dark(#767676, #8e8e93);
16584
16573
  --background-color: white;
16585
- --color: light-dark(#4476ff, #3b82f6);
16586
- --radiomark-color: var(--color);
16587
- --border-color-checked: var(--color);
16588
-
16589
- --color-mix-light: white;
16590
- --color-mix-dark: black;
16591
- --color-mix: var(--color-mix-light);
16592
-
16574
+ --color: currentColor;
16575
+ --color-dimmed: color-mix(in srgb, currentColor 60%, transparent);
16576
+ --placeholder-color: var(--color-dimmed);
16593
16577
  /* Hover */
16594
- --border-color-hover: color-mix(in srgb, var(--border-color) 60%, black);
16595
- --border-color-hover-checked: color-mix(
16596
- in srgb,
16597
- var(--color) 80%,
16598
- var(--color-mix)
16599
- );
16600
- --radiomark-color-hover: color-mix(
16578
+ --border-color-hover: color-mix(in srgb, var(--border-color) 70%, black);
16579
+ --background-color-hover: color-mix(
16601
16580
  in srgb,
16602
- var(--color) 80%,
16603
- var(--color-mix)
16581
+ var(--background-color) 95%,
16582
+ black
16604
16583
  );
16584
+ --color-hover: var(--color);
16585
+ /* Active */
16586
+ --border-color-active: color-mix(in srgb, var(--border-color) 90%, black);
16605
16587
  /* Readonly */
16606
16588
  --border-color-readonly: color-mix(
16607
16589
  in srgb,
16608
- var(--border-color) 30%,
16609
- white
16590
+ var(--border-color) 45%,
16591
+ transparent
16610
16592
  );
16611
16593
  --background-color-readonly: var(--background-color);
16612
- --radiomark-color-readonly: grey;
16613
- --border-color-readonly-checked: #d3d3d3;
16614
- --background-color-readonly-checked: #d3d3d3;
16594
+ --color-readonly: var(--color-dimmed);
16615
16595
  /* Disabled */
16616
16596
  --border-color-disabled: var(--border-color-readonly);
16617
- --background-color-disabled: rgba(248, 248, 248, 0.7);
16618
- --radiomark-color-disabled: #d3d3d3;
16619
- --border-color-checked-disabled: #d3d3d3;
16620
- --background-color-disabled-checked: var(--background-color);
16621
- }
16622
-
16623
- .navi_radio[data-dark] {
16624
- --color-mix: var(--color-mix-dark);
16597
+ --background-color-disabled: color-mix(
16598
+ in srgb,
16599
+ var(--background-color) 95%,
16600
+ grey
16601
+ );
16602
+ --color-disabled: color-mix(in srgb, var(--color) 95%, grey);
16625
16603
  }
16626
16604
  }
16627
16605
 
16628
- .navi_radio {
16606
+ .navi_input {
16629
16607
  position: relative;
16630
- display: inline-flex;
16631
- box-sizing: content-box;
16632
- margin-top: 3px;
16633
- margin-right: 3px;
16634
- margin-left: 5px;
16608
+ box-sizing: border-box;
16609
+ width: fit-content;
16610
+ height: fit-content;
16611
+ flex-direction: inherit;
16612
+ border-radius: inherit;
16613
+ cursor: inherit;
16635
16614
 
16636
- --x-outline-offset: var(--outline-offset);
16637
16615
  --x-outline-width: var(--outline-width);
16616
+ --x-border-radius: var(--border-radius);
16638
16617
  --x-border-width: var(--border-width);
16639
- --x-width: var(--width);
16640
- --x-height: var(--height);
16618
+ --x-outer-width: calc(var(--x-border-width) + var(--x-outline-width));
16641
16619
  --x-outline-color: var(--outline-color);
16642
- --x-background-color: var(--background-color);
16643
16620
  --x-border-color: var(--border-color);
16621
+ --x-background-color: var(--background-color);
16644
16622
  --x-color: var(--color);
16645
- --x-radiomark-color: var(--radiomark-color);
16646
- }
16647
- .navi_radio .navi_native_field {
16648
- position: absolute;
16649
- inset: 0;
16650
- margin: 0;
16651
- padding: 0;
16652
- opacity: 0;
16653
- cursor: inherit;
16654
- }
16655
- .navi_radio .navi_radio_field {
16656
- display: inline-flex;
16657
- box-sizing: border-box;
16658
- width: var(--x-width);
16659
- height: var(--x-height);
16660
- align-items: center;
16661
- justify-content: center;
16662
- border-radius: 50%;
16663
- outline-width: var(--x-outline-width);
16664
- outline-style: none;
16665
- outline-color: var(--x-outline-color);
16666
- outline-offset: var(--x-outline-offset);
16667
- }
16668
- .navi_radio_field svg {
16669
- overflow: visible;
16670
- }
16671
- .navi_radio_border {
16672
- fill: var(--x-background-color);
16673
- stroke: var(--x-border-color);
16674
- }
16675
- .navi_radio_marker {
16676
- width: 100%;
16677
- height: 100%;
16678
- opacity: 0;
16679
- fill: var(--x-radiomark-color);
16680
- transform: scale(0.3);
16681
- transform-origin: center;
16682
- pointer-events: none;
16683
- }
16684
- .navi_radio_dashed_border {
16685
- display: none;
16686
- }
16687
- .navi_radio[data-transition] .navi_radio_marker {
16688
- transition: all 0.15s ease;
16689
- }
16690
- .navi_radio[data-transition] .navi_radio_dashed_border {
16691
- transition: all 0.15s ease;
16692
- }
16693
- .navi_radio[data-transition] .navi_radio_border {
16694
- transition: all 0.15s ease;
16623
+ --x-placeholder-color: var(--placeholder-color);
16695
16624
  }
16696
16625
 
16697
- /* Focus */
16698
- .navi_radio[data-focus-visible] .navi_radio_field {
16626
+ .navi_input .navi_native_input {
16627
+ box-sizing: border-box;
16628
+ padding-top: var(--padding-top, var(--padding-y, var(--padding, 1px)));
16629
+ padding-right: var(--padding-right, var(--padding-x, var(--padding, 2px)));
16630
+ padding-bottom: var(
16631
+ --padding-bottom,
16632
+ var(--padding-y, var(--padding, 1px))
16633
+ );
16634
+ padding-left: var(--padding-left, var(--padding-x, var(--padding, 2px)));
16635
+ color: var(--x-color);
16636
+ background-color: var(--x-background-color);
16637
+ border-width: var(--x-outer-width);
16638
+ border-width: var(--x-outer-width);
16639
+ border-style: solid;
16640
+ border-color: transparent;
16641
+ border-radius: var(--x-border-radius);
16642
+ outline-width: var(--x-border-width);
16699
16643
  outline-style: solid;
16644
+ outline-color: var(--x-border-color);
16645
+ outline-offset: calc(-1 * (var(--x-border-width)));
16700
16646
  }
16701
- /* Hover */
16702
- .navi_radio[data-hover] {
16703
- --x-border-color: var(--border-color-hover);
16704
- --x-radiomark-color: var(--radiomark-color-hover);
16705
- }
16706
- /* Checked */
16707
- .navi_radio[data-checked] {
16708
- --x-border-color: var(--border-color-checked);
16709
- }
16710
- .navi_radio[data-checked] .navi_radio_marker {
16711
- opacity: 1;
16712
- transform: scale(1);
16647
+ .navi_input .navi_native_input::placeholder {
16648
+ color: var(--x-placeholder-color);
16713
16649
  }
16714
- .navi_radio[data-hover][data-checked] {
16715
- --x-border-color: var(--border-color-hover-checked);
16650
+ .navi_input .navi_native_input:-internal-autofill-selected {
16651
+ /* Webkit is putting some nasty styles after automplete that look as follow */
16652
+ /* input:-internal-autofill-selected { color: FieldText !important; } */
16653
+ /* Fortunately we can override it as follow */
16654
+ -webkit-text-fill-color: var(--x-color) !important;
16716
16655
  }
16717
16656
  /* Readonly */
16718
- .navi_radio[data-readonly] {
16719
- --x-background-color: var(--background-color-readonly);
16657
+ .navi_input[data-readonly] {
16720
16658
  --x-border-color: var(--border-color-readonly);
16721
- --x-radiomark-color: var(--radiomark-color-readonly);
16722
- }
16723
- .navi_radio[data-readonly] .navi_radio_dashed_border {
16724
- display: none;
16659
+ --x-background-color: var(--background-color-readonly);
16660
+ --x-color: var(--color-readonly);
16725
16661
  }
16726
- .navi_radio[data-readonly][data-checked] {
16727
- --x-background-color: var(--background-color-readonly-checked);
16728
- --x-border-color: var(--border-color-readonly-checked);
16729
- --x-radiomark-color: var(--radiomark-color-readonly);
16662
+ /* Focus */
16663
+ .navi_input[data-focus] .navi_native_input,
16664
+ .navi_input[data-focus-visible] .navi_native_input {
16665
+ outline-width: var(--x-outer-width);
16666
+ outline-offset: calc(-1 * var(--x-outer-width));
16667
+ --x-border-color: var(--x-outline-color);
16730
16668
  }
16731
16669
  /* Disabled */
16732
- .navi_radio[data-disabled] {
16733
- --x-background-color: var(--background-color-disabled);
16670
+ .navi_input[data-disabled] {
16734
16671
  --x-border-color: var(--border-color-disabled);
16735
- --x-radiomark-color: var(--radiomark-color-disabled);
16672
+ --x-background-color: var(--background-color-disabled);
16673
+ --x-color: var(--color-disabled);
16736
16674
  }
16737
- .navi_radio[data-disabled][data-checked] {
16738
- --x-border-color: var(--border-color-disabled);
16739
- --x-radiomark-color: var(--radiomark-color-disabled);
16675
+ /* Callout (info, warning, error) */
16676
+ .navi_input[data-callout] {
16677
+ --x-border-color: var(--callout-color);
16740
16678
  }
16741
16679
  `;
16742
- const InputRadio = props => {
16743
- const {
16744
- value = "on"
16745
- } = props;
16746
- const uiStateController = useUIStateController(props, "radio", {
16747
- statePropName: "checked",
16748
- fallbackState: false,
16749
- getStateFromProp: checked => checked ? value : undefined,
16750
- getPropFromState: Boolean
16751
- });
16680
+ const InputTextual = props => {
16681
+ const uiStateController = useUIStateController(props, "input");
16752
16682
  const uiState = useUIState(uiStateController);
16753
- const radio = renderActionableComponent(props, {
16754
- Basic: InputRadioBasic,
16755
- WithAction: InputRadioWithAction,
16756
- InsideForm: InputRadioInsideForm
16683
+ const input = renderActionableComponent(props, {
16684
+ Basic: InputTextualBasic,
16685
+ WithAction: InputTextualWithAction,
16686
+ InsideForm: InputTextualInsideForm
16757
16687
  });
16758
16688
  return jsx(UIStateControllerContext.Provider, {
16759
16689
  value: uiStateController,
16760
16690
  children: jsx(UIStateContext.Provider, {
16761
16691
  value: uiState,
16762
- children: radio
16692
+ children: input
16763
16693
  })
16764
16694
  });
16765
16695
  };
16766
- const RadioStyleCSSVars = {
16696
+ const InputStyleCSSVars = {
16767
16697
  "outlineWidth": "--outline-width",
16768
16698
  "borderWidth": "--border-width",
16769
16699
  "borderRadius": "--border-radius",
16770
- "backgroundColor": "--background-color",
16771
- "borderColor": "--border-color",
16772
- "color": "--color",
16700
+ "paddingTop": "--padding-top",
16701
+ "paddingRight": "--padding-right",
16702
+ "paddingBottom": "--padding-bottom",
16703
+ "paddingLeft": "--padding-left",
16704
+ "backgroundColor": "--background-color",
16705
+ "borderColor": "--border-color",
16706
+ "color": "--color",
16773
16707
  ":hover": {
16774
16708
  backgroundColor: "--background-color-hover",
16775
16709
  borderColor: "--border-color-hover",
@@ -16789,428 +16723,95 @@ const RadioStyleCSSVars = {
16789
16723
  color: "--color-disabled"
16790
16724
  }
16791
16725
  };
16792
- const RadioPseudoClasses = [":hover", ":active", ":focus", ":focus-visible", ":read-only", ":disabled", ":checked", ":-navi-loading"];
16793
- const RadioPseudoElements = ["::-navi-loader", "::-navi-radiomark"];
16794
- const InputRadioBasic = props => {
16795
- const contextName = useContext(FieldNameContext);
16726
+ const InputPseudoClasses = [":hover", ":active", ":focus", ":focus-visible", ":read-only", ":disabled", ":-navi-loading"];
16727
+ const InputPseudoElements = ["::-navi-loader"];
16728
+ const InputTextualBasic = props => {
16796
16729
  const contextReadOnly = useContext(ReadOnlyContext);
16797
16730
  const contextDisabled = useContext(DisabledContext);
16798
- const contextRequired = useContext(RequiredContext);
16799
16731
  const contextLoading = useContext(LoadingContext);
16800
16732
  const contextLoadingElement = useContext(LoadingElementContext);
16733
+ const reportReadOnlyOnLabel = useContext(ReportReadOnlyOnLabelContext);
16801
16734
  const uiStateController = useContext(UIStateControllerContext);
16802
16735
  const uiState = useContext(UIStateContext);
16803
- const reportReadOnlyOnLabel = useContext(ReportReadOnlyOnLabelContext);
16804
- const reportDisabledOnLabel = useContext(ReportDisabledOnLabelContext);
16805
16736
  const {
16806
- /* eslint-disable no-unused-vars */
16807
16737
  type,
16808
- /* eslint-enable no-unused-vars */
16809
-
16810
- name,
16738
+ onInput,
16811
16739
  readOnly,
16812
16740
  disabled,
16813
- required,
16741
+ constraints = [],
16814
16742
  loading,
16815
16743
  autoFocus,
16816
- constraints = [],
16817
- onClick,
16818
- onInput,
16819
- color,
16744
+ autoFocusVisible,
16745
+ autoSelect,
16820
16746
  ...rest
16821
16747
  } = props;
16822
16748
  const defaultRef = useRef();
16823
16749
  const ref = props.ref || defaultRef;
16824
- const innerName = name || contextName;
16825
- const innerDisabled = disabled || contextDisabled;
16826
- const innerRequired = required || contextRequired;
16750
+ const innerValue = type === "datetime-local" ? convertToLocalTimezone(uiState) : uiState;
16827
16751
  const innerLoading = loading || contextLoading && contextLoadingElement === ref.current;
16828
16752
  const innerReadOnly = readOnly || contextReadOnly || innerLoading || uiStateController.readOnly;
16753
+ const innerDisabled = disabled || contextDisabled;
16754
+ // infom any <label> parent of our readOnly state
16829
16755
  reportReadOnlyOnLabel?.(innerReadOnly);
16830
- reportDisabledOnLabel?.(innerDisabled);
16831
- useAutoFocus(ref, autoFocus);
16756
+ useAutoFocus(ref, autoFocus, {
16757
+ autoFocusVisible,
16758
+ autoSelect
16759
+ });
16832
16760
  useConstraints(ref, constraints);
16833
- const checked = Boolean(uiState);
16834
- // we must first dispatch an event to inform all other radios they where unchecked
16835
- // this way each other radio uiStateController knows thery are unchecked
16836
- // we do this on "input"
16837
- // but also when we are becoming checked from outside (hence the useLayoutEffect)
16838
- const updateOtherRadiosInGroup = () => {
16839
- const thisRadio = ref.current;
16840
- const radioList = thisRadio.closest("[data-radio-list]");
16841
- if (!radioList) {
16842
- return;
16843
- }
16844
- const radioInputs = radioList.querySelectorAll(`input[type="radio"][name="${thisRadio.name}"]`);
16845
- for (const radioInput of radioInputs) {
16846
- if (radioInput === thisRadio) {
16847
- continue;
16761
+ const innerOnInput = useStableCallback(onInput);
16762
+ const renderInput = inputProps => {
16763
+ return jsx(Box, {
16764
+ ...inputProps,
16765
+ as: "input",
16766
+ ref: ref,
16767
+ type: type,
16768
+ "data-value": uiState,
16769
+ value: innerValue,
16770
+ onInput: e => {
16771
+ let inputValue;
16772
+ if (type === "number") {
16773
+ inputValue = e.target.valueAsNumber;
16774
+ } else if (type === "datetime-local") {
16775
+ inputValue = convertToUTCTimezone(e.target.value);
16776
+ } else {
16777
+ inputValue = e.target.value;
16778
+ }
16779
+ uiStateController.setUIState(inputValue, e);
16780
+ innerOnInput?.(e);
16781
+ },
16782
+ onresetuistate: e => {
16783
+ uiStateController.resetUIState(e);
16784
+ },
16785
+ onsetuistate: e => {
16786
+ uiStateController.setUIState(e.detail.value, e);
16848
16787
  }
16849
- radioInput.dispatchEvent(new CustomEvent("setuistate", {
16850
- detail: false
16851
- }));
16852
- }
16788
+ // style management
16789
+ ,
16790
+ baseClassName: "navi_native_input"
16791
+ });
16853
16792
  };
16854
- useLayoutEffect(() => {
16855
- if (checked) {
16856
- updateOtherRadiosInGroup();
16857
- }
16858
- }, [checked]);
16859
- const innerOnInput = useStableCallback(e => {
16860
- const radio = e.target;
16861
- const radioIsChecked = radio.checked;
16862
- if (radioIsChecked) {
16863
- updateOtherRadiosInGroup();
16864
- }
16865
- uiStateController.setUIState(radioIsChecked, e);
16866
- onInput?.(e);
16867
- });
16868
- const innerOnClick = useStableCallback(e => {
16869
- if (innerReadOnly) {
16870
- e.preventDefault();
16871
- }
16872
- onClick?.(e);
16873
- });
16874
- const renderRadio = radioProps => jsx(Box, {
16875
- ...radioProps,
16876
- as: "input",
16877
- ref: ref,
16878
- type: "radio",
16879
- name: innerName,
16880
- checked: checked,
16881
- disabled: innerDisabled,
16882
- required: innerRequired,
16883
- baseClassName: "navi_native_field",
16884
- "data-callout-arrow-x": "center",
16885
- onClick: innerOnClick,
16886
- onInput: innerOnInput,
16887
- onresetuistate: e => {
16888
- uiStateController.resetUIState(e);
16889
- },
16890
- onsetuistate: e => {
16891
- uiStateController.setUIState(e.detail.value, e);
16892
- }
16893
- });
16894
- const renderRadioMemoized = useCallback(renderRadio, [innerName, checked, innerRequired]);
16895
- useLayoutEffect(() => {
16896
- const naviRadio = ref.current;
16897
- const luminance = resolveColorLuminance("var(--color)", naviRadio);
16898
- if (luminance < 0.3) {
16899
- naviRadio.setAttribute("data-dark", "");
16900
- } else {
16901
- naviRadio.removeAttribute("data-dark");
16902
- }
16903
- }, [color]);
16793
+ const renderInputMemoized = useCallback(renderInput, [type, uiState, innerValue, innerOnInput]);
16904
16794
  return jsxs(Box, {
16905
16795
  as: "span",
16906
- ...rest,
16907
- ref: ref,
16908
- baseClassName: "navi_radio",
16909
- pseudoStateSelector: ".navi_native_field",
16910
- styleCSSVars: RadioStyleCSSVars,
16911
- pseudoClasses: RadioPseudoClasses,
16912
- pseudoElements: RadioPseudoElements,
16796
+ box: true,
16797
+ baseClassName: "navi_input",
16798
+ styleCSSVars: InputStyleCSSVars,
16799
+ pseudoStateSelector: ".navi_native_input",
16800
+ visualSelector: ".navi_native_input",
16913
16801
  basePseudoState: {
16914
16802
  ":read-only": innerReadOnly,
16915
16803
  ":disabled": innerDisabled,
16916
16804
  ":-navi-loading": innerLoading
16917
16805
  },
16918
- color: color,
16806
+ pseudoClasses: InputPseudoClasses,
16807
+ pseudoElements: InputPseudoElements,
16919
16808
  hasChildFunction: true,
16809
+ ...rest,
16920
16810
  children: [jsx(LoaderBackground, {
16921
16811
  loading: innerLoading,
16922
- inset: -1,
16923
- targetSelector: ".navi_radio_field",
16924
- color: "var(--loader-color)"
16925
- }), renderRadioMemoized, jsx("span", {
16926
- className: "navi_radio_field",
16927
- children: jsxs("svg", {
16928
- viewBox: "0 0 12 12",
16929
- "aria-hidden": "true",
16930
- preserveAspectRatio: "xMidYMid meet",
16931
- children: [jsx("circle", {
16932
- className: "navi_radio_border",
16933
- cx: "6",
16934
- cy: "6",
16935
- r: "5.5",
16936
- strokeWidth: "1"
16937
- }), jsx("circle", {
16938
- className: "navi_radio_dashed_border",
16939
- cx: "6",
16940
- cy: "6",
16941
- r: "5.5",
16942
- strokeWidth: "1",
16943
- strokeDasharray: "2.16 2.16",
16944
- strokeDashoffset: "0"
16945
- }), jsx("circle", {
16946
- className: "navi_radio_marker",
16947
- cx: "6",
16948
- cy: "6",
16949
- r: "3.5"
16950
- })]
16951
- })
16952
- })]
16953
- });
16954
- };
16955
- const InputRadioWithAction = () => {
16956
- throw new Error(`<Input type="radio" /> with an action make no sense. Use <RadioList action={something} /> instead`);
16957
- };
16958
- const InputRadioInsideForm = InputRadio;
16959
-
16960
- installImportMetaCss(import.meta);import.meta.css = /* css */`
16961
- @layer navi {
16962
- .navi_input {
16963
- --border-radius: 2px;
16964
- --border-width: 1px;
16965
- --outline-width: 1px;
16966
- --outer-width: calc(var(--border-width) + var(--outline-width));
16967
-
16968
- /* Default */
16969
- --outline-color: var(--navi-focus-outline-color);
16970
- --loader-color: var(--navi-loader-color);
16971
- --border-color: light-dark(#767676, #8e8e93);
16972
- --background-color: white;
16973
- --color: currentColor;
16974
- --color-dimmed: color-mix(in srgb, currentColor 60%, transparent);
16975
- --placeholder-color: var(--color-dimmed);
16976
- /* Hover */
16977
- --border-color-hover: color-mix(in srgb, var(--border-color) 70%, black);
16978
- --background-color-hover: color-mix(
16979
- in srgb,
16980
- var(--background-color) 95%,
16981
- black
16982
- );
16983
- --color-hover: var(--color);
16984
- /* Active */
16985
- --border-color-active: color-mix(in srgb, var(--border-color) 90%, black);
16986
- /* Readonly */
16987
- --border-color-readonly: color-mix(
16988
- in srgb,
16989
- var(--border-color) 45%,
16990
- transparent
16991
- );
16992
- --background-color-readonly: var(--background-color);
16993
- --color-readonly: var(--color-dimmed);
16994
- /* Disabled */
16995
- --border-color-disabled: var(--border-color-readonly);
16996
- --background-color-disabled: color-mix(
16997
- in srgb,
16998
- var(--background-color) 95%,
16999
- grey
17000
- );
17001
- --color-disabled: color-mix(in srgb, var(--color) 95%, grey);
17002
- }
17003
- }
17004
-
17005
- .navi_input {
17006
- position: relative;
17007
- box-sizing: border-box;
17008
- width: fit-content;
17009
- height: fit-content;
17010
- flex-direction: inherit;
17011
- border-radius: inherit;
17012
- cursor: inherit;
17013
-
17014
- --x-outline-width: var(--outline-width);
17015
- --x-border-radius: var(--border-radius);
17016
- --x-border-width: var(--border-width);
17017
- --x-outer-width: calc(var(--x-border-width) + var(--x-outline-width));
17018
- --x-outline-color: var(--outline-color);
17019
- --x-border-color: var(--border-color);
17020
- --x-background-color: var(--background-color);
17021
- --x-color: var(--color);
17022
- --x-placeholder-color: var(--placeholder-color);
17023
- }
17024
-
17025
- .navi_input .navi_native_input {
17026
- box-sizing: border-box;
17027
- padding-top: var(--padding-top, var(--padding-y, var(--padding, 1px)));
17028
- padding-right: var(--padding-right, var(--padding-x, var(--padding, 2px)));
17029
- padding-bottom: var(
17030
- --padding-bottom,
17031
- var(--padding-y, var(--padding, 1px))
17032
- );
17033
- padding-left: var(--padding-left, var(--padding-x, var(--padding, 2px)));
17034
- color: var(--x-color);
17035
- background-color: var(--x-background-color);
17036
- border-width: var(--x-outer-width);
17037
- border-width: var(--x-outer-width);
17038
- border-style: solid;
17039
- border-color: transparent;
17040
- border-radius: var(--x-border-radius);
17041
- outline-width: var(--x-border-width);
17042
- outline-style: solid;
17043
- outline-color: var(--x-border-color);
17044
- outline-offset: calc(-1 * (var(--x-border-width)));
17045
- }
17046
- .navi_input .navi_native_input::placeholder {
17047
- color: var(--x-placeholder-color);
17048
- }
17049
- .navi_input .navi_native_input:-internal-autofill-selected {
17050
- /* Webkit is putting some nasty styles after automplete that look as follow */
17051
- /* input:-internal-autofill-selected { color: FieldText !important; } */
17052
- /* Fortunately we can override it as follow */
17053
- -webkit-text-fill-color: var(--x-color) !important;
17054
- }
17055
- /* Readonly */
17056
- .navi_input[data-readonly] {
17057
- --x-border-color: var(--border-color-readonly);
17058
- --x-background-color: var(--background-color-readonly);
17059
- --x-color: var(--color-readonly);
17060
- }
17061
- /* Focus */
17062
- .navi_input[data-focus] .navi_native_input,
17063
- .navi_input[data-focus-visible] .navi_native_input {
17064
- outline-width: var(--x-outer-width);
17065
- outline-offset: calc(-1 * var(--x-outer-width));
17066
- --x-border-color: var(--x-outline-color);
17067
- }
17068
- /* Disabled */
17069
- .navi_input[data-disabled] {
17070
- --x-border-color: var(--border-color-disabled);
17071
- --x-background-color: var(--background-color-disabled);
17072
- --x-color: var(--color-disabled);
17073
- }
17074
- /* Callout (info, warning, error) */
17075
- .navi_input[data-callout] {
17076
- --x-border-color: var(--callout-color);
17077
- }
17078
- `;
17079
- const InputTextual = props => {
17080
- const uiStateController = useUIStateController(props, "input");
17081
- const uiState = useUIState(uiStateController);
17082
- const input = renderActionableComponent(props, {
17083
- Basic: InputTextualBasic,
17084
- WithAction: InputTextualWithAction,
17085
- InsideForm: InputTextualInsideForm
17086
- });
17087
- return jsx(UIStateControllerContext.Provider, {
17088
- value: uiStateController,
17089
- children: jsx(UIStateContext.Provider, {
17090
- value: uiState,
17091
- children: input
17092
- })
17093
- });
17094
- };
17095
- const InputStyleCSSVars = {
17096
- "outlineWidth": "--outline-width",
17097
- "borderWidth": "--border-width",
17098
- "borderRadius": "--border-radius",
17099
- "paddingTop": "--padding-top",
17100
- "paddingRight": "--padding-right",
17101
- "paddingBottom": "--padding-bottom",
17102
- "paddingLeft": "--padding-left",
17103
- "backgroundColor": "--background-color",
17104
- "borderColor": "--border-color",
17105
- "color": "--color",
17106
- ":hover": {
17107
- backgroundColor: "--background-color-hover",
17108
- borderColor: "--border-color-hover",
17109
- color: "--color-hover"
17110
- },
17111
- ":active": {
17112
- borderColor: "--border-color-active"
17113
- },
17114
- ":read-only": {
17115
- backgroundColor: "--background-color-readonly",
17116
- borderColor: "--border-color-readonly",
17117
- color: "--color-readonly"
17118
- },
17119
- ":disabled": {
17120
- backgroundColor: "--background-color-disabled",
17121
- borderColor: "--border-color-disabled",
17122
- color: "--color-disabled"
17123
- }
17124
- };
17125
- const InputPseudoClasses = [":hover", ":active", ":focus", ":focus-visible", ":read-only", ":disabled", ":-navi-loading"];
17126
- const InputPseudoElements = ["::-navi-loader"];
17127
- const InputTextualBasic = props => {
17128
- const contextReadOnly = useContext(ReadOnlyContext);
17129
- const contextDisabled = useContext(DisabledContext);
17130
- const contextLoading = useContext(LoadingContext);
17131
- const contextLoadingElement = useContext(LoadingElementContext);
17132
- const reportReadOnlyOnLabel = useContext(ReportReadOnlyOnLabelContext);
17133
- const uiStateController = useContext(UIStateControllerContext);
17134
- const uiState = useContext(UIStateContext);
17135
- const {
17136
- type,
17137
- onInput,
17138
- readOnly,
17139
- disabled,
17140
- constraints = [],
17141
- loading,
17142
- autoFocus,
17143
- autoFocusVisible,
17144
- autoSelect,
17145
- ...rest
17146
- } = props;
17147
- const defaultRef = useRef();
17148
- const ref = props.ref || defaultRef;
17149
- const innerValue = type === "datetime-local" ? convertToLocalTimezone(uiState) : uiState;
17150
- const innerLoading = loading || contextLoading && contextLoadingElement === ref.current;
17151
- const innerReadOnly = readOnly || contextReadOnly || innerLoading || uiStateController.readOnly;
17152
- const innerDisabled = disabled || contextDisabled;
17153
- // infom any <label> parent of our readOnly state
17154
- reportReadOnlyOnLabel?.(innerReadOnly);
17155
- useAutoFocus(ref, autoFocus, {
17156
- autoFocusVisible,
17157
- autoSelect
17158
- });
17159
- useConstraints(ref, constraints);
17160
- const innerOnInput = useStableCallback(onInput);
17161
- const renderInput = inputProps => {
17162
- return jsx(Box, {
17163
- ...inputProps,
17164
- as: "input",
17165
- ref: ref,
17166
- type: type,
17167
- "data-value": uiState,
17168
- value: innerValue,
17169
- onInput: e => {
17170
- let inputValue;
17171
- if (type === "number") {
17172
- inputValue = e.target.valueAsNumber;
17173
- } else if (type === "datetime-local") {
17174
- inputValue = convertToUTCTimezone(e.target.value);
17175
- } else {
17176
- inputValue = e.target.value;
17177
- }
17178
- uiStateController.setUIState(inputValue, e);
17179
- innerOnInput?.(e);
17180
- },
17181
- onresetuistate: e => {
17182
- uiStateController.resetUIState(e);
17183
- },
17184
- onsetuistate: e => {
17185
- uiStateController.setUIState(e.detail.value, e);
17186
- }
17187
- // style management
17188
- ,
17189
- baseClassName: "navi_native_input"
17190
- });
17191
- };
17192
- const renderInputMemoized = useCallback(renderInput, [type, uiState, innerValue, innerOnInput]);
17193
- return jsxs(Box, {
17194
- as: "span",
17195
- box: true,
17196
- baseClassName: "navi_input",
17197
- styleCSSVars: InputStyleCSSVars,
17198
- pseudoStateSelector: ".navi_native_input",
17199
- visualSelector: ".navi_native_input",
17200
- basePseudoState: {
17201
- ":read-only": innerReadOnly,
17202
- ":disabled": innerDisabled,
17203
- ":-navi-loading": innerLoading
17204
- },
17205
- pseudoClasses: InputPseudoClasses,
17206
- pseudoElements: InputPseudoElements,
17207
- hasChildFunction: true,
17208
- ...rest,
17209
- children: [jsx(LoaderBackground, {
17210
- loading: innerLoading,
17211
- color: "var(--loader-color)",
17212
- inset: -1
17213
- }), renderInputMemoized]
16812
+ color: "var(--loader-color)",
16813
+ inset: -1
16814
+ }), renderInputMemoized]
17214
16815
  });
17215
16816
  };
17216
16817
  const InputTextualWithAction = props => {
@@ -18113,213 +17714,759 @@ const useRefArray = (items, keyFromItem) => {
18113
17714
  };
18114
17715
 
18115
17716
  installImportMetaCss(import.meta);import.meta.css = /* css */`
18116
- .navi_select[data-readonly] {
18117
- pointer-events: none;
17717
+ .navi_select[data-readonly] {
17718
+ pointer-events: none;
17719
+ }
17720
+ `;
17721
+ const Select = forwardRef((props, ref) => {
17722
+ const select = renderActionableComponent(props, ref);
17723
+ return select;
17724
+ });
17725
+ const SelectControlled = forwardRef((props, ref) => {
17726
+ const {
17727
+ name,
17728
+ value,
17729
+ loading,
17730
+ disabled,
17731
+ readOnly,
17732
+ children,
17733
+ ...rest
17734
+ } = props;
17735
+ const innerRef = useRef();
17736
+ useImperativeHandle(ref, () => innerRef.current);
17737
+ const selectElement = jsx("select", {
17738
+ className: "navi_select",
17739
+ ref: innerRef,
17740
+ "data-readonly": readOnly && !disabled ? "" : undefined,
17741
+ onKeyDown: e => {
17742
+ if (readOnly) {
17743
+ e.preventDefault();
17744
+ }
17745
+ },
17746
+ ...rest,
17747
+ children: children.map(child => {
17748
+ const {
17749
+ label,
17750
+ readOnly: childReadOnly,
17751
+ disabled: childDisabled,
17752
+ loading: childLoading,
17753
+ value: childValue,
17754
+ ...childRest
17755
+ } = child;
17756
+ return jsx("option", {
17757
+ name: name,
17758
+ value: childValue,
17759
+ selected: childValue === value,
17760
+ readOnly: readOnly || childReadOnly,
17761
+ disabled: disabled || childDisabled,
17762
+ loading: loading || childLoading,
17763
+ ...childRest,
17764
+ children: label
17765
+ }, childValue);
17766
+ })
17767
+ });
17768
+ return jsx(LoaderBackground, {
17769
+ loading: loading,
17770
+ color: "light-dark(#355fcc, #3b82f6)",
17771
+ inset: -1,
17772
+ children: selectElement
17773
+ });
17774
+ });
17775
+ forwardRef((props, ref) => {
17776
+ const {
17777
+ value: initialValue,
17778
+ id,
17779
+ children,
17780
+ ...rest
17781
+ } = props;
17782
+ const innerRef = useRef();
17783
+ useImperativeHandle(ref, () => innerRef.current);
17784
+ const [navState, setNavState] = useNavState(id);
17785
+ const valueAtStart = navState === undefined ? initialValue : navState;
17786
+ const [value, setValue] = useState(valueAtStart);
17787
+ useEffect(() => {
17788
+ setNavState(value);
17789
+ }, [value]);
17790
+ return jsx(SelectControlled, {
17791
+ ref: innerRef,
17792
+ value: value,
17793
+ onChange: event => {
17794
+ const select = event.target;
17795
+ const selectedValue = select.value;
17796
+ setValue(selectedValue);
17797
+ },
17798
+ ...rest,
17799
+ children: children
17800
+ });
17801
+ });
17802
+ forwardRef((props, ref) => {
17803
+ const {
17804
+ id,
17805
+ name,
17806
+ value: externalValue,
17807
+ valueSignal,
17808
+ action,
17809
+ children,
17810
+ onCancel,
17811
+ onActionPrevented,
17812
+ onActionStart,
17813
+ onActionAbort,
17814
+ onActionError,
17815
+ onActionEnd,
17816
+ actionErrorEffect,
17817
+ ...rest
17818
+ } = props;
17819
+ const innerRef = useRef();
17820
+ useImperativeHandle(ref, () => innerRef.current);
17821
+ const [navState, setNavState, resetNavState] = useNavState(id);
17822
+ const [boundAction, value, setValue, initialValue] = useActionBoundToOneParam(action, name);
17823
+ const {
17824
+ loading: actionLoading
17825
+ } = useActionStatus(boundAction);
17826
+ const executeAction = useExecuteAction(innerRef, {
17827
+ errorEffect: actionErrorEffect
17828
+ });
17829
+ useEffect(() => {
17830
+ setNavState(value);
17831
+ }, [value]);
17832
+ const actionRequesterRef = useRef(null);
17833
+ useActionEvents(innerRef, {
17834
+ onCancel: (e, reason) => {
17835
+ resetNavState();
17836
+ setValue(initialValue);
17837
+ onCancel?.(e, reason);
17838
+ },
17839
+ onPrevented: onActionPrevented,
17840
+ onAction: actionEvent => {
17841
+ actionRequesterRef.current = actionEvent.detail.requester;
17842
+ executeAction(actionEvent);
17843
+ },
17844
+ onStart: onActionStart,
17845
+ onAbort: e => {
17846
+ setValue(initialValue);
17847
+ onActionAbort?.(e);
17848
+ },
17849
+ onError: error => {
17850
+ setValue(initialValue);
17851
+ onActionError?.(error);
17852
+ },
17853
+ onEnd: () => {
17854
+ resetNavState();
17855
+ onActionEnd?.();
17856
+ }
17857
+ });
17858
+ const childRefArray = useRefArray(children, child => child.value);
17859
+ return jsx(SelectControlled, {
17860
+ ref: innerRef,
17861
+ name: name,
17862
+ value: value,
17863
+ "data-action": boundAction,
17864
+ onChange: event => {
17865
+ const select = event.target;
17866
+ const selectedValue = select.value;
17867
+ setValue(selectedValue);
17868
+ const radioListContainer = innerRef.current;
17869
+ const optionSelected = select.querySelector(`option[value="${selectedValue}"]`);
17870
+ requestAction(radioListContainer, boundAction, {
17871
+ event,
17872
+ requester: optionSelected
17873
+ });
17874
+ },
17875
+ ...rest,
17876
+ children: children.map((child, i) => {
17877
+ const childRef = childRefArray[i];
17878
+ return {
17879
+ ...child,
17880
+ ref: childRef,
17881
+ loading: child.loading || actionLoading && actionRequesterRef.current === childRef.current,
17882
+ readOnly: child.readOnly || actionLoading
17883
+ };
17884
+ })
17885
+ });
17886
+ });
17887
+ forwardRef((props, ref) => {
17888
+ const {
17889
+ id,
17890
+ name,
17891
+ value: externalValue,
17892
+ children,
17893
+ ...rest
17894
+ } = props;
17895
+ const innerRef = useRef();
17896
+ useImperativeHandle(ref, () => innerRef.current);
17897
+ const [navState, setNavState] = useNavState(id);
17898
+ const [value, setValue, initialValue] = [name, externalValue, navState];
17899
+ useEffect(() => {
17900
+ setNavState(value);
17901
+ }, [value]);
17902
+ useFormEvents(innerRef, {
17903
+ onFormReset: () => {
17904
+ setValue(undefined);
17905
+ },
17906
+ onFormActionAbort: () => {
17907
+ setValue(initialValue);
17908
+ },
17909
+ onFormActionError: () => {
17910
+ setValue(initialValue);
17911
+ }
17912
+ });
17913
+ return jsx(SelectControlled, {
17914
+ ref: innerRef,
17915
+ name: name,
17916
+ value: value,
17917
+ onChange: event => {
17918
+ const select = event.target;
17919
+ const selectedValue = select.checked;
17920
+ setValue(selectedValue);
17921
+ },
17922
+ ...rest,
17923
+ children: children
17924
+ });
17925
+ });
17926
+
17927
+ const createUniqueValueConstraint = (
17928
+ // the set might be incomplete (the front usually don't have the full copy of all the items from the backend)
17929
+ // but this is already nice to help user with what we know
17930
+ // it's also possible that front is unsync with backend, preventing user to choose a value
17931
+ // that is actually free.
17932
+ // But this is unlikely to happen and user could reload the page to be able to choose that name
17933
+ // that suddenly became available
17934
+ existingValueSet,
17935
+ message = `"{value}" already exists. Please choose another value.`,
17936
+ ) => {
17937
+ return {
17938
+ name: "unique_value",
17939
+ check: (input) => {
17940
+ const inputValue = input.value;
17941
+ const hasConflict = existingValueSet.has(inputValue);
17942
+ // console.log({
17943
+ // inputValue,
17944
+ // names: Array.from(otherNameSet.values()),
17945
+ // hasConflict,
17946
+ // });
17947
+ if (hasConflict) {
17948
+ return message.replace("{value}", inputValue);
17949
+ }
17950
+ return "";
17951
+ },
17952
+ };
17953
+ };
17954
+
17955
+ const SINGLE_SPACE_CONSTRAINT = {
17956
+ name: "single_space",
17957
+ check: (input) => {
17958
+ const inputValue = input.value;
17959
+ const hasLeadingSpace = inputValue.startsWith(" ");
17960
+ const hasTrailingSpace = inputValue.endsWith(" ");
17961
+ const hasDoubleSpace = inputValue.includes(" ");
17962
+ if (hasLeadingSpace || hasDoubleSpace || hasTrailingSpace) {
17963
+ return "Spaces at the beginning, end, or consecutive spaces are not allowed";
17964
+ }
17965
+ return "";
17966
+ },
17967
+ };
17968
+
17969
+ installImportMetaCss(import.meta);import.meta.css = /* css */`
17970
+ .action_error {
17971
+ padding: 20px;
17972
+ background: #fdd;
17973
+ border: 1px solid red;
17974
+ margin-top: 0;
17975
+ margin-bottom: 20px;
17976
+ }
17977
+ `;
17978
+ const renderIdleDefault = () => null;
17979
+ const renderLoadingDefault = () => null;
17980
+ const renderAbortedDefault = () => null;
17981
+ const renderErrorDefault = error => {
17982
+ let routeErrorText = error && error.message ? error.message : error;
17983
+ return jsxs("p", {
17984
+ className: "action_error",
17985
+ children: ["An error occured: ", routeErrorText]
17986
+ });
17987
+ };
17988
+ const renderCompletedDefault = () => null;
17989
+ const ActionRenderer = ({
17990
+ action,
17991
+ children,
17992
+ disabled
17993
+ }) => {
17994
+ const {
17995
+ idle: renderIdle = renderIdleDefault,
17996
+ loading: renderLoading = renderLoadingDefault,
17997
+ aborted: renderAborted = renderAbortedDefault,
17998
+ error: renderError = renderErrorDefault,
17999
+ completed: renderCompleted,
18000
+ always: renderAlways
18001
+ } = typeof children === "function" ? {
18002
+ completed: children
18003
+ } : children || {};
18004
+ if (disabled) {
18005
+ return null;
18006
+ }
18007
+ if (action === undefined) {
18008
+ throw new Error("ActionRenderer requires an action to render, but none was provided.");
18009
+ }
18010
+ const {
18011
+ idle,
18012
+ loading,
18013
+ aborted,
18014
+ error,
18015
+ data
18016
+ } = useActionStatus(action);
18017
+ const UIRenderedPromise = useUIRenderedPromise(action);
18018
+ const [errorBoundary, resetErrorBoundary] = useErrorBoundary();
18019
+
18020
+ // Mark this action as bound to UI components (has renderers)
18021
+ // This tells the action system that errors should be caught and stored
18022
+ // in the action's error state rather than bubbling up
18023
+ useLayoutEffect(() => {
18024
+ if (action) {
18025
+ const {
18026
+ ui
18027
+ } = getActionPrivateProperties(action);
18028
+ ui.hasRenderers = true;
18029
+ }
18030
+ }, [action]);
18031
+ useLayoutEffect(() => {
18032
+ resetErrorBoundary();
18033
+ }, [action, loading, idle, resetErrorBoundary]);
18034
+ useLayoutEffect(() => {
18035
+ UIRenderedPromise.resolve();
18036
+ return () => {
18037
+ actionUIRenderedPromiseWeakMap.delete(action);
18038
+ };
18039
+ }, [action]);
18040
+
18041
+ // If renderAlways is provided, it wins and handles all rendering
18042
+ if (renderAlways) {
18043
+ return renderAlways({
18044
+ idle,
18045
+ loading,
18046
+ aborted,
18047
+ error,
18048
+ data
18049
+ });
18050
+ }
18051
+ if (idle) {
18052
+ return renderIdle(action);
18053
+ }
18054
+ if (errorBoundary) {
18055
+ return renderError(errorBoundary, "ui_error", action);
18056
+ }
18057
+ if (error) {
18058
+ return renderError(error, "action_error", action);
18059
+ }
18060
+ if (aborted) {
18061
+ return renderAborted(action);
18062
+ }
18063
+ let renderCompletedSafe;
18064
+ if (renderCompleted) {
18065
+ renderCompletedSafe = renderCompleted;
18066
+ } else {
18067
+ const {
18068
+ ui
18069
+ } = getActionPrivateProperties(action);
18070
+ if (ui.renderCompleted) {
18071
+ renderCompletedSafe = ui.renderCompleted;
18072
+ } else {
18073
+ renderCompletedSafe = renderCompletedDefault;
18074
+ }
18075
+ }
18076
+ if (loading) {
18077
+ if (action.canDisplayOldData && data !== undefined) {
18078
+ return renderCompletedSafe(data, action);
18079
+ }
18080
+ return renderLoading(action);
18081
+ }
18082
+ return renderCompletedSafe(data, action);
18083
+ };
18084
+ const defaultPromise = Promise.resolve();
18085
+ defaultPromise.resolve = () => {};
18086
+ const actionUIRenderedPromiseWeakMap = new WeakMap();
18087
+ const useUIRenderedPromise = action => {
18088
+ if (!action) {
18089
+ return defaultPromise;
18090
+ }
18091
+ const actionUIRenderedPromise = actionUIRenderedPromiseWeakMap.get(action);
18092
+ if (actionUIRenderedPromise) {
18093
+ return actionUIRenderedPromise;
18094
+ }
18095
+ let resolve;
18096
+ const promise = new Promise(res => {
18097
+ resolve = res;
18098
+ });
18099
+ promise.resolve = resolve;
18100
+ actionUIRenderedPromiseWeakMap.set(action, promise);
18101
+ return promise;
18102
+ };
18103
+
18104
+ const useFocusGroup = (
18105
+ elementRef,
18106
+ { enabled = true, direction, skipTab, loop, name } = {},
18107
+ ) => {
18108
+ useLayoutEffect(() => {
18109
+ if (!enabled) {
18110
+ return null;
18111
+ }
18112
+ const focusGroup = initFocusGroup(elementRef.current, {
18113
+ direction,
18114
+ skipTab,
18115
+ loop,
18116
+ name,
18117
+ });
18118
+ return focusGroup.cleanup;
18119
+ }, [direction, skipTab, loop, name]);
18120
+ };
18121
+
18122
+ installImportMetaCss(import.meta);const rightArrowPath = "M680-480L360-160l-80-80 240-240-240-240 80-80 320 320z";
18123
+ const downArrowPath = "M480-280L160-600l80-80 240 240 240-240 80 80-320 320z";
18124
+ import.meta.css = /* css */`
18125
+ .summary_marker {
18126
+ width: 1em;
18127
+ height: 1em;
18128
+ line-height: 1em;
18129
+ }
18130
+ .summary_marker_svg .arrow {
18131
+ animation-duration: 0.3s;
18132
+ animation-fill-mode: forwards;
18133
+ animation-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1);
18134
+ }
18135
+ .summary_marker_svg .arrow[data-animation-target="down"] {
18136
+ animation-name: morph-to-down;
18137
+ }
18138
+ @keyframes morph-to-down {
18139
+ from {
18140
+ d: path("${rightArrowPath}");
18141
+ }
18142
+ to {
18143
+ d: path("${downArrowPath}");
18144
+ }
18145
+ }
18146
+ .summary_marker_svg .arrow[data-animation-target="right"] {
18147
+ animation-name: morph-to-right;
18148
+ }
18149
+ @keyframes morph-to-right {
18150
+ from {
18151
+ d: path("${downArrowPath}");
18152
+ }
18153
+ to {
18154
+ d: path("${rightArrowPath}");
18155
+ }
18156
+ }
18157
+
18158
+ .summary_marker_svg .foreground_circle {
18159
+ stroke-dasharray: 503 1507; /* ~25% of circle perimeter */
18160
+ stroke-dashoffset: 0;
18161
+ animation: progress-around-circle 1.5s linear infinite;
18162
+ }
18163
+ @keyframes progress-around-circle {
18164
+ 0% {
18165
+ stroke-dashoffset: 0;
18166
+ }
18167
+ 100% {
18168
+ stroke-dashoffset: -2010;
18169
+ }
18170
+ }
18171
+
18172
+ /* fading and scaling */
18173
+ .summary_marker_svg .arrow {
18174
+ transition: opacity 0.3s ease-in-out;
18175
+ opacity: 1;
18176
+ }
18177
+ .summary_marker_svg .loading_container {
18178
+ transition: transform 0.3s linear;
18179
+ transform: scale(0.3);
18180
+ }
18181
+ .summary_marker_svg .background_circle,
18182
+ .summary_marker_svg .foreground_circle {
18183
+ transition: opacity 0.3s ease-in-out;
18184
+ opacity: 0;
18185
+ }
18186
+ .summary_marker_svg[data-loading] .arrow {
18187
+ opacity: 0;
18188
+ }
18189
+ .summary_marker_svg[data-loading] .loading_container {
18190
+ transform: scale(1);
18191
+ }
18192
+ .summary_marker_svg[data-loading] .background_circle {
18193
+ opacity: 0.2;
18194
+ }
18195
+ .summary_marker_svg[data-loading] .foreground_circle {
18196
+ opacity: 1;
18197
+ }
18198
+ `;
18199
+ const SummaryMarker = ({
18200
+ open,
18201
+ loading
18202
+ }) => {
18203
+ const showLoading = useDebounceTrue(loading, 300);
18204
+ const mountedRef = useRef(false);
18205
+ const prevOpenRef = useRef(open);
18206
+ useLayoutEffect(() => {
18207
+ mountedRef.current = true;
18208
+ return () => {
18209
+ mountedRef.current = false;
18210
+ };
18211
+ }, []);
18212
+ const shouldAnimate = mountedRef.current && prevOpenRef.current !== open;
18213
+ prevOpenRef.current = open;
18214
+ return jsx("span", {
18215
+ className: "summary_marker",
18216
+ children: jsxs("svg", {
18217
+ className: "summary_marker_svg",
18218
+ viewBox: "0 -960 960 960",
18219
+ xmlns: "http://www.w3.org/2000/svg",
18220
+ "data-loading": open ? showLoading || undefined : undefined,
18221
+ children: [jsxs("g", {
18222
+ className: "loading_container",
18223
+ "transform-origin": "480px -480px",
18224
+ children: [jsx("circle", {
18225
+ className: "background_circle",
18226
+ cx: "480",
18227
+ cy: "-480",
18228
+ r: "320",
18229
+ stroke: "currentColor",
18230
+ fill: "none",
18231
+ strokeWidth: "60",
18232
+ opacity: "0.2"
18233
+ }), jsx("circle", {
18234
+ className: "foreground_circle",
18235
+ cx: "480",
18236
+ cy: "-480",
18237
+ r: "320",
18238
+ stroke: "currentColor",
18239
+ fill: "none",
18240
+ strokeWidth: "60",
18241
+ strokeLinecap: "round",
18242
+ strokeDasharray: "503 1507"
18243
+ })]
18244
+ }), jsx("g", {
18245
+ className: "arrow_container",
18246
+ "transform-origin": "480px -480px",
18247
+ children: jsx("path", {
18248
+ className: "arrow",
18249
+ fill: "currentColor",
18250
+ "data-animation-target": shouldAnimate ? open ? "down" : "right" : undefined,
18251
+ d: open ? downArrowPath : rightArrowPath
18252
+ })
18253
+ })]
18254
+ })
18255
+ });
18256
+ };
18257
+
18258
+ installImportMetaCss(import.meta);import.meta.css = /* css */`
18259
+ .navi_details {
18260
+ position: relative;
18261
+ z-index: 1;
18262
+ display: flex;
18263
+ flex-shrink: 0;
18264
+ flex-direction: column;
18265
+ }
18266
+
18267
+ .navi_details > summary {
18268
+ display: flex;
18269
+ flex-shrink: 0;
18270
+ flex-direction: column;
18271
+ cursor: pointer;
18272
+ user-select: none;
18273
+ }
18274
+ .summary_body {
18275
+ display: flex;
18276
+ width: 100%;
18277
+ flex-direction: row;
18278
+ align-items: center;
18279
+ gap: 0.2em;
18280
+ }
18281
+ .summary_label {
18282
+ display: flex;
18283
+ padding-right: 10px;
18284
+ flex: 1;
18285
+ align-items: center;
18286
+ gap: 0.2em;
18287
+ }
18288
+
18289
+ .navi_details > summary:focus {
18290
+ z-index: 1;
18118
18291
  }
18119
18292
  `;
18120
- const Select = forwardRef((props, ref) => {
18121
- const select = renderActionableComponent(props, ref);
18122
- return select;
18123
- });
18124
- const SelectControlled = forwardRef((props, ref) => {
18125
- const {
18126
- name,
18127
- value,
18128
- loading,
18129
- disabled,
18130
- readOnly,
18131
- children,
18132
- ...rest
18133
- } = props;
18134
- const innerRef = useRef();
18135
- useImperativeHandle(ref, () => innerRef.current);
18136
- const selectElement = jsx("select", {
18137
- className: "navi_select",
18138
- ref: innerRef,
18139
- "data-readonly": readOnly && !disabled ? "" : undefined,
18140
- onKeyDown: e => {
18141
- if (readOnly) {
18142
- e.preventDefault();
18143
- }
18144
- },
18145
- ...rest,
18146
- children: children.map(child => {
18147
- const {
18148
- label,
18149
- readOnly: childReadOnly,
18150
- disabled: childDisabled,
18151
- loading: childLoading,
18152
- value: childValue,
18153
- ...childRest
18154
- } = child;
18155
- return jsx("option", {
18156
- name: name,
18157
- value: childValue,
18158
- selected: childValue === value,
18159
- readOnly: readOnly || childReadOnly,
18160
- disabled: disabled || childDisabled,
18161
- loading: loading || childLoading,
18162
- ...childRest,
18163
- children: label
18164
- }, childValue);
18165
- })
18166
- });
18167
- return jsx(LoaderBackground, {
18168
- loading: loading,
18169
- color: "light-dark(#355fcc, #3b82f6)",
18170
- inset: -1,
18171
- children: selectElement
18172
- });
18293
+ const Details = forwardRef((props, ref) => {
18294
+ return renderActionableComponent(props, ref);
18173
18295
  });
18174
- forwardRef((props, ref) => {
18296
+ const DetailsBasic = forwardRef((props, ref) => {
18175
18297
  const {
18176
- value: initialValue,
18177
18298
  id,
18299
+ label = "Summary",
18300
+ open,
18301
+ loading,
18302
+ className,
18303
+ focusGroup,
18304
+ focusGroupDirection,
18305
+ arrowKeyShortcuts = true,
18306
+ openKeyShortcut = "ArrowRight",
18307
+ closeKeyShortcut = "ArrowLeft",
18308
+ onToggle,
18178
18309
  children,
18179
18310
  ...rest
18180
18311
  } = props;
18181
18312
  const innerRef = useRef();
18182
18313
  useImperativeHandle(ref, () => innerRef.current);
18183
18314
  const [navState, setNavState] = useNavState(id);
18184
- const valueAtStart = navState === undefined ? initialValue : navState;
18185
- const [value, setValue] = useState(valueAtStart);
18186
- useEffect(() => {
18187
- setNavState(value);
18188
- }, [value]);
18189
- return jsx(SelectControlled, {
18190
- ref: innerRef,
18191
- value: value,
18192
- onChange: event => {
18193
- const select = event.target;
18194
- const selectedValue = select.value;
18195
- setValue(selectedValue);
18196
- },
18197
- ...rest,
18198
- children: children
18199
- });
18200
- });
18201
- forwardRef((props, ref) => {
18202
- const {
18203
- id,
18204
- name,
18205
- value: externalValue,
18206
- valueSignal,
18207
- action,
18208
- children,
18209
- onCancel,
18210
- onActionPrevented,
18211
- onActionStart,
18212
- onActionAbort,
18213
- onActionError,
18214
- onActionEnd,
18215
- actionErrorEffect,
18216
- ...rest
18217
- } = props;
18218
- const innerRef = useRef();
18219
- useImperativeHandle(ref, () => innerRef.current);
18220
- const [navState, setNavState, resetNavState] = useNavState(id);
18221
- const [boundAction, value, setValue, initialValue] = useActionBoundToOneParam(action, name);
18222
- const {
18223
- loading: actionLoading
18224
- } = useActionStatus(boundAction);
18225
- const executeAction = useExecuteAction(innerRef, {
18226
- errorEffect: actionErrorEffect
18227
- });
18228
- useEffect(() => {
18229
- setNavState(value);
18230
- }, [value]);
18231
- const actionRequesterRef = useRef(null);
18232
- useActionEvents(innerRef, {
18233
- onCancel: (e, reason) => {
18234
- resetNavState();
18235
- setValue(initialValue);
18236
- onCancel?.(e, reason);
18237
- },
18238
- onPrevented: onActionPrevented,
18239
- onAction: actionEvent => {
18240
- actionRequesterRef.current = actionEvent.detail.requester;
18241
- executeAction(actionEvent);
18242
- },
18243
- onStart: onActionStart,
18244
- onAbort: e => {
18245
- setValue(initialValue);
18246
- onActionAbort?.(e);
18247
- },
18248
- onError: error => {
18249
- setValue(initialValue);
18250
- onActionError?.(error);
18251
- },
18252
- onEnd: () => {
18253
- resetNavState();
18254
- onActionEnd?.();
18255
- }
18315
+ const [innerOpen, innerOpenSetter] = useState(open || navState);
18316
+ useFocusGroup(innerRef, {
18317
+ enabled: focusGroup,
18318
+ name: typeof focusGroup === "string" ? focusGroup : undefined,
18319
+ direction: focusGroupDirection
18256
18320
  });
18257
- const childRefArray = useRefArray(children, child => child.value);
18258
- return jsx(SelectControlled, {
18259
- ref: innerRef,
18260
- name: name,
18261
- value: value,
18262
- "data-action": boundAction,
18263
- onChange: event => {
18264
- const select = event.target;
18265
- const selectedValue = select.value;
18266
- setValue(selectedValue);
18267
- const radioListContainer = innerRef.current;
18268
- const optionSelected = select.querySelector(`option[value="${selectedValue}"]`);
18269
- requestAction(radioListContainer, boundAction, {
18270
- event,
18271
- requester: optionSelected
18321
+
18322
+ /**
18323
+ * Browser will dispatch "toggle" event even if we set open={true}
18324
+ * When rendering the component for the first time
18325
+ * We have to ensure the initial "toggle" event is ignored.
18326
+ *
18327
+ * If we don't do that code will think the details has changed and run logic accordingly
18328
+ * For example it will try to navigate to the current url while we are already there
18329
+ *
18330
+ * See:
18331
+ * - https://techblog.thescore.com/2024/10/08/why-we-decided-to-change-how-the-details-element-works/
18332
+ * - https://github.com/whatwg/html/issues/4500
18333
+ * - https://stackoverflow.com/questions/58942600/react-html-details-toggles-uncontrollably-when-starts-open
18334
+ *
18335
+ */
18336
+
18337
+ const summaryRef = useRef(null);
18338
+ useKeyboardShortcuts(innerRef, [{
18339
+ key: openKeyShortcut,
18340
+ enabled: arrowKeyShortcuts,
18341
+ when: e => document.activeElement === summaryRef.current &&
18342
+ // avoid handling openKeyShortcut twice when keydown occurs inside nested details
18343
+ !e.defaultPrevented,
18344
+ action: e => {
18345
+ const details = innerRef.current;
18346
+ if (!details.open) {
18347
+ e.preventDefault();
18348
+ details.open = true;
18349
+ return;
18350
+ }
18351
+ const summary = summaryRef.current;
18352
+ const firstFocusableElementInDetails = findAfter(summary, elementIsFocusable, {
18353
+ root: details
18272
18354
  });
18355
+ if (!firstFocusableElementInDetails) {
18356
+ return;
18357
+ }
18358
+ e.preventDefault();
18359
+ firstFocusableElementInDetails.focus();
18360
+ }
18361
+ }, {
18362
+ key: closeKeyShortcut,
18363
+ enabled: arrowKeyShortcuts,
18364
+ when: () => {
18365
+ const details = innerRef.current;
18366
+ return details.open;
18273
18367
  },
18368
+ action: e => {
18369
+ const details = innerRef.current;
18370
+ const summary = summaryRef.current;
18371
+ if (document.activeElement === summary) {
18372
+ e.preventDefault();
18373
+ summary.focus();
18374
+ details.open = false;
18375
+ } else {
18376
+ e.preventDefault();
18377
+ summary.focus();
18378
+ }
18379
+ }
18380
+ }]);
18381
+ const mountedRef = useRef(false);
18382
+ useEffect(() => {
18383
+ mountedRef.current = true;
18384
+ }, []);
18385
+ return jsxs("details", {
18274
18386
  ...rest,
18275
- children: children.map((child, i) => {
18276
- const childRef = childRefArray[i];
18277
- return {
18278
- ...child,
18279
- ref: childRef,
18280
- loading: child.loading || actionLoading && actionRequesterRef.current === childRef.current,
18281
- readOnly: child.readOnly || actionLoading
18282
- };
18283
- })
18387
+ ref: innerRef,
18388
+ id: id,
18389
+ className: ["navi_details", ...(className ? className.split(" ") : [])].join(" "),
18390
+ onToggle: e => {
18391
+ const isOpen = e.newState === "open";
18392
+ if (mountedRef.current) {
18393
+ if (isOpen) {
18394
+ innerOpenSetter(true);
18395
+ setNavState(true);
18396
+ } else {
18397
+ innerOpenSetter(false);
18398
+ setNavState(undefined);
18399
+ }
18400
+ }
18401
+ onToggle?.(e);
18402
+ },
18403
+ open: innerOpen,
18404
+ children: [jsx("summary", {
18405
+ ref: summaryRef,
18406
+ children: jsxs("div", {
18407
+ className: "summary_body",
18408
+ children: [jsx(SummaryMarker, {
18409
+ open: innerOpen,
18410
+ loading: loading
18411
+ }), jsx("div", {
18412
+ className: "summary_label",
18413
+ children: label
18414
+ })]
18415
+ })
18416
+ }), children]
18284
18417
  });
18285
18418
  });
18286
18419
  forwardRef((props, ref) => {
18287
18420
  const {
18288
- id,
18289
- name,
18290
- value: externalValue,
18421
+ action,
18422
+ loading,
18423
+ onToggle,
18424
+ onActionPrevented,
18425
+ onActionStart,
18426
+ onActionError,
18427
+ onActionEnd,
18291
18428
  children,
18292
18429
  ...rest
18293
18430
  } = props;
18294
18431
  const innerRef = useRef();
18295
18432
  useImperativeHandle(ref, () => innerRef.current);
18296
- const [navState, setNavState] = useNavState(id);
18297
- const [value, setValue, initialValue] = [name, externalValue, navState];
18298
- useEffect(() => {
18299
- setNavState(value);
18300
- }, [value]);
18301
- useFormEvents(innerRef, {
18302
- onFormReset: () => {
18303
- setValue(undefined);
18304
- },
18305
- onFormActionAbort: () => {
18306
- setValue(initialValue);
18433
+ const effectiveAction = useAction(action);
18434
+ const {
18435
+ loading: actionLoading
18436
+ } = useActionStatus(effectiveAction);
18437
+ const executeAction = useExecuteAction(innerRef, {
18438
+ // the error will be displayed by actionRenderer inside <details>
18439
+ errorEffect: "none"
18440
+ });
18441
+ useActionEvents(innerRef, {
18442
+ onPrevented: onActionPrevented,
18443
+ onAction: e => {
18444
+ executeAction(e);
18307
18445
  },
18308
- onFormActionError: () => {
18309
- setValue(initialValue);
18310
- }
18446
+ onStart: onActionStart,
18447
+ onError: onActionError,
18448
+ onEnd: onActionEnd
18311
18449
  });
18312
- return jsx(SelectControlled, {
18450
+ return jsx(DetailsBasic, {
18451
+ ...rest,
18313
18452
  ref: innerRef,
18314
- name: name,
18315
- value: value,
18316
- onChange: event => {
18317
- const select = event.target;
18318
- const selectedValue = select.checked;
18319
- setValue(selectedValue);
18453
+ loading: loading || actionLoading,
18454
+ onToggle: toggleEvent => {
18455
+ const isOpen = toggleEvent.newState === "open";
18456
+ if (isOpen) {
18457
+ requestAction(toggleEvent.target, effectiveAction, {
18458
+ event: toggleEvent,
18459
+ method: "run"
18460
+ });
18461
+ } else {
18462
+ effectiveAction.abort();
18463
+ }
18464
+ onToggle?.(toggleEvent);
18320
18465
  },
18321
- ...rest,
18322
- children: children
18466
+ children: jsx(ActionRenderer, {
18467
+ action: effectiveAction,
18468
+ children: children
18469
+ })
18323
18470
  });
18324
18471
  });
18325
18472
 
@@ -21687,116 +21834,6 @@ const useCellsAndColumns = (cells, columns) => {
21687
21834
  };
21688
21835
  };
21689
21836
 
21690
- installImportMetaCss(import.meta);import.meta.css = /* css */`
21691
- .navi_tablist {
21692
- display: flex;
21693
- overflow-x: auto;
21694
- overflow-y: hidden;
21695
- justify-content: space-between;
21696
- }
21697
-
21698
- .navi_tablist > ul {
21699
- align-items: center;
21700
- display: flex;
21701
- gap: 0.5rem;
21702
- list-style: none;
21703
- margin: 0;
21704
- padding: 0;
21705
- }
21706
-
21707
- .navi_tablist > ul > li {
21708
- display: inline-flex;
21709
- position: relative;
21710
- }
21711
-
21712
- .navi_tab {
21713
- white-space: nowrap;
21714
- display: flex;
21715
- flex-direction: column;
21716
- }
21717
-
21718
- .navi_tab_content {
21719
- transition: background 0.12s ease-out;
21720
- border-radius: 6px;
21721
- text-decoration: none;
21722
- line-height: 30px;
21723
- display: flex;
21724
- padding: 0 0.5rem;
21725
- }
21726
-
21727
- .navi_tab:hover .navi_tab_content {
21728
- background: #dae0e7;
21729
- color: #010409;
21730
- }
21731
-
21732
- .navi_tab .active_marker {
21733
- display: flex;
21734
- background: transparent;
21735
- border-radius: 0.1px;
21736
- width: 100%;
21737
- z-index: 1;
21738
- height: 2px;
21739
- margin-top: 5px;
21740
- }
21741
-
21742
- /* Hidden bold clone to reserve space for bold width without affecting height */
21743
- .navi_tab_content_bold_clone {
21744
- font-weight: 600; /* force bold to compute max width */
21745
- visibility: hidden; /* not visible */
21746
- display: block; /* in-flow so it contributes to width */
21747
- height: 0; /* zero height so it doesn't change layout height */
21748
- overflow: hidden; /* avoid any accidental height */
21749
- pointer-events: none; /* inert */
21750
- }
21751
-
21752
- .navi_tab[aria-selected="true"] .active_marker {
21753
- background: rgb(205, 52, 37);
21754
- }
21755
-
21756
- .navi_tab[aria-selected="true"] .navi_tab_content {
21757
- font-weight: 600;
21758
- }
21759
- `;
21760
- const TabList = ({
21761
- children,
21762
- ...props
21763
- }) => {
21764
- return jsx("nav", {
21765
- className: "navi_tablist",
21766
- role: "tablist",
21767
- ...props,
21768
- children: jsx("ul", {
21769
- children: children.map(child => {
21770
- return jsx("li", {
21771
- children: child
21772
- }, child.props.key);
21773
- })
21774
- })
21775
- });
21776
- };
21777
- const Tab = ({
21778
- children,
21779
- selected,
21780
- ...props
21781
- }) => {
21782
- return jsxs("div", {
21783
- className: "navi_tab",
21784
- role: "tab",
21785
- "aria-selected": selected ? "true" : "false",
21786
- ...props,
21787
- children: [jsx("div", {
21788
- className: "navi_tab_content",
21789
- children: children
21790
- }), jsx("div", {
21791
- className: "navi_tab_content_bold_clone",
21792
- "aria-hidden": "true",
21793
- children: children
21794
- }), jsx("span", {
21795
- className: "active_marker"
21796
- })]
21797
- });
21798
- };
21799
-
21800
21837
  /**
21801
21838
  * Creates a signal that stays synchronized with an external value,
21802
21839
  * only updating the signal when the value actually changes.
@@ -22366,48 +22403,6 @@ const ViewportLayout = props => {
22366
22403
  });
22367
22404
  };
22368
22405
 
22369
- const createUniqueValueConstraint = (
22370
- // the set might be incomplete (the front usually don't have the full copy of all the items from the backend)
22371
- // but this is already nice to help user with what we know
22372
- // it's also possible that front is unsync with backend, preventing user to choose a value
22373
- // that is actually free.
22374
- // But this is unlikely to happen and user could reload the page to be able to choose that name
22375
- // that suddenly became available
22376
- existingValueSet,
22377
- message = `"{value}" already exists. Please choose another value.`,
22378
- ) => {
22379
- return {
22380
- name: "unique_value",
22381
- check: (input) => {
22382
- const inputValue = input.value;
22383
- const hasConflict = existingValueSet.has(inputValue);
22384
- // console.log({
22385
- // inputValue,
22386
- // names: Array.from(otherNameSet.values()),
22387
- // hasConflict,
22388
- // });
22389
- if (hasConflict) {
22390
- return message.replace("{value}", inputValue);
22391
- }
22392
- return "";
22393
- },
22394
- };
22395
- };
22396
-
22397
- const SINGLE_SPACE_CONSTRAINT = {
22398
- name: "single_space",
22399
- check: (input) => {
22400
- const inputValue = input.value;
22401
- const hasLeadingSpace = inputValue.startsWith(" ");
22402
- const hasTrailingSpace = inputValue.endsWith(" ");
22403
- const hasDoubleSpace = inputValue.includes(" ");
22404
- if (hasLeadingSpace || hasDoubleSpace || hasTrailingSpace) {
22405
- return "Spaces at the beginning, end, or consecutive spaces are not allowed";
22406
- }
22407
- return "";
22408
- },
22409
- };
22410
-
22411
22406
  /*
22412
22407
  * - Usage
22413
22408
  * useEffect(() => {