@jsenv/navi 0.25.1 → 0.25.2
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.
- package/dist/jsenv_navi.js +1264 -1031
- package/dist/jsenv_navi.js.map +75 -52
- package/package.json +1 -1
package/dist/jsenv_navi.js
CHANGED
|
@@ -3,7 +3,7 @@ import { isValidElement, h, createContext, options, toChildArray, render, create
|
|
|
3
3
|
import { useErrorBoundary, useLayoutEffect, useEffect, useMemo, useRef, useState, useCallback, useContext, useImperativeHandle, useId } from "preact/hooks";
|
|
4
4
|
import { jsxs, jsx, Fragment } from "preact/jsx-runtime";
|
|
5
5
|
import { signal, effect, computed, batch, useSignal } from "@preact/signals";
|
|
6
|
-
import { createIterableWeakSet, mergeOneStyle, stringifyStyle, createPubSub, mergeTwoStyles, normalizeStyles, createGroupTransitionController, getElementSignature, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, findBefore, findAfter, createValueEffect, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, allowWheelThrough, resolveCSSColor, createStyleController, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, resolveCSSSize, activeElementSignal, canInterceptKeys,
|
|
6
|
+
import { createIterableWeakSet, mergeOneStyle, stringifyStyle, createPubSub, mergeTwoStyles, normalizeStyles, createGroupTransitionController, getElementSignature, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, findBefore, findAfter, createValueEffect, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, allowWheelThrough, resolveCSSColor, createStyleController, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, resolveCSSSize, activeElementSignal, canInterceptKeys, contrastColor, hasCSSSizeUnit, initFocusGroup, elementIsFocusable, resolveColorLuminance, dragAfterThreshold, getScrollContainer, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement } from "@jsenv/dom";
|
|
7
7
|
export { contrastColor } from "@jsenv/dom";
|
|
8
8
|
import { prefixFirstAndIndentRemainingLines } from "@jsenv/humanize";
|
|
9
9
|
import { createValidity } from "@jsenv/validity";
|
|
@@ -6306,7 +6306,7 @@ const DIMENSION_PROPS = {
|
|
|
6306
6306
|
return {
|
|
6307
6307
|
alignSelf: "stretch",
|
|
6308
6308
|
// Here flex grow is "useless" for the item itself
|
|
6309
|
-
// buuut it would allow children (hello ".
|
|
6309
|
+
// buuut it would allow children (hello ".navi_text_sizer")
|
|
6310
6310
|
// to inherit expand behavior
|
|
6311
6311
|
flexGrow: 1,
|
|
6312
6312
|
};
|
|
@@ -6328,7 +6328,7 @@ const DIMENSION_PROPS = {
|
|
|
6328
6328
|
return {
|
|
6329
6329
|
alignSelf: "stretch",
|
|
6330
6330
|
// Here flex grow is "useless" for the item itself
|
|
6331
|
-
// buuut it would allow children (hello ".
|
|
6331
|
+
// buuut it would allow children (hello ".navi_text_sizer")
|
|
6332
6332
|
// to inherit expand behavior
|
|
6333
6333
|
flexGrow: 1,
|
|
6334
6334
|
};
|
|
@@ -7245,6 +7245,9 @@ const PSEUDO_CLASSES = {
|
|
|
7245
7245
|
":-navi-status-error": {
|
|
7246
7246
|
attribute: "data-status-error",
|
|
7247
7247
|
},
|
|
7248
|
+
":navi-expanded": {
|
|
7249
|
+
attribute: "data-expanded",
|
|
7250
|
+
},
|
|
7248
7251
|
};
|
|
7249
7252
|
|
|
7250
7253
|
const NAVI_PSEUDO_STATE_CUSTOM_EVENT = "navi_pseudo_state";
|
|
@@ -16486,7 +16489,7 @@ const stickCalloutToAnchor = (calloutElement, anchorElement) => {
|
|
|
16486
16489
|
}
|
|
16487
16490
|
calloutElement.setAttribute("data-position", position);
|
|
16488
16491
|
calloutStyleController.set(calloutElement, {
|
|
16489
|
-
opacity: visibilityRatio ? 1 : 0,
|
|
16492
|
+
opacity: visibilityRatio > 0 ? 1 : 0,
|
|
16490
16493
|
transform: {
|
|
16491
16494
|
translateX: calloutLeft,
|
|
16492
16495
|
translateY: calloutTop
|
|
@@ -20276,6 +20279,109 @@ const isSameKey = (browserEventKey, key) => {
|
|
|
20276
20279
|
return false;
|
|
20277
20280
|
};
|
|
20278
20281
|
|
|
20282
|
+
/**
|
|
20283
|
+
* Toggles a `data-dark-background` attribute on the referenced element based on its
|
|
20284
|
+
* computed background color. Pair it with a CSS variable to get automatic
|
|
20285
|
+
* light/dark text without hard-coding colors:
|
|
20286
|
+
*
|
|
20287
|
+
* ```css
|
|
20288
|
+
* .my-element {
|
|
20289
|
+
* --color-contrasting: black;
|
|
20290
|
+
* &[data-dark-background] {
|
|
20291
|
+
* --color-contrasting: white;
|
|
20292
|
+
* }
|
|
20293
|
+
* color: var(--color-contrasting);
|
|
20294
|
+
* }
|
|
20295
|
+
* ```
|
|
20296
|
+
*
|
|
20297
|
+
* - `data-dark-background` is **set** when the background is dark enough that white text
|
|
20298
|
+
* provides better (or equal) contrast.
|
|
20299
|
+
* - `data-dark-background` is **absent** when black text is the better choice.
|
|
20300
|
+
*
|
|
20301
|
+
* @param {import("preact").RefObject} ref - Ref to the element that receives
|
|
20302
|
+
* the `data-dark-background` attribute and is also passed to `contrastColor` for
|
|
20303
|
+
* resolving CSS variables.
|
|
20304
|
+
* @param {object} [options]
|
|
20305
|
+
* @param {string} [options.backgroundElementSelector] - CSS selector relative
|
|
20306
|
+
* to `ref.current` pointing to a child element whose `background-color`
|
|
20307
|
+
* should be tested instead of the element itself. Useful when the element
|
|
20308
|
+
* has a transparent background but contains a coloured child (e.g. a fill
|
|
20309
|
+
* bar inside a track).
|
|
20310
|
+
*/
|
|
20311
|
+
|
|
20312
|
+
const useDarkBackgroundAttribute = (
|
|
20313
|
+
ref,
|
|
20314
|
+
deps = [],
|
|
20315
|
+
{
|
|
20316
|
+
backgroundElementSelector,
|
|
20317
|
+
attributeName = "data-dark-background",
|
|
20318
|
+
hardcoded = {},
|
|
20319
|
+
} = {},
|
|
20320
|
+
) => {
|
|
20321
|
+
const innerDeps = [
|
|
20322
|
+
...deps,
|
|
20323
|
+
// ref can change is the component pass a different ref on different render based on some logic
|
|
20324
|
+
// (can be used to control which element backgroundColor is being checked by switching the ref to another element)
|
|
20325
|
+
ref,
|
|
20326
|
+
// backgroundElementSelector can change if the component pass a different selector on different render based on some logic
|
|
20327
|
+
// (can be used to control which element backgroundColor is being checked by switching the selector to point to another element)
|
|
20328
|
+
backgroundElementSelector,
|
|
20329
|
+
];
|
|
20330
|
+
|
|
20331
|
+
const hardcodedMap = new Map();
|
|
20332
|
+
for (const key of Object.keys(hardcoded)) {
|
|
20333
|
+
const value = hardcoded[key];
|
|
20334
|
+
innerDeps.push(key, value);
|
|
20335
|
+
const colorString = normalizeColorString(key);
|
|
20336
|
+
hardcodedMap.set(colorString, value);
|
|
20337
|
+
}
|
|
20338
|
+
|
|
20339
|
+
useLayoutEffect(() => {
|
|
20340
|
+
const el = ref.current;
|
|
20341
|
+
if (!el) {
|
|
20342
|
+
return undefined;
|
|
20343
|
+
}
|
|
20344
|
+
let elementToCheck = el;
|
|
20345
|
+
if (backgroundElementSelector) {
|
|
20346
|
+
elementToCheck = el.querySelector(backgroundElementSelector);
|
|
20347
|
+
if (!elementToCheck) {
|
|
20348
|
+
return undefined;
|
|
20349
|
+
}
|
|
20350
|
+
}
|
|
20351
|
+
const updateAttribute = () => {
|
|
20352
|
+
const computedStyle = getComputedStyle(elementToCheck);
|
|
20353
|
+
const backgroundColor = computedStyle.backgroundColor;
|
|
20354
|
+
if (!backgroundColor) {
|
|
20355
|
+
el.removeAttribute(attributeName);
|
|
20356
|
+
return;
|
|
20357
|
+
}
|
|
20358
|
+
const backgroundColorString = normalizeColorString(backgroundColor, el);
|
|
20359
|
+
const hardcodedContrast = hardcodedMap.get(backgroundColorString);
|
|
20360
|
+
const contrastingColor =
|
|
20361
|
+
hardcodedContrast || contrastColor(backgroundColor, el);
|
|
20362
|
+
if (contrastingColor === "white") {
|
|
20363
|
+
el.setAttribute(attributeName, "");
|
|
20364
|
+
} else {
|
|
20365
|
+
el.removeAttribute(attributeName);
|
|
20366
|
+
}
|
|
20367
|
+
};
|
|
20368
|
+
updateAttribute();
|
|
20369
|
+
el.addEventListener(NAVI_PSEUDO_STATE_CUSTOM_EVENT, updateAttribute);
|
|
20370
|
+
return () => {
|
|
20371
|
+
el.removeEventListener(NAVI_PSEUDO_STATE_CUSTOM_EVENT, updateAttribute);
|
|
20372
|
+
el.removeAttribute(attributeName);
|
|
20373
|
+
};
|
|
20374
|
+
}, innerDeps);
|
|
20375
|
+
};
|
|
20376
|
+
|
|
20377
|
+
const normalizeColorString = (color, el) => {
|
|
20378
|
+
const colorRgba = resolveCSSColor(color, el);
|
|
20379
|
+
if (!colorRgba) {
|
|
20380
|
+
return "";
|
|
20381
|
+
}
|
|
20382
|
+
return String(colorRgba);
|
|
20383
|
+
};
|
|
20384
|
+
|
|
20279
20385
|
const useInitialTextSelection = (ref, textSelection) => {
|
|
20280
20386
|
const deps = [];
|
|
20281
20387
|
if (Array.isArray(textSelection)) {
|
|
@@ -20375,8 +20481,7 @@ const selectByTextStrings = (element, range, startText, endText) => {
|
|
|
20375
20481
|
}
|
|
20376
20482
|
};
|
|
20377
20483
|
|
|
20378
|
-
installImportMetaCssBuild(import.meta)
|
|
20379
|
-
const css$v = /* css */`
|
|
20484
|
+
installImportMetaCssBuild(import.meta);const css$v = /* css */`
|
|
20380
20485
|
@layer navi {
|
|
20381
20486
|
.navi_text {
|
|
20382
20487
|
&[data-skeleton] {
|
|
@@ -20391,23 +20496,27 @@ const css$v = /* css */`
|
|
|
20391
20496
|
.navi_text {
|
|
20392
20497
|
position: relative;
|
|
20393
20498
|
|
|
20499
|
+
&[data-dark-background] {
|
|
20500
|
+
color: white;
|
|
20501
|
+
}
|
|
20502
|
+
|
|
20394
20503
|
/* There is a chrome specific bug that prevents text-transform: capitalize to be applied in nested DOM structure */
|
|
20395
20504
|
/* The CSS below ensure capitalize is propagated to the bold clones */
|
|
20396
20505
|
&[data-capitalize] {
|
|
20397
20506
|
&::first-letter {
|
|
20398
20507
|
text-transform: uppercase;
|
|
20399
20508
|
}
|
|
20400
|
-
.
|
|
20509
|
+
.navi_text_sizer_placeholder::first-letter {
|
|
20401
20510
|
text-transform: uppercase;
|
|
20402
20511
|
}
|
|
20403
|
-
.
|
|
20512
|
+
.navi_text_sizer_overlay::first-letter {
|
|
20404
20513
|
text-transform: uppercase;
|
|
20405
20514
|
}
|
|
20406
20515
|
}
|
|
20407
20516
|
|
|
20408
|
-
.
|
|
20409
|
-
.
|
|
20410
|
-
.
|
|
20517
|
+
.navi_text_sizer,
|
|
20518
|
+
.navi_text_sizer_placeholder,
|
|
20519
|
+
.navi_text_sizer_overlay {
|
|
20411
20520
|
display: inherit;
|
|
20412
20521
|
width: inherit;
|
|
20413
20522
|
min-width: inherit;
|
|
@@ -20520,15 +20629,14 @@ const css$v = /* css */`
|
|
|
20520
20629
|
}
|
|
20521
20630
|
}
|
|
20522
20631
|
|
|
20523
|
-
.
|
|
20632
|
+
.navi_text_sizer {
|
|
20524
20633
|
position: relative;
|
|
20525
20634
|
display: inline-block;
|
|
20526
20635
|
|
|
20527
|
-
.
|
|
20528
|
-
font-weight: bold;
|
|
20636
|
+
.navi_text_sizer_placeholder {
|
|
20529
20637
|
opacity: 0;
|
|
20530
20638
|
}
|
|
20531
|
-
.
|
|
20639
|
+
.navi_text_sizer_overlay {
|
|
20532
20640
|
position: absolute;
|
|
20533
20641
|
inset: 0;
|
|
20534
20642
|
}
|
|
@@ -20547,26 +20655,14 @@ const css$v = /* css */`
|
|
|
20547
20655
|
-webkit-text-fill-color: transparent;
|
|
20548
20656
|
opacity: 0;
|
|
20549
20657
|
}
|
|
20550
|
-
|
|
20658
|
+
.navi_text[data-contains-absolute-child] {
|
|
20659
|
+
display: inline-block;
|
|
20660
|
+
}
|
|
20551
20661
|
.navi_text[data-bold] {
|
|
20552
20662
|
.navi_text_bold_background {
|
|
20553
20663
|
opacity: 1;
|
|
20554
20664
|
}
|
|
20555
20665
|
}
|
|
20556
|
-
|
|
20557
|
-
.navi_text[data-bold-transition] {
|
|
20558
|
-
.navi_text_bold_foreground {
|
|
20559
|
-
transition-property: font-weight;
|
|
20560
|
-
transition-duration: 0.3s;
|
|
20561
|
-
transition-timing-function: ease;
|
|
20562
|
-
}
|
|
20563
|
-
|
|
20564
|
-
.navi_text_bold_background {
|
|
20565
|
-
transition-property: opacity;
|
|
20566
|
-
transition-duration: 0.3s;
|
|
20567
|
-
transition-timing-function: ease;
|
|
20568
|
-
}
|
|
20569
|
-
}
|
|
20570
20666
|
`;
|
|
20571
20667
|
const REGULAR_SPACE = jsx("span", {
|
|
20572
20668
|
"data-navi-space": "",
|
|
@@ -20685,6 +20781,61 @@ const shouldInjectSpacingBetween = (left, right) => {
|
|
|
20685
20781
|
return true;
|
|
20686
20782
|
};
|
|
20687
20783
|
const OverflowPinnedElementContext = createContext(null);
|
|
20784
|
+
/**
|
|
20785
|
+
* Text component for rendering inline or block text with layout-stable style changes.
|
|
20786
|
+
*
|
|
20787
|
+
* Most props are forwarded to the underlying `Box` component (as, style, bold, noWrap, …).
|
|
20788
|
+
* The props listed below are specific to Text.
|
|
20789
|
+
*
|
|
20790
|
+
* @param {object} props
|
|
20791
|
+
*
|
|
20792
|
+
* @param {boolean} [props.overflowEllipsis]
|
|
20793
|
+
* Truncates overflowing text with an ellipsis.
|
|
20794
|
+
*
|
|
20795
|
+
* @param {boolean} [props.overflowPinned]
|
|
20796
|
+
* Must be used inside a `<Text overflowEllipsis>` parent.
|
|
20797
|
+
* Pins this element outside the truncated text flow (e.g. a badge or icon).
|
|
20798
|
+
*
|
|
20799
|
+
* @param {string} [props.spacing]
|
|
20800
|
+
* Controls the separator injected between child nodes.
|
|
20801
|
+
* Accepts a size/spacing scale key, a CSS length, or `"pre"` / `0` to disable.
|
|
20802
|
+
* Defaults to a regular space character (or a padding-based fake space when
|
|
20803
|
+
* `preventSpaceUnderlines` is active).
|
|
20804
|
+
*
|
|
20805
|
+
* @param {boolean} [props.loading]
|
|
20806
|
+
* Renders a shimmer skeleton in place of the text.
|
|
20807
|
+
*
|
|
20808
|
+
* @param {boolean} [props.skeleton]
|
|
20809
|
+
* Same as `loading` but without the shimmer animation.
|
|
20810
|
+
*
|
|
20811
|
+
* @param {boolean} [props.preventSpaceUnderlines]
|
|
20812
|
+
* Replaces real space characters between children with padding-based spaces
|
|
20813
|
+
* to avoid the underline browsers draw under spaces inside links.
|
|
20814
|
+
*
|
|
20815
|
+
* @param {object} [props.holdSpaceForStyle]
|
|
20816
|
+
* Prevents layout shifts when text styles change (font-weight, font-size, …).
|
|
20817
|
+
* Pass an object of CSS-in-JS style properties representing the "maximum" state of the text.
|
|
20818
|
+
* An invisible placeholder is rendered with those styles to reserve the space,
|
|
20819
|
+
* and the real visible text is layered on top via `position: absolute`.
|
|
20820
|
+
* Only works reliably with single-line (`noWrap`) text.
|
|
20821
|
+
* Example: `holdSpaceForStyle={{ fontWeight: "bold", fontSize: "1.5rem" }}`
|
|
20822
|
+
*
|
|
20823
|
+
* @param {boolean} [props.boldStable]
|
|
20824
|
+
* Alternative to `holdSpaceForStyle` for multi-line text.
|
|
20825
|
+
* Keeps a consistent visual width regardless of font-weight by painting normal-weight
|
|
20826
|
+
* text on top of a bold background using `background-clip: text`.
|
|
20827
|
+
* Does not support font-size changes.
|
|
20828
|
+
*
|
|
20829
|
+
* @param {boolean} [props.capitalize]
|
|
20830
|
+
* Applies `text-transform: uppercase` to the first letter via CSS.
|
|
20831
|
+
*
|
|
20832
|
+
* @param {string|Array} [props.selectRange]
|
|
20833
|
+
* Selects a portion of the text on mount. Forwarded to `useInitialTextSelection`.
|
|
20834
|
+
*
|
|
20835
|
+
* @param {*} [props.childrenOutsideFlow]
|
|
20836
|
+
* Rendered after children but outside the text flow (useful for overlays
|
|
20837
|
+
* like the skeleton container).
|
|
20838
|
+
*/
|
|
20688
20839
|
const Text = props => {
|
|
20689
20840
|
import.meta.css = [css$v, "@jsenv/navi/src/text/text.jsx"];
|
|
20690
20841
|
if (props.loading || props.skeleton) {
|
|
@@ -20814,21 +20965,25 @@ const TextWithSelectRange = ({
|
|
|
20814
20965
|
const TextBasic = ({
|
|
20815
20966
|
spacing,
|
|
20816
20967
|
preventSpaceUnderlines = false,
|
|
20817
|
-
boldTransition,
|
|
20818
20968
|
boldStable,
|
|
20819
|
-
|
|
20969
|
+
holdSpaceForStyle,
|
|
20820
20970
|
capitalize,
|
|
20821
20971
|
children,
|
|
20822
20972
|
childrenOutsideFlow,
|
|
20973
|
+
basePseudoState,
|
|
20823
20974
|
...rest
|
|
20824
20975
|
}) => {
|
|
20976
|
+
const defaultRef = useRef();
|
|
20977
|
+
const ref = rest.ref || defaultRef;
|
|
20978
|
+
const bgDeps = basePseudoState ? Object.values(basePseudoState) : [];
|
|
20979
|
+
useDarkBackgroundAttribute(ref, bgDeps);
|
|
20825
20980
|
const defaultSpace = preventSpaceUnderlines ? FAKE_SPACE : REGULAR_SPACE;
|
|
20826
20981
|
const resolvedSpacing = spacing ?? defaultSpace;
|
|
20827
20982
|
const boxProps = {
|
|
20828
20983
|
"as": "span",
|
|
20829
|
-
"data-bold-transition": boldTransition ? "" : undefined,
|
|
20830
20984
|
"data-capitalize": capitalize ? "" : undefined,
|
|
20831
20985
|
...rest,
|
|
20986
|
+
ref,
|
|
20832
20987
|
"baseClassName": withPropsClassName("navi_text", rest.baseClassName)
|
|
20833
20988
|
};
|
|
20834
20989
|
const shouldPreserveSpacing = rest.as === "pre" || rest.flex || rest.grid;
|
|
@@ -20845,6 +21000,7 @@ const TextBasic = ({
|
|
|
20845
21000
|
...boxProps,
|
|
20846
21001
|
bold: undefined,
|
|
20847
21002
|
"data-bold": bold ? "" : undefined,
|
|
21003
|
+
"data-contains-absolute-child": "",
|
|
20848
21004
|
children: [jsx("span", {
|
|
20849
21005
|
className: "navi_text_bold_background",
|
|
20850
21006
|
"aria-hidden": "true",
|
|
@@ -20852,25 +21008,23 @@ const TextBasic = ({
|
|
|
20852
21008
|
}), children, childrenOutsideFlow]
|
|
20853
21009
|
});
|
|
20854
21010
|
}
|
|
20855
|
-
if (
|
|
20856
|
-
|
|
20857
|
-
|
|
20858
|
-
//
|
|
20859
|
-
//
|
|
20860
|
-
//
|
|
20861
|
-
// ne fonctionne que sur une seule ligne de texte (donc lorsque noWrap est actif)
|
|
20862
|
-
// on pourrait auto-active cela sur une prop genre boldCanChange
|
|
21011
|
+
if (holdSpaceForStyle) {
|
|
21012
|
+
// The sizer technique prevents layout shifts when styles that affect text dimensions change.
|
|
21013
|
+
// - navi_text_sizer_placeholder: invisible, rendered with holdSpaceForStyle applied so it
|
|
21014
|
+
// always occupies the "maximum" dimensions (e.g. bold + larger font-size).
|
|
21015
|
+
// - navi_text_sizer_overlay: absolutely positioned on top, renders the actual visible text
|
|
21016
|
+
// with its current style. Transitions can be applied on this element from the outside.
|
|
20863
21017
|
return jsxs(Box, {
|
|
20864
21018
|
...boxProps,
|
|
20865
21019
|
children: [jsxs("span", {
|
|
20866
|
-
className: "
|
|
21020
|
+
className: "navi_text_sizer",
|
|
20867
21021
|
children: [jsx("span", {
|
|
20868
|
-
className: "
|
|
21022
|
+
className: "navi_text_sizer_placeholder",
|
|
20869
21023
|
"aria-hidden": "true",
|
|
21024
|
+
style: holdSpaceForStyle,
|
|
20870
21025
|
children: children
|
|
20871
21026
|
}), jsx("span", {
|
|
20872
|
-
className: "
|
|
20873
|
-
"data-align": alignX,
|
|
21027
|
+
className: "navi_text_sizer_overlay",
|
|
20874
21028
|
children: children
|
|
20875
21029
|
})]
|
|
20876
21030
|
}), childrenOutsideFlow]
|
|
@@ -21166,109 +21320,6 @@ const Icon = ({
|
|
|
21166
21320
|
});
|
|
21167
21321
|
};
|
|
21168
21322
|
|
|
21169
|
-
/**
|
|
21170
|
-
* Toggles a `data-dark-background` attribute on the referenced element based on its
|
|
21171
|
-
* computed background color. Pair it with a CSS variable to get automatic
|
|
21172
|
-
* light/dark text without hard-coding colors:
|
|
21173
|
-
*
|
|
21174
|
-
* ```css
|
|
21175
|
-
* .my-element {
|
|
21176
|
-
* --color-contrasting: black;
|
|
21177
|
-
* &[data-dark-background] {
|
|
21178
|
-
* --color-contrasting: white;
|
|
21179
|
-
* }
|
|
21180
|
-
* color: var(--color-contrasting);
|
|
21181
|
-
* }
|
|
21182
|
-
* ```
|
|
21183
|
-
*
|
|
21184
|
-
* - `data-dark-background` is **set** when the background is dark enough that white text
|
|
21185
|
-
* provides better (or equal) contrast.
|
|
21186
|
-
* - `data-dark-background` is **absent** when black text is the better choice.
|
|
21187
|
-
*
|
|
21188
|
-
* @param {import("preact").RefObject} ref - Ref to the element that receives
|
|
21189
|
-
* the `data-dark-background` attribute and is also passed to `contrastColor` for
|
|
21190
|
-
* resolving CSS variables.
|
|
21191
|
-
* @param {object} [options]
|
|
21192
|
-
* @param {string} [options.backgroundElementSelector] - CSS selector relative
|
|
21193
|
-
* to `ref.current` pointing to a child element whose `background-color`
|
|
21194
|
-
* should be tested instead of the element itself. Useful when the element
|
|
21195
|
-
* has a transparent background but contains a coloured child (e.g. a fill
|
|
21196
|
-
* bar inside a track).
|
|
21197
|
-
*/
|
|
21198
|
-
|
|
21199
|
-
const useDarkBackgroundAttribute = (
|
|
21200
|
-
ref,
|
|
21201
|
-
deps = [],
|
|
21202
|
-
{
|
|
21203
|
-
backgroundElementSelector,
|
|
21204
|
-
attributeName = "data-dark-background",
|
|
21205
|
-
hardcoded = {},
|
|
21206
|
-
} = {},
|
|
21207
|
-
) => {
|
|
21208
|
-
const innerDeps = [
|
|
21209
|
-
...deps,
|
|
21210
|
-
// ref can change is the component pass a different ref on different render based on some logic
|
|
21211
|
-
// (can be used to control which element backgroundColor is being checked by switching the ref to another element)
|
|
21212
|
-
ref,
|
|
21213
|
-
// backgroundElementSelector can change if the component pass a different selector on different render based on some logic
|
|
21214
|
-
// (can be used to control which element backgroundColor is being checked by switching the selector to point to another element)
|
|
21215
|
-
backgroundElementSelector,
|
|
21216
|
-
];
|
|
21217
|
-
|
|
21218
|
-
const hardcodedMap = new Map();
|
|
21219
|
-
for (const key of Object.keys(hardcoded)) {
|
|
21220
|
-
const value = hardcoded[key];
|
|
21221
|
-
innerDeps.push(key, value);
|
|
21222
|
-
const colorString = normalizeColorString(key);
|
|
21223
|
-
hardcodedMap.set(colorString, value);
|
|
21224
|
-
}
|
|
21225
|
-
|
|
21226
|
-
useLayoutEffect(() => {
|
|
21227
|
-
const el = ref.current;
|
|
21228
|
-
if (!el) {
|
|
21229
|
-
return undefined;
|
|
21230
|
-
}
|
|
21231
|
-
let elementToCheck = el;
|
|
21232
|
-
if (backgroundElementSelector) {
|
|
21233
|
-
elementToCheck = el.querySelector(backgroundElementSelector);
|
|
21234
|
-
if (!elementToCheck) {
|
|
21235
|
-
return undefined;
|
|
21236
|
-
}
|
|
21237
|
-
}
|
|
21238
|
-
const updateAttribute = () => {
|
|
21239
|
-
const computedStyle = getComputedStyle(elementToCheck);
|
|
21240
|
-
const backgroundColor = computedStyle.backgroundColor;
|
|
21241
|
-
if (!backgroundColor) {
|
|
21242
|
-
el.removeAttribute(attributeName);
|
|
21243
|
-
return;
|
|
21244
|
-
}
|
|
21245
|
-
const backgroundColorString = normalizeColorString(backgroundColor, el);
|
|
21246
|
-
const hardcodedContrast = hardcodedMap.get(backgroundColorString);
|
|
21247
|
-
const contrastingColor =
|
|
21248
|
-
hardcodedContrast || contrastColor(backgroundColor, el);
|
|
21249
|
-
if (contrastingColor === "white") {
|
|
21250
|
-
el.setAttribute(attributeName, "");
|
|
21251
|
-
} else {
|
|
21252
|
-
el.removeAttribute(attributeName);
|
|
21253
|
-
}
|
|
21254
|
-
};
|
|
21255
|
-
updateAttribute();
|
|
21256
|
-
el.addEventListener(NAVI_PSEUDO_STATE_CUSTOM_EVENT, updateAttribute);
|
|
21257
|
-
return () => {
|
|
21258
|
-
el.removeEventListener(NAVI_PSEUDO_STATE_CUSTOM_EVENT, updateAttribute);
|
|
21259
|
-
el.removeAttribute(attributeName);
|
|
21260
|
-
};
|
|
21261
|
-
}, innerDeps);
|
|
21262
|
-
};
|
|
21263
|
-
|
|
21264
|
-
const normalizeColorString = (color, el) => {
|
|
21265
|
-
const colorRgba = resolveCSSColor(color, el);
|
|
21266
|
-
if (!colorRgba) {
|
|
21267
|
-
return "";
|
|
21268
|
-
}
|
|
21269
|
-
return String(colorRgba);
|
|
21270
|
-
};
|
|
21271
|
-
|
|
21272
21323
|
const useFormEvents = (
|
|
21273
21324
|
elementRef,
|
|
21274
21325
|
{
|
|
@@ -21790,8 +21841,7 @@ const useUIState = (uiStateController) => {
|
|
|
21790
21841
|
return trackedUIState;
|
|
21791
21842
|
};
|
|
21792
21843
|
|
|
21793
|
-
installImportMetaCssBuild(import.meta)
|
|
21794
|
-
const css$s = /* css */`
|
|
21844
|
+
installImportMetaCssBuild(import.meta);const css$s = /* css */`
|
|
21795
21845
|
@layer navi {
|
|
21796
21846
|
.navi_button {
|
|
21797
21847
|
--button-outline-width: 1px;
|
|
@@ -22588,8 +22638,7 @@ const useDimColorWhen = (elementRef, shouldDim) => {
|
|
|
22588
22638
|
});
|
|
22589
22639
|
};
|
|
22590
22640
|
|
|
22591
|
-
installImportMetaCssBuild(import.meta)
|
|
22592
|
-
const css$r = /* css */`
|
|
22641
|
+
installImportMetaCssBuild(import.meta);const css$r = /* css */`
|
|
22593
22642
|
@layer navi {
|
|
22594
22643
|
.navi_link {
|
|
22595
22644
|
--link-border-radius: unset;
|
|
@@ -22735,7 +22784,7 @@ const css$r = /* css */`
|
|
|
22735
22784
|
}
|
|
22736
22785
|
|
|
22737
22786
|
/* Dark background */
|
|
22738
|
-
&[data-dark-background] {
|
|
22787
|
+
&[data-dark-background].navi_text {
|
|
22739
22788
|
--x-link-contrasting-color: white;
|
|
22740
22789
|
--x-link-color: var(--link-color, white);
|
|
22741
22790
|
}
|
|
@@ -23041,7 +23090,6 @@ const LinkPlain = props => {
|
|
|
23041
23090
|
isCurrent
|
|
23042
23091
|
} = getHrefTargetInfo(href);
|
|
23043
23092
|
const innerCurrent = current || isCurrent;
|
|
23044
|
-
useDarkBackgroundAttribute(ref, [selected, innerCurrent], {});
|
|
23045
23093
|
const innerTarget = target === undefined ? isSameSite ? "_self" : "_blank" : target;
|
|
23046
23094
|
const innerRel = rel === undefined ? isSameSite ? undefined : "noopener noreferrer" : rel;
|
|
23047
23095
|
let innerEndIcon;
|
|
@@ -23094,7 +23142,9 @@ const LinkPlain = props => {
|
|
|
23094
23142
|
onnavi_value: e => {
|
|
23095
23143
|
e.detail.setValue(value);
|
|
23096
23144
|
},
|
|
23097
|
-
|
|
23145
|
+
holdSpaceForStyle: currentEffectBold ? {
|
|
23146
|
+
fontWeight: "bold"
|
|
23147
|
+
} : undefined,
|
|
23098
23148
|
preventSpaceUnderlines: true,
|
|
23099
23149
|
overflowEllipsis: overflowEllipsis
|
|
23100
23150
|
// Visual
|
|
@@ -25699,6 +25749,16 @@ const InputRangeWithAction = props => {
|
|
|
25699
25749
|
});
|
|
25700
25750
|
};
|
|
25701
25751
|
|
|
25752
|
+
const ChevronDownSvg = () => {
|
|
25753
|
+
return jsx("svg", {
|
|
25754
|
+
viewBox: "0 0 16 16",
|
|
25755
|
+
fill: "currentColor",
|
|
25756
|
+
children: jsx("path", {
|
|
25757
|
+
d: "M4.427 7.427l3.396 3.396a.25.25 0 0 0 .354 0l3.396-3.396A.25.25 0 0 0 11.396 7H4.604a.25.25 0 0 0-.177.427z"
|
|
25758
|
+
})
|
|
25759
|
+
});
|
|
25760
|
+
};
|
|
25761
|
+
|
|
25702
25762
|
const SearchSvg = () => jsx("svg", {
|
|
25703
25763
|
viewBox: "0 0 24 24",
|
|
25704
25764
|
xmlns: "http://www.w3.org/2000/svg",
|
|
@@ -25708,7 +25768,23 @@ const SearchSvg = () => jsx("svg", {
|
|
|
25708
25768
|
})
|
|
25709
25769
|
});
|
|
25710
25770
|
|
|
25711
|
-
installImportMetaCssBuild(import.meta)
|
|
25771
|
+
installImportMetaCssBuild(import.meta);/**
|
|
25772
|
+
* Input component for all textual input types.
|
|
25773
|
+
*
|
|
25774
|
+
* Supports:
|
|
25775
|
+
* - text (default)
|
|
25776
|
+
* - password
|
|
25777
|
+
* - hidden
|
|
25778
|
+
* - email
|
|
25779
|
+
* - url
|
|
25780
|
+
* - search
|
|
25781
|
+
* - tel
|
|
25782
|
+
* - etc.
|
|
25783
|
+
*
|
|
25784
|
+
* For non-textual inputs, specialized components will be used:
|
|
25785
|
+
* - <InputCheckbox /> for type="checkbox"
|
|
25786
|
+
* - <InputRadio /> for type="radio"
|
|
25787
|
+
*/
|
|
25712
25788
|
const css$j = /* css */`
|
|
25713
25789
|
@layer navi {
|
|
25714
25790
|
.navi_input {
|
|
@@ -25767,8 +25843,8 @@ const css$j = /* css */`
|
|
|
25767
25843
|
border-radius: inherit;
|
|
25768
25844
|
cursor: inherit;
|
|
25769
25845
|
|
|
25770
|
-
--
|
|
25771
|
-
--
|
|
25846
|
+
--left-slot-size: 0px;
|
|
25847
|
+
--right-slot-size: 0px;
|
|
25772
25848
|
--x-outline-width: var(--outline-width);
|
|
25773
25849
|
--x-border-radius: var(--border-radius);
|
|
25774
25850
|
--x-border-width: var(--border-width);
|
|
@@ -25799,9 +25875,9 @@ const css$j = /* css */`
|
|
|
25799
25875
|
.navi_native_input {
|
|
25800
25876
|
box-sizing: border-box;
|
|
25801
25877
|
padding-top: var(--x-padding-top-base);
|
|
25802
|
-
padding-right: calc(var(--x-padding-right-base) + var(--
|
|
25878
|
+
padding-right: calc(var(--x-padding-right-base) + var(--right-slot-size));
|
|
25803
25879
|
padding-bottom: var(--x-padding-bottom-base);
|
|
25804
|
-
padding-left: calc(var(--x-padding-left-base) + var(--
|
|
25880
|
+
padding-left: calc(var(--x-padding-left-base) + var(--left-slot-size));
|
|
25805
25881
|
color: var(--x-color);
|
|
25806
25882
|
font-size: var(--font-size);
|
|
25807
25883
|
background-color: var(--x-background-color);
|
|
@@ -25824,18 +25900,10 @@ const css$j = /* css */`
|
|
|
25824
25900
|
}
|
|
25825
25901
|
}
|
|
25826
25902
|
|
|
25827
|
-
.
|
|
25903
|
+
.navi_input_slot {
|
|
25828
25904
|
position: absolute;
|
|
25829
25905
|
top: 0;
|
|
25830
25906
|
bottom: 0;
|
|
25831
|
-
left: var(--x-padding-left-base);
|
|
25832
|
-
font-size: var(--font-size);
|
|
25833
|
-
}
|
|
25834
|
-
.navi_input_end_button {
|
|
25835
|
-
position: absolute;
|
|
25836
|
-
top: 0;
|
|
25837
|
-
right: var(--x-padding-right-base);
|
|
25838
|
-
bottom: 0;
|
|
25839
25907
|
display: inline-flex;
|
|
25840
25908
|
margin: 0;
|
|
25841
25909
|
padding: 0;
|
|
@@ -25844,34 +25912,43 @@ const css$j = /* css */`
|
|
|
25844
25912
|
font-size: var(--font-size);
|
|
25845
25913
|
background: none;
|
|
25846
25914
|
border: none;
|
|
25847
|
-
|
|
25848
|
-
|
|
25915
|
+
|
|
25916
|
+
&[data-left] {
|
|
25917
|
+
left: var(--x-padding-left-base);
|
|
25918
|
+
}
|
|
25919
|
+
&[data-right] {
|
|
25920
|
+
right: var(--x-padding-right-base);
|
|
25921
|
+
}
|
|
25922
|
+
&[data-hide-while-empty] {
|
|
25923
|
+
opacity: 0;
|
|
25924
|
+
pointer-events: none;
|
|
25925
|
+
}
|
|
25849
25926
|
}
|
|
25850
25927
|
&[data-has-value] {
|
|
25851
|
-
.
|
|
25928
|
+
.navi_input_slot[data-hide-while-empty] {
|
|
25852
25929
|
opacity: 1;
|
|
25853
25930
|
cursor: pointer;
|
|
25854
25931
|
pointer-events: auto;
|
|
25855
25932
|
}
|
|
25856
25933
|
|
|
25857
25934
|
&[data-readonly] {
|
|
25858
|
-
.
|
|
25935
|
+
.navi_input_slot[data-hide-while-empty] {
|
|
25859
25936
|
opacity: 0;
|
|
25860
25937
|
pointer-events: none;
|
|
25861
25938
|
}
|
|
25862
25939
|
}
|
|
25863
25940
|
&[data-disabled] {
|
|
25864
|
-
.
|
|
25941
|
+
.navi_input_slot[data-hide-while-empty] {
|
|
25865
25942
|
opacity: 0;
|
|
25866
25943
|
pointer-events: none;
|
|
25867
25944
|
}
|
|
25868
25945
|
}
|
|
25869
25946
|
}
|
|
25870
|
-
|
|
25871
|
-
--
|
|
25947
|
+
&:has(.navi_input_slot[data-left]) {
|
|
25948
|
+
--left-slot-size: 1em;
|
|
25872
25949
|
}
|
|
25873
|
-
|
|
25874
|
-
--
|
|
25950
|
+
&:has(.navi_input_slot[data-right]) {
|
|
25951
|
+
--right-slot-size: 1em;
|
|
25875
25952
|
}
|
|
25876
25953
|
|
|
25877
25954
|
/* Hover */
|
|
@@ -25977,7 +26054,7 @@ const InputStyleCSSVars = {
|
|
|
25977
26054
|
color: "--color-disabled"
|
|
25978
26055
|
}
|
|
25979
26056
|
};
|
|
25980
|
-
const InputPseudoClasses = [":hover", ":active", ":focus", ":focus-visible", ":read-only", ":disabled", ":-navi-loading", ":navi-has-value"];
|
|
26057
|
+
const InputPseudoClasses = [":hover", ":active", ":focus", ":focus-visible", ":read-only", ":disabled", ":-navi-loading", ":navi-has-value", ":navi-expanded"];
|
|
25981
26058
|
Object.assign(PSEUDO_CLASSES, {
|
|
25982
26059
|
":navi-has-value": {
|
|
25983
26060
|
attribute: "data-has-value",
|
|
@@ -25994,6 +26071,55 @@ Object.assign(PSEUDO_CLASSES, {
|
|
|
25994
26071
|
});
|
|
25995
26072
|
const InputPseudoElements = ["::-navi-loader"];
|
|
25996
26073
|
const InputChildPropSet = new Set([...fieldPropSet]);
|
|
26074
|
+
const InputNativeContext = createContext(null);
|
|
26075
|
+
const InputSlot = ({
|
|
26076
|
+
side,
|
|
26077
|
+
onClick,
|
|
26078
|
+
hideWhileEmpty,
|
|
26079
|
+
...props
|
|
26080
|
+
}) => {
|
|
26081
|
+
const ctx = useContext(InputNativeContext);
|
|
26082
|
+
const {
|
|
26083
|
+
id,
|
|
26084
|
+
readOnly,
|
|
26085
|
+
disabled
|
|
26086
|
+
} = ctx;
|
|
26087
|
+
return jsx(Label, {
|
|
26088
|
+
htmlFor: id,
|
|
26089
|
+
className: "navi_input_slot",
|
|
26090
|
+
disabled: disabled,
|
|
26091
|
+
readOnly: readOnly,
|
|
26092
|
+
"data-readonly": readOnly,
|
|
26093
|
+
"data-disabled": disabled,
|
|
26094
|
+
"data-left": side === "left" ? "" : undefined,
|
|
26095
|
+
"data-right": side === "right" ? "" : undefined,
|
|
26096
|
+
"data-hide-while-empty": hideWhileEmpty ? "" : undefined,
|
|
26097
|
+
flex: true,
|
|
26098
|
+
alignY: "center",
|
|
26099
|
+
onMouseDown: e => {
|
|
26100
|
+
e.preventDefault(); // keep focus in the input
|
|
26101
|
+
},
|
|
26102
|
+
onClick: e => {
|
|
26103
|
+
if (readOnly || disabled) {
|
|
26104
|
+
return;
|
|
26105
|
+
}
|
|
26106
|
+
onClick?.(e);
|
|
26107
|
+
},
|
|
26108
|
+
...props
|
|
26109
|
+
});
|
|
26110
|
+
};
|
|
26111
|
+
const InputLeftSlot = props => {
|
|
26112
|
+
return jsx(InputSlot, {
|
|
26113
|
+
...props,
|
|
26114
|
+
side: "left"
|
|
26115
|
+
});
|
|
26116
|
+
};
|
|
26117
|
+
const InputRightSlot = props => {
|
|
26118
|
+
return jsx(InputSlot, {
|
|
26119
|
+
...props,
|
|
26120
|
+
side: "right"
|
|
26121
|
+
});
|
|
26122
|
+
};
|
|
25997
26123
|
const InputTextualBasic = props => {
|
|
25998
26124
|
if (props.suggestions) {
|
|
25999
26125
|
return jsx(InputTextualWithSuggestions, {
|
|
@@ -26009,54 +26135,48 @@ const InputTextualWithSuggestions = ({
|
|
|
26009
26135
|
onInput,
|
|
26010
26136
|
onFocus,
|
|
26011
26137
|
onBlur,
|
|
26138
|
+
children,
|
|
26012
26139
|
...rest
|
|
26013
26140
|
}) => {
|
|
26014
26141
|
const defaultRef = useRef();
|
|
26015
26142
|
const ref = rest.ref || defaultRef;
|
|
26016
|
-
const [
|
|
26017
|
-
const
|
|
26018
|
-
|
|
26019
|
-
const
|
|
26020
|
-
|
|
26143
|
+
const [expanded, setExpanded] = useState(false);
|
|
26144
|
+
const expandedRef = useRef(expanded);
|
|
26145
|
+
expandedRef.current = expanded;
|
|
26146
|
+
const expand = () => {
|
|
26147
|
+
expandedRef.current = true;
|
|
26148
|
+
setExpanded(true);
|
|
26149
|
+
};
|
|
26150
|
+
const collapse = () => {
|
|
26151
|
+
expandedRef.current = false;
|
|
26152
|
+
setExpanded(false);
|
|
26153
|
+
};
|
|
26154
|
+
const showSuggestions = e => {
|
|
26155
|
+
if (expandedRef.current) {
|
|
26021
26156
|
return;
|
|
26022
26157
|
}
|
|
26023
26158
|
console.debug(`showPopover (e.type:${e.type})`);
|
|
26024
26159
|
const popoverEl = document.getElementById(suggestions);
|
|
26025
|
-
|
|
26026
|
-
|
|
26027
|
-
|
|
26028
|
-
|
|
26029
|
-
|
|
26030
|
-
|
|
26031
|
-
|
|
26032
|
-
});
|
|
26160
|
+
if (!popoverEl) {
|
|
26161
|
+
return;
|
|
26162
|
+
}
|
|
26163
|
+
popoverEl.dispatchEvent(new CustomEvent("navi_suggestion_list_open", {
|
|
26164
|
+
detail: {
|
|
26165
|
+
anchor: ref.current
|
|
26166
|
+
}
|
|
26167
|
+
}));
|
|
26168
|
+
expand();
|
|
26033
26169
|
};
|
|
26034
|
-
const
|
|
26035
|
-
if (!
|
|
26170
|
+
const hideSuggestions = e => {
|
|
26171
|
+
if (!expandedRef.current) {
|
|
26036
26172
|
return;
|
|
26037
26173
|
}
|
|
26038
26174
|
console.debug(`hidePopover (e.type:${e.type})`);
|
|
26039
|
-
suggestionsOpenRef.current = false;
|
|
26040
|
-
setSuggestionsOpen(false);
|
|
26041
|
-
window.removeEventListener("scroll", positionPopover, {
|
|
26042
|
-
capture: true
|
|
26043
|
-
});
|
|
26044
|
-
const popoverEl = document.getElementById(suggestions);
|
|
26045
|
-
if (popoverEl) {
|
|
26046
|
-
popoverEl.dispatchEvent(new CustomEvent("navi_suggestion_list_clear"));
|
|
26047
|
-
popoverEl.hidePopover();
|
|
26048
|
-
}
|
|
26049
|
-
setSuggestionsOpen(false);
|
|
26050
|
-
};
|
|
26051
|
-
const positionPopover = () => {
|
|
26052
|
-
const input = ref.current;
|
|
26053
|
-
const rect = input.getBoundingClientRect();
|
|
26054
26175
|
const popoverEl = document.getElementById(suggestions);
|
|
26055
26176
|
if (popoverEl) {
|
|
26056
|
-
popoverEl.
|
|
26057
|
-
popoverEl.style.left = `${rect.left}px`;
|
|
26058
|
-
popoverEl.style.width = `${rect.width}px`;
|
|
26177
|
+
popoverEl.dispatchEvent(new CustomEvent("navi_suggestion_list_close"));
|
|
26059
26178
|
}
|
|
26179
|
+
collapse();
|
|
26060
26180
|
};
|
|
26061
26181
|
const dispatchToSuggestionList = customEvent => {
|
|
26062
26182
|
const popoverEl = document.getElementById(suggestions);
|
|
@@ -26070,7 +26190,7 @@ const InputTextualWithSuggestions = ({
|
|
|
26070
26190
|
key: "arrowdown",
|
|
26071
26191
|
description: "Open popover and point to next suggestion",
|
|
26072
26192
|
handler: e => {
|
|
26073
|
-
|
|
26193
|
+
showSuggestions(e);
|
|
26074
26194
|
const popoverEl = document.getElementById(suggestions);
|
|
26075
26195
|
if (!popoverEl) {
|
|
26076
26196
|
return false;
|
|
@@ -26086,7 +26206,7 @@ const InputTextualWithSuggestions = ({
|
|
|
26086
26206
|
key: "arrowup",
|
|
26087
26207
|
description: "Open popover and point to previous suggestion",
|
|
26088
26208
|
handler: e => {
|
|
26089
|
-
|
|
26209
|
+
showSuggestions(e);
|
|
26090
26210
|
return dispatchToSuggestionList(new CustomEvent("navi_suggestion_list_navigate", {
|
|
26091
26211
|
detail: {
|
|
26092
26212
|
direction: "up"
|
|
@@ -26097,7 +26217,7 @@ const InputTextualWithSuggestions = ({
|
|
|
26097
26217
|
key: "home",
|
|
26098
26218
|
description: "Point to first suggestion",
|
|
26099
26219
|
handler: () => {
|
|
26100
|
-
if (!
|
|
26220
|
+
if (!expandedRef.current) {
|
|
26101
26221
|
return false;
|
|
26102
26222
|
}
|
|
26103
26223
|
return dispatchToSuggestionList(new CustomEvent("navi_suggestion_list_navigate", {
|
|
@@ -26110,7 +26230,7 @@ const InputTextualWithSuggestions = ({
|
|
|
26110
26230
|
key: "end",
|
|
26111
26231
|
description: "Point to last suggestion",
|
|
26112
26232
|
handler: () => {
|
|
26113
|
-
if (!
|
|
26233
|
+
if (!expandedRef.current) {
|
|
26114
26234
|
return false;
|
|
26115
26235
|
}
|
|
26116
26236
|
return dispatchToSuggestionList(new CustomEvent("navi_suggestion_list_navigate", {
|
|
@@ -26123,7 +26243,7 @@ const InputTextualWithSuggestions = ({
|
|
|
26123
26243
|
key: "enter",
|
|
26124
26244
|
description: "Confirm pointed suggestion",
|
|
26125
26245
|
handler: () => {
|
|
26126
|
-
if (!
|
|
26246
|
+
if (!expandedRef.current) {
|
|
26127
26247
|
return false;
|
|
26128
26248
|
}
|
|
26129
26249
|
return dispatchToSuggestionList(new CustomEvent("navi_suggestion_list_confirm", {
|
|
@@ -26134,10 +26254,10 @@ const InputTextualWithSuggestions = ({
|
|
|
26134
26254
|
key: "escape",
|
|
26135
26255
|
description: "Close popover",
|
|
26136
26256
|
handler: e => {
|
|
26137
|
-
if (!
|
|
26257
|
+
if (!expandedRef.current) {
|
|
26138
26258
|
return false;
|
|
26139
26259
|
}
|
|
26140
|
-
|
|
26260
|
+
hideSuggestions(e);
|
|
26141
26261
|
return true;
|
|
26142
26262
|
}
|
|
26143
26263
|
}]);
|
|
@@ -26152,7 +26272,7 @@ const InputTextualWithSuggestions = ({
|
|
|
26152
26272
|
inputEl.dispatchEvent(new Event("input", {
|
|
26153
26273
|
bubbles: true
|
|
26154
26274
|
}));
|
|
26155
|
-
|
|
26275
|
+
hideSuggestions(e);
|
|
26156
26276
|
};
|
|
26157
26277
|
popoverEl.addEventListener("navi_suggestion_list_selected", onSelected);
|
|
26158
26278
|
return () => {
|
|
@@ -26165,24 +26285,40 @@ const InputTextualWithSuggestions = ({
|
|
|
26165
26285
|
autoComplete: "off",
|
|
26166
26286
|
"aria-controls": suggestions,
|
|
26167
26287
|
"aria-haspopup": "listbox",
|
|
26168
|
-
"aria-expanded":
|
|
26288
|
+
"aria-expanded": expanded,
|
|
26169
26289
|
"aria-autocomplete": "list",
|
|
26290
|
+
basePseudoState: {
|
|
26291
|
+
":navi-expanded": expanded
|
|
26292
|
+
},
|
|
26170
26293
|
onnavi_callout_open: e => {
|
|
26171
|
-
|
|
26294
|
+
hideSuggestions(e);
|
|
26172
26295
|
},
|
|
26173
26296
|
onFocus: e => {
|
|
26174
26297
|
onFocus?.(e);
|
|
26175
|
-
|
|
26298
|
+
showSuggestions(e);
|
|
26176
26299
|
},
|
|
26177
26300
|
onBlur: e => {
|
|
26178
26301
|
onBlur?.(e);
|
|
26179
|
-
|
|
26302
|
+
hideSuggestions(e);
|
|
26180
26303
|
},
|
|
26181
26304
|
onInput: e => {
|
|
26182
26305
|
onInput?.(e);
|
|
26183
|
-
|
|
26306
|
+
showSuggestions(e);
|
|
26184
26307
|
},
|
|
26185
|
-
...rest
|
|
26308
|
+
...rest,
|
|
26309
|
+
children: children || jsx(InputRightSlot, {
|
|
26310
|
+
onClick: e => {
|
|
26311
|
+
if (expanded) {
|
|
26312
|
+
hideSuggestions(e);
|
|
26313
|
+
} else {
|
|
26314
|
+
showSuggestions(e);
|
|
26315
|
+
}
|
|
26316
|
+
},
|
|
26317
|
+
children: jsx(Icon, {
|
|
26318
|
+
color: "rgba(28, 43, 52, 0.5)",
|
|
26319
|
+
children: jsx(ChevronDownSvg, {})
|
|
26320
|
+
})
|
|
26321
|
+
})
|
|
26186
26322
|
});
|
|
26187
26323
|
};
|
|
26188
26324
|
const InputTextualPlain = props => {
|
|
@@ -26201,8 +26337,8 @@ const InputTextualPlain = props => {
|
|
|
26201
26337
|
autoFocus,
|
|
26202
26338
|
autoFocusVisible,
|
|
26203
26339
|
autoSelect,
|
|
26204
|
-
|
|
26205
|
-
|
|
26340
|
+
basePseudoState,
|
|
26341
|
+
children,
|
|
26206
26342
|
...rest
|
|
26207
26343
|
} = props;
|
|
26208
26344
|
const defaultRef = useRef();
|
|
@@ -26261,17 +26397,45 @@ const InputTextualPlain = props => {
|
|
|
26261
26397
|
});
|
|
26262
26398
|
};
|
|
26263
26399
|
const renderInputMemoized = useCallback(renderInput, [type, uiState, innerValue, innerOnInput, innerId]);
|
|
26264
|
-
let
|
|
26265
|
-
if (
|
|
26266
|
-
|
|
26267
|
-
|
|
26268
|
-
|
|
26269
|
-
|
|
26270
|
-
|
|
26271
|
-
|
|
26272
|
-
|
|
26273
|
-
|
|
26274
|
-
|
|
26400
|
+
let innerChildren;
|
|
26401
|
+
if (children) {
|
|
26402
|
+
innerChildren = children;
|
|
26403
|
+
} else if (type === "search") {
|
|
26404
|
+
innerChildren = jsxs(Fragment, {
|
|
26405
|
+
children: [jsx(InputLeftSlot, {
|
|
26406
|
+
children: jsx(Icon, {
|
|
26407
|
+
color: "rgba(28, 43, 52, 0.5)",
|
|
26408
|
+
children: jsx(SearchSvg, {})
|
|
26409
|
+
})
|
|
26410
|
+
}), jsx(InputRightSlot, {
|
|
26411
|
+
hideWhileEmpty: true,
|
|
26412
|
+
onClick: () => {
|
|
26413
|
+
uiStateController.setUIState("", {
|
|
26414
|
+
trigger: "cancel_button"
|
|
26415
|
+
});
|
|
26416
|
+
ref.current.value = "";
|
|
26417
|
+
ref.current.dispatchEvent(new Event("navi_delete_content"));
|
|
26418
|
+
},
|
|
26419
|
+
children: jsx(Icon, {
|
|
26420
|
+
color: "rgba(28, 43, 52, 0.5)",
|
|
26421
|
+
children: jsx(CloseSvg, {})
|
|
26422
|
+
})
|
|
26423
|
+
})]
|
|
26424
|
+
});
|
|
26425
|
+
} else if (type === "email") {
|
|
26426
|
+
innerChildren = jsx(InputLeftSlot, {
|
|
26427
|
+
children: jsx(Icon, {
|
|
26428
|
+
color: "rgba(28, 43, 52, 0.5)",
|
|
26429
|
+
children: jsx(EmailSvg, {})
|
|
26430
|
+
})
|
|
26431
|
+
});
|
|
26432
|
+
} else if (type === "tel") {
|
|
26433
|
+
innerChildren = jsx(InputLeftSlot, {
|
|
26434
|
+
children: jsx(Icon, {
|
|
26435
|
+
color: "rgba(28, 43, 52, 0.5)",
|
|
26436
|
+
children: jsx(PhoneSvg, {})
|
|
26437
|
+
})
|
|
26438
|
+
});
|
|
26275
26439
|
}
|
|
26276
26440
|
return jsxs(Box, {
|
|
26277
26441
|
as: "span",
|
|
@@ -26281,6 +26445,7 @@ const InputTextualPlain = props => {
|
|
|
26281
26445
|
pseudoStateSelector: ".navi_native_input",
|
|
26282
26446
|
visualSelector: ".navi_native_input",
|
|
26283
26447
|
basePseudoState: {
|
|
26448
|
+
...basePseudoState,
|
|
26284
26449
|
":read-only": innerReadOnly,
|
|
26285
26450
|
":disabled": innerDisabled,
|
|
26286
26451
|
":-navi-loading": innerLoading
|
|
@@ -26289,48 +26454,20 @@ const InputTextualPlain = props => {
|
|
|
26289
26454
|
pseudoElements: InputPseudoElements,
|
|
26290
26455
|
hasChildFunction: true,
|
|
26291
26456
|
baseChildPropSet: InputChildPropSet,
|
|
26292
|
-
"data-start-icon": innerIcon ? "" : undefined,
|
|
26293
|
-
"data-end-icon": cancelButton ? "" : undefined,
|
|
26294
26457
|
...remainingProps,
|
|
26295
26458
|
ref: undefined,
|
|
26296
26459
|
children: [jsx(LoaderBackground, {
|
|
26297
26460
|
loading: innerLoading,
|
|
26298
26461
|
color: "var(--loader-color)",
|
|
26299
26462
|
inset: -1
|
|
26300
|
-
}),
|
|
26301
|
-
|
|
26302
|
-
|
|
26303
|
-
|
|
26304
|
-
|
|
26305
|
-
flex: true,
|
|
26306
|
-
alignY: "center",
|
|
26307
|
-
children: jsx(Icon, {
|
|
26308
|
-
color: "rgba(28, 43, 52, 0.5)",
|
|
26309
|
-
children: innerIcon
|
|
26310
|
-
})
|
|
26311
|
-
}), renderInputMemoized, cancelButton && jsx("label", {
|
|
26312
|
-
htmlFor: innerId,
|
|
26313
|
-
"data-readonly": innerReadOnly ? "" : undefined,
|
|
26314
|
-
"data-disabled": innerDisabled ? "" : undefined,
|
|
26315
|
-
className: "navi_input_end_button",
|
|
26316
|
-
onMouseDown: e => {
|
|
26317
|
-
e.preventDefault(); // keep focus in the input
|
|
26318
|
-
},
|
|
26319
|
-
onClick: () => {
|
|
26320
|
-
if (innerReadOnly || innerDisabled) {
|
|
26321
|
-
return;
|
|
26322
|
-
}
|
|
26323
|
-
uiStateController.setUIState("", {
|
|
26324
|
-
trigger: "cancel_button"
|
|
26325
|
-
});
|
|
26326
|
-
ref.current.value = "";
|
|
26327
|
-
ref.current.dispatchEvent(new Event("navi_delete_content"));
|
|
26463
|
+
}), renderInputMemoized, innerChildren ? jsx(InputNativeContext.Provider, {
|
|
26464
|
+
value: {
|
|
26465
|
+
id: innerId,
|
|
26466
|
+
readOnly: innerReadOnly,
|
|
26467
|
+
disabled: innerDisabled
|
|
26328
26468
|
},
|
|
26329
|
-
children:
|
|
26330
|
-
|
|
26331
|
-
children: jsx(CloseSvg, {})
|
|
26332
|
-
})
|
|
26333
|
-
})]
|
|
26469
|
+
children: innerChildren
|
|
26470
|
+
}) : null]
|
|
26334
26471
|
});
|
|
26335
26472
|
};
|
|
26336
26473
|
const InputTextualWithAction = props => {
|
|
@@ -27058,553 +27195,32 @@ const Group = ({
|
|
|
27058
27195
|
});
|
|
27059
27196
|
};
|
|
27060
27197
|
|
|
27061
|
-
const
|
|
27062
|
-
const
|
|
27063
|
-
|
|
27064
|
-
|
|
27065
|
-
|
|
27066
|
-
|
|
27067
|
-
|
|
27068
|
-
|
|
27069
|
-
|
|
27070
|
-
}) => {
|
|
27071
|
-
// Reset on each render to start fresh
|
|
27072
|
-
tracker.reset();
|
|
27073
|
-
return jsx(ItemTrackerContext.Provider, {
|
|
27074
|
-
value: tracker,
|
|
27075
|
-
children: children
|
|
27076
|
-
});
|
|
27077
|
-
};
|
|
27078
|
-
ItemTrackerProvider.items = items;
|
|
27079
|
-
return {
|
|
27080
|
-
ItemTrackerProvider,
|
|
27081
|
-
items,
|
|
27082
|
-
registerItem: data => {
|
|
27083
|
-
const index = itemCountRef.current++;
|
|
27084
|
-
items[index] = data;
|
|
27085
|
-
return index;
|
|
27086
|
-
},
|
|
27087
|
-
getItem: index => {
|
|
27088
|
-
return items[index];
|
|
27089
|
-
},
|
|
27090
|
-
getAllItems: () => {
|
|
27091
|
-
return items;
|
|
27092
|
-
},
|
|
27093
|
-
reset: () => {
|
|
27094
|
-
items.length = 0;
|
|
27095
|
-
itemCountRef.current = 0;
|
|
27198
|
+
const RadioList = props => {
|
|
27199
|
+
const uiStateController = useUIGroupStateController(props, "radio_list", {
|
|
27200
|
+
childComponentType: "radio",
|
|
27201
|
+
aggregateChildStates: childUIStateControllers => {
|
|
27202
|
+
let activeValue;
|
|
27203
|
+
for (const childUIStateController of childUIStateControllers) {
|
|
27204
|
+
if (childUIStateController.uiState) {
|
|
27205
|
+
activeValue = childUIStateController.uiState;
|
|
27206
|
+
break;
|
|
27096
27207
|
}
|
|
27097
|
-
}
|
|
27098
|
-
|
|
27099
|
-
return tracker.ItemTrackerProvider;
|
|
27100
|
-
};
|
|
27101
|
-
const useTrackItem = data => {
|
|
27102
|
-
const tracker = useContext(ItemTrackerContext);
|
|
27103
|
-
if (!tracker) {
|
|
27104
|
-
throw new Error("useTrackItem must be used within SimpleItemTrackerProvider");
|
|
27105
|
-
}
|
|
27106
|
-
return tracker.registerItem(data);
|
|
27107
|
-
};
|
|
27108
|
-
const useTrackedItem = index => {
|
|
27109
|
-
const trackedItems = useTrackedItems();
|
|
27110
|
-
const item = trackedItems[index];
|
|
27111
|
-
return item;
|
|
27112
|
-
};
|
|
27113
|
-
const useTrackedItems = () => {
|
|
27114
|
-
const tracker = useContext(ItemTrackerContext);
|
|
27115
|
-
if (!tracker) {
|
|
27116
|
-
throw new Error("useTrackedItems must be used within SimpleItemTrackerProvider");
|
|
27208
|
+
}
|
|
27209
|
+
return activeValue;
|
|
27117
27210
|
}
|
|
27118
|
-
|
|
27119
|
-
|
|
27120
|
-
|
|
27121
|
-
|
|
27122
|
-
|
|
27123
|
-
|
|
27124
|
-
|
|
27125
|
-
|
|
27126
|
-
|
|
27127
|
-
|
|
27128
|
-
|
|
27129
|
-
|
|
27130
|
-
|
|
27131
|
-
* <Suggestion value="b">Option B</Suggestion>
|
|
27132
|
-
* </SuggestionList>
|
|
27133
|
-
*
|
|
27134
|
-
* CSS vars on .navi_suggestion_list:
|
|
27135
|
-
* --suggestion-list-border-radius, --suggestion-list-border-width, --suggestion-list-border-color, --suggestion-list-background-color, --suggestion-list-max-height
|
|
27136
|
-
*
|
|
27137
|
-
* CSS vars on .navi_suggestion:
|
|
27138
|
-
* --suggestion-padding, --suggestion-color, --suggestion-background-color, --suggestion-font-weight
|
|
27139
|
-
* --suggestion-color-hover, --suggestion-background-color-hover
|
|
27140
|
-
* --suggestion-color-pointed, --suggestion-background-color-pointed
|
|
27141
|
-
* --suggestion-color-selected, --suggestion-background-color-selected, --suggestion-font-weight-selected
|
|
27142
|
-
* --suggestion-color-pointed-selected, --suggestion-background-color-pointed-selected
|
|
27143
|
-
* --suggestion-color-highlight, --suggestion-background-color-highlight
|
|
27144
|
-
*
|
|
27145
|
-
* CSS vars on .navi_suggestion_group_label:
|
|
27146
|
-
* --suggestion-group-label-padding, --suggestion-group-label-color, --suggestion-group-label-font-size, --suggestion-group-label-font-weight
|
|
27147
|
-
*/
|
|
27148
|
-
|
|
27149
|
-
const css$g = /* css */`
|
|
27150
|
-
@layer navi {
|
|
27151
|
-
.navi_suggestion_list {
|
|
27152
|
-
--suggestion-list-border-radius: 4px;
|
|
27153
|
-
--suggestion-list-border-width: 1px;
|
|
27154
|
-
--suggestion-list-border-color: light-dark(#ccc, #555);
|
|
27155
|
-
--suggestion-list-background-color: light-dark(#fff, #1e1e1e);
|
|
27156
|
-
--suggestion-list-max-height: 220px;
|
|
27157
|
-
}
|
|
27158
|
-
.navi_suggestion_group_label {
|
|
27159
|
-
--suggestion-group-label-padding: 4px 12px 2px;
|
|
27160
|
-
--suggestion-group-label-color: light-dark(#888, #aaa);
|
|
27161
|
-
--suggestion-group-label-font-size: 0.75em;
|
|
27162
|
-
--suggestion-group-label-font-weight: 600;
|
|
27163
|
-
}
|
|
27164
|
-
.navi_suggestion {
|
|
27165
|
-
--suggestion-padding: 8px 12px;
|
|
27166
|
-
--suggestion-color: inherit;
|
|
27167
|
-
--suggestion-background-color: transparent;
|
|
27168
|
-
--suggestion-font-weight: inherit;
|
|
27169
|
-
|
|
27170
|
-
/* Hover (mouse) */
|
|
27171
|
-
--suggestion-color-hover: var(--suggestion-color);
|
|
27172
|
-
--suggestion-background-color-hover: light-dark(#f5f5f5, #2a2a2a);
|
|
27173
|
-
|
|
27174
|
-
/* Pointed (keyboard navigation position) */
|
|
27175
|
-
--suggestion-color-pointed: var(--suggestion-color);
|
|
27176
|
-
--suggestion-background-color-pointed: light-dark(#e8f0fe, #1c3a6e);
|
|
27177
|
-
|
|
27178
|
-
/* Selected */
|
|
27179
|
-
--suggestion-color-selected: light-dark(#1a73e8, #7baaf7);
|
|
27180
|
-
--suggestion-background-color-selected: light-dark(#e8f0fe, #1c3a6e);
|
|
27181
|
-
--suggestion-font-weight-selected: 500;
|
|
27182
|
-
|
|
27183
|
-
/* Highlight (CSS Highlight API match) */
|
|
27184
|
-
--suggestion-color-highlight: inherit;
|
|
27185
|
-
--suggestion-background-color-highlight: #ffe066;
|
|
27186
|
-
--suggestion-color-pointed-selected: var(--suggestion-color-selected);
|
|
27187
|
-
--suggestion-background-color-pointed-selected: light-dark(
|
|
27188
|
-
#d2e3fc,
|
|
27189
|
-
#174ea6
|
|
27190
|
-
);
|
|
27191
|
-
}
|
|
27192
|
-
}
|
|
27193
|
-
|
|
27194
|
-
.navi_suggestion_list {
|
|
27195
|
-
--x-border-radius: var(--suggestion-list-border-radius);
|
|
27196
|
-
--x-border-width: var(--suggestion-list-border-width);
|
|
27197
|
-
--x-border-color: var(--suggestion-list-border-color);
|
|
27198
|
-
--x-background-color: var(--suggestion-list-background-color);
|
|
27199
|
-
box-sizing: border-box;
|
|
27200
|
-
max-height: var(--suggestion-list-max-height);
|
|
27201
|
-
|
|
27202
|
-
margin: 0;
|
|
27203
|
-
padding: 0;
|
|
27204
|
-
list-style: none;
|
|
27205
|
-
background-color: var(--x-background-color);
|
|
27206
|
-
border: var(--x-border-width) solid var(--x-border-color);
|
|
27207
|
-
border-radius: var(--x-border-radius);
|
|
27208
|
-
outline: none;
|
|
27209
|
-
overflow-y: auto;
|
|
27210
|
-
|
|
27211
|
-
/* Popover reset — browser adds border, background, padding, margin by default */
|
|
27212
|
-
&[popover] {
|
|
27213
|
-
position: fixed;
|
|
27214
|
-
inset: unset;
|
|
27215
|
-
margin: 0;
|
|
27216
|
-
padding: 0;
|
|
27217
|
-
border: none;
|
|
27218
|
-
}
|
|
27219
|
-
}
|
|
27220
|
-
::highlight(navi-suggestion-match) {
|
|
27221
|
-
color: var(--suggestion-color-highlight);
|
|
27222
|
-
background-color: var(--suggestion-background-color-highlight);
|
|
27223
|
-
}
|
|
27224
|
-
.navi_suggestion {
|
|
27225
|
-
--x-color: var(--suggestion-color);
|
|
27226
|
-
--x-background-color: var(--suggestion-background-color);
|
|
27227
|
-
--x-font-weight: var(--suggestion-font-weight);
|
|
27228
|
-
|
|
27229
|
-
padding: var(--suggestion-padding);
|
|
27230
|
-
color: var(--x-color);
|
|
27231
|
-
font-weight: var(--x-font-weight);
|
|
27232
|
-
background-color: var(--x-background-color);
|
|
27233
|
-
cursor: pointer;
|
|
27234
|
-
user-select: none;
|
|
27235
|
-
|
|
27236
|
-
&:hover {
|
|
27237
|
-
--x-color: var(--suggestion-color-hover);
|
|
27238
|
-
--x-background-color: var(--suggestion-background-color-hover);
|
|
27239
|
-
}
|
|
27240
|
-
|
|
27241
|
-
&[data-pointed] {
|
|
27242
|
-
--x-color: var(--suggestion-color-pointed);
|
|
27243
|
-
--x-background-color: var(--suggestion-background-color-pointed);
|
|
27244
|
-
}
|
|
27245
|
-
|
|
27246
|
-
&[data-selected] {
|
|
27247
|
-
--x-color: var(--suggestion-color-selected);
|
|
27248
|
-
--x-background-color: var(--suggestion-background-color-selected);
|
|
27249
|
-
--x-font-weight: var(--suggestion-font-weight-selected);
|
|
27250
|
-
}
|
|
27251
|
-
|
|
27252
|
-
&[data-pointed][data-selected] {
|
|
27253
|
-
--x-color: var(--suggestion-color-pointed-selected);
|
|
27254
|
-
--x-background-color: var(--suggestion-background-color-pointed-selected);
|
|
27255
|
-
}
|
|
27256
|
-
}
|
|
27257
|
-
.navi_suggestion_group_label {
|
|
27258
|
-
position: sticky;
|
|
27259
|
-
top: 0;
|
|
27260
|
-
z-index: 1;
|
|
27261
|
-
display: block;
|
|
27262
|
-
padding: var(--suggestion-group-label-padding);
|
|
27263
|
-
color: var(--suggestion-group-label-color);
|
|
27264
|
-
font-weight: var(--suggestion-group-label-font-weight);
|
|
27265
|
-
font-size: var(--suggestion-group-label-font-size);
|
|
27266
|
-
text-transform: uppercase;
|
|
27267
|
-
letter-spacing: 0.05em;
|
|
27268
|
-
background-color: var(--suggestion-group-label-background-color);
|
|
27269
|
-
user-select: none;
|
|
27270
|
-
}
|
|
27271
|
-
`;
|
|
27272
|
-
const SuggestionListStyleCSSVars = {
|
|
27273
|
-
borderRadius: "--suggestion-list-border-radius",
|
|
27274
|
-
borderWidth: "--suggestion-list-border-width",
|
|
27275
|
-
borderColor: "--suggestion-list-border-color",
|
|
27276
|
-
backgroundColor: "--suggestion-list-background-color",
|
|
27277
|
-
maxHeight: "--suggestion-list-max-height"
|
|
27278
|
-
};
|
|
27279
|
-
const SuggestionStyleCSSVars = {
|
|
27280
|
-
"padding": "--suggestion-padding",
|
|
27281
|
-
"color": "--suggestion-color",
|
|
27282
|
-
"backgroundColor": "--suggestion-background-color",
|
|
27283
|
-
"fontWeight": "--suggestion-font-weight",
|
|
27284
|
-
":-navi-pointed": {
|
|
27285
|
-
color: "--suggestion-color-pointed",
|
|
27286
|
-
backgroundColor: "--suggestion-background-color-pointed"
|
|
27287
|
-
},
|
|
27288
|
-
":hover": {
|
|
27289
|
-
color: "--suggestion-color-hover",
|
|
27290
|
-
backgroundColor: "--suggestion-background-color-hover"
|
|
27291
|
-
},
|
|
27292
|
-
":-navi-selected": {
|
|
27293
|
-
color: "--suggestion-color-selected",
|
|
27294
|
-
backgroundColor: "--suggestion-background-color-selected",
|
|
27295
|
-
fontWeight: "--suggestion-font-weight-selected"
|
|
27296
|
-
},
|
|
27297
|
-
"::highlight": {
|
|
27298
|
-
color: "--suggestion-color-highlight",
|
|
27299
|
-
backgroundColor: "--suggestion-background-color-highlight"
|
|
27300
|
-
}
|
|
27301
|
-
};
|
|
27302
|
-
|
|
27303
|
-
/**
|
|
27304
|
-
* Context OptionList provides downward to its Option children.
|
|
27305
|
-
*/
|
|
27306
|
-
const SuggestionListContext = createContext(null);
|
|
27307
|
-
const SuggestionList = ({
|
|
27308
|
-
popover,
|
|
27309
|
-
onChange: onChangeProp,
|
|
27310
|
-
highlight,
|
|
27311
|
-
children,
|
|
27312
|
-
...rest
|
|
27313
|
-
}) => {
|
|
27314
|
-
import.meta.css = [css$g, "@jsenv/navi/src/field/suggestion_list.jsx"];
|
|
27315
|
-
const ItemTrackerProvider = useSuggestionItemTrackerProvider();
|
|
27316
|
-
const [pointedValue, setPointedValue] = useState(null);
|
|
27317
|
-
const pointedValueRef = useRef(null);
|
|
27318
|
-
pointedValueRef.current = pointedValue;
|
|
27319
|
-
const ownId = useId();
|
|
27320
|
-
const id = rest.id ?? ownId;
|
|
27321
|
-
const defaultRef = useRef(null);
|
|
27322
|
-
const ref = rest.ref || defaultRef;
|
|
27323
|
-
useLayoutEffect(() => {
|
|
27324
|
-
if (!CSS.highlights) {
|
|
27325
|
-
return undefined;
|
|
27326
|
-
}
|
|
27327
|
-
if (!highlight) {
|
|
27328
|
-
CSS.highlights.delete("navi-suggestion-match");
|
|
27329
|
-
return undefined;
|
|
27330
|
-
}
|
|
27331
|
-
const listEl = ref.current;
|
|
27332
|
-
if (!listEl) {
|
|
27333
|
-
return undefined;
|
|
27334
|
-
}
|
|
27335
|
-
const ranges = [];
|
|
27336
|
-
const lowerHighlight = highlight.toLowerCase();
|
|
27337
|
-
for (const suggestionEl of listEl.querySelectorAll("[role='option']")) {
|
|
27338
|
-
const walker = document.createTreeWalker(suggestionEl, NodeFilter.SHOW_TEXT);
|
|
27339
|
-
let node;
|
|
27340
|
-
while (node = walker.nextNode()) {
|
|
27341
|
-
const text = node.textContent;
|
|
27342
|
-
const lowerText = text.toLowerCase();
|
|
27343
|
-
let index = lowerText.indexOf(lowerHighlight);
|
|
27344
|
-
while (index !== -1) {
|
|
27345
|
-
const range = new Range();
|
|
27346
|
-
range.setStart(node, index);
|
|
27347
|
-
range.setEnd(node, index + highlight.length);
|
|
27348
|
-
ranges.push(range);
|
|
27349
|
-
index = lowerText.indexOf(lowerHighlight, index + 1);
|
|
27350
|
-
}
|
|
27351
|
-
}
|
|
27352
|
-
}
|
|
27353
|
-
if (ranges.length === 0) {
|
|
27354
|
-
CSS.highlights.delete("navi-suggestion-match");
|
|
27355
|
-
} else {
|
|
27356
|
-
CSS.highlights.set("navi-suggestion-match", new Highlight(...ranges));
|
|
27357
|
-
}
|
|
27358
|
-
return () => {
|
|
27359
|
-
CSS.highlights.delete("navi-suggestion-match");
|
|
27360
|
-
};
|
|
27361
|
-
}, [highlight, children]);
|
|
27362
|
-
const effectiveOnChange = popover ? value => {
|
|
27363
|
-
onChangeProp?.(value);
|
|
27364
|
-
ref.current?.dispatchEvent(new CustomEvent("navi_suggestion_list_selected", {
|
|
27365
|
-
detail: {
|
|
27366
|
-
value
|
|
27367
|
-
},
|
|
27368
|
-
bubbles: true
|
|
27369
|
-
}));
|
|
27370
|
-
} : onChangeProp;
|
|
27371
|
-
const onChangeRef = useRef(effectiveOnChange);
|
|
27372
|
-
onChangeRef.current = effectiveOnChange;
|
|
27373
|
-
const navigate = direction => {
|
|
27374
|
-
const values = ItemTrackerProvider.items.filter(item => !item.hidden).map(item => item.value);
|
|
27375
|
-
if (values.length === 0) {
|
|
27376
|
-
return false;
|
|
27377
|
-
}
|
|
27378
|
-
const current = pointedValueRef.current;
|
|
27379
|
-
if (direction === "down") {
|
|
27380
|
-
const idx = current === null ? -1 : values.indexOf(current);
|
|
27381
|
-
setPointedValue(values[idx < values.length - 1 ? idx + 1 : idx]);
|
|
27382
|
-
} else if (direction === "up") {
|
|
27383
|
-
const idx = current === null ? -1 : values.indexOf(current);
|
|
27384
|
-
setPointedValue(values[idx > 0 ? idx - 1 : 0]);
|
|
27385
|
-
} else if (direction === "first") {
|
|
27386
|
-
setPointedValue(values[0]);
|
|
27387
|
-
} else if (direction === "last") {
|
|
27388
|
-
setPointedValue(values[values.length - 1]);
|
|
27389
|
-
}
|
|
27390
|
-
return true;
|
|
27391
|
-
};
|
|
27392
|
-
|
|
27393
|
-
// Listen for commands dispatched by a linked Input (combobox mode)
|
|
27394
|
-
const noopRef = useRef(null);
|
|
27395
|
-
useEffect(() => {
|
|
27396
|
-
if (!popover || !ref.current) {
|
|
27397
|
-
return undefined;
|
|
27398
|
-
}
|
|
27399
|
-
const el = ref.current;
|
|
27400
|
-
const onNavigate = e => {
|
|
27401
|
-
navigate(e.detail.direction);
|
|
27402
|
-
};
|
|
27403
|
-
const onConfirm = e => {
|
|
27404
|
-
const current = pointedValueRef.current;
|
|
27405
|
-
if (current !== null) {
|
|
27406
|
-
onChangeRef.current?.(current);
|
|
27407
|
-
e.preventDefault();
|
|
27408
|
-
}
|
|
27409
|
-
};
|
|
27410
|
-
const onClear = () => {
|
|
27411
|
-
setPointedValue(null);
|
|
27412
|
-
};
|
|
27413
|
-
el.addEventListener("navi_suggestion_list_navigate", onNavigate);
|
|
27414
|
-
el.addEventListener("navi_suggestion_list_confirm", onConfirm);
|
|
27415
|
-
el.addEventListener("navi_suggestion_list_clear", onClear);
|
|
27416
|
-
return () => {
|
|
27417
|
-
el.removeEventListener("navi_suggestion_list_navigate", onNavigate);
|
|
27418
|
-
el.removeEventListener("navi_suggestion_list_confirm", onConfirm);
|
|
27419
|
-
el.removeEventListener("navi_suggestion_list_clear", onClear);
|
|
27420
|
-
};
|
|
27421
|
-
}, [popover]);
|
|
27422
|
-
useKeyboardShortcuts(popover ? noopRef : ref, [{
|
|
27423
|
-
key: "arrowdown",
|
|
27424
|
-
description: "Point to next suggestion",
|
|
27425
|
-
handler: () => navigate("down")
|
|
27426
|
-
}, {
|
|
27427
|
-
key: "arrowup",
|
|
27428
|
-
description: "Point to previous suggestion",
|
|
27429
|
-
handler: () => navigate("up")
|
|
27430
|
-
}, {
|
|
27431
|
-
key: "home",
|
|
27432
|
-
description: "Point to first suggestion",
|
|
27433
|
-
handler: () => navigate("first")
|
|
27434
|
-
}, {
|
|
27435
|
-
key: "end",
|
|
27436
|
-
description: "Point to last suggestion",
|
|
27437
|
-
handler: () => navigate("last")
|
|
27438
|
-
}, {
|
|
27439
|
-
key: "enter",
|
|
27440
|
-
description: "Confirm pointed suggestion",
|
|
27441
|
-
handler: () => {
|
|
27442
|
-
const current = pointedValueRef.current;
|
|
27443
|
-
if (current === null) {
|
|
27444
|
-
return false;
|
|
27445
|
-
}
|
|
27446
|
-
onChangeRef.current?.(current);
|
|
27447
|
-
return true;
|
|
27448
|
-
}
|
|
27449
|
-
}, {
|
|
27450
|
-
key: "escape",
|
|
27451
|
-
description: "Clear pointed suggestion",
|
|
27452
|
-
handler: () => {
|
|
27453
|
-
setPointedValue(null);
|
|
27454
|
-
return true;
|
|
27455
|
-
}
|
|
27456
|
-
}]);
|
|
27457
|
-
const suggestionListContext = {
|
|
27458
|
-
pointedValue,
|
|
27459
|
-
setPointedValue,
|
|
27460
|
-
onSelect: effectiveOnChange
|
|
27461
|
-
};
|
|
27462
|
-
return jsx(Box, {
|
|
27463
|
-
as: "ul",
|
|
27464
|
-
ref: ref,
|
|
27465
|
-
id: id,
|
|
27466
|
-
role: "listbox",
|
|
27467
|
-
tabIndex: popover ? -1 : 0,
|
|
27468
|
-
popover: popover ? "manual" : undefined,
|
|
27469
|
-
...rest,
|
|
27470
|
-
baseClassName: "navi_suggestion_list",
|
|
27471
|
-
styleCSSVars: SuggestionListStyleCSSVars,
|
|
27472
|
-
children: jsx(SuggestionListContext.Provider, {
|
|
27473
|
-
value: suggestionListContext,
|
|
27474
|
-
children: jsx(ItemTrackerProvider, {
|
|
27475
|
-
children: children
|
|
27476
|
-
})
|
|
27477
|
-
})
|
|
27478
|
-
});
|
|
27479
|
-
};
|
|
27480
|
-
const SUGGESTION_PSEUDO_CLASSES = [":-navi-pointed", ":-navi-selected"];
|
|
27481
|
-
const SUGGESTION_PSEUDO_ELEMENTS = ["::highlight"];
|
|
27482
|
-
const Suggestion = ({
|
|
27483
|
-
value,
|
|
27484
|
-
selected,
|
|
27485
|
-
hidden,
|
|
27486
|
-
children,
|
|
27487
|
-
...rest
|
|
27488
|
-
}) => {
|
|
27489
|
-
import.meta.css = [css$g, "@jsenv/navi/src/field/suggestion_list.jsx"];
|
|
27490
|
-
const suggestionId = useId();
|
|
27491
|
-
const id = rest.id || suggestionId;
|
|
27492
|
-
useTrackSuggestion({
|
|
27493
|
-
value,
|
|
27494
|
-
suggestionId: id,
|
|
27495
|
-
hidden
|
|
27496
|
-
});
|
|
27497
|
-
const {
|
|
27498
|
-
pointedValue,
|
|
27499
|
-
setPointedValue,
|
|
27500
|
-
onSelect
|
|
27501
|
-
} = useContext(SuggestionListContext);
|
|
27502
|
-
const isPointed = pointedValue === value;
|
|
27503
|
-
const suggestionRef = useRef(null);
|
|
27504
|
-
useEffect(() => {
|
|
27505
|
-
const suggestionEl = suggestionRef.current;
|
|
27506
|
-
if (isPointed && suggestionEl) {
|
|
27507
|
-
suggestionEl.scrollIntoView({
|
|
27508
|
-
block: "nearest"
|
|
27509
|
-
});
|
|
27510
|
-
}
|
|
27511
|
-
}, [isPointed]);
|
|
27512
|
-
return jsx(Box, {
|
|
27513
|
-
as: "li",
|
|
27514
|
-
ref: suggestionRef,
|
|
27515
|
-
baseClassName: "navi_suggestion",
|
|
27516
|
-
id: suggestionId,
|
|
27517
|
-
role: "option",
|
|
27518
|
-
"aria-selected": selected,
|
|
27519
|
-
"aria-hidden": hidden ? true : undefined,
|
|
27520
|
-
hidden: hidden,
|
|
27521
|
-
basePseudoState: {
|
|
27522
|
-
":-navi-pointed": isPointed,
|
|
27523
|
-
":-navi-selected": selected
|
|
27524
|
-
},
|
|
27525
|
-
pseudoClasses: SUGGESTION_PSEUDO_CLASSES,
|
|
27526
|
-
pseudoElements: SUGGESTION_PSEUDO_ELEMENTS,
|
|
27527
|
-
styleCSSVars: SuggestionStyleCSSVars,
|
|
27528
|
-
onMouseEnter: () => {
|
|
27529
|
-
if (!hidden) {
|
|
27530
|
-
setPointedValue(value);
|
|
27531
|
-
}
|
|
27532
|
-
},
|
|
27533
|
-
onMouseLeave: () => {
|
|
27534
|
-
if (!hidden) {
|
|
27535
|
-
setPointedValue(null);
|
|
27536
|
-
}
|
|
27537
|
-
},
|
|
27538
|
-
onMouseDown: e => {
|
|
27539
|
-
if (hidden || e.button !== 0) {
|
|
27540
|
-
return;
|
|
27541
|
-
}
|
|
27542
|
-
onSelect?.(value);
|
|
27543
|
-
},
|
|
27544
|
-
...rest,
|
|
27545
|
-
children: children
|
|
27546
|
-
});
|
|
27547
|
-
};
|
|
27548
|
-
const SuggestionGroup = ({
|
|
27549
|
-
label,
|
|
27550
|
-
children,
|
|
27551
|
-
...rest
|
|
27552
|
-
}) => {
|
|
27553
|
-
import.meta.css = [css$g, "@jsenv/navi/src/field/suggestion_list.jsx"];
|
|
27554
|
-
const groupId = useId();
|
|
27555
|
-
return jsxs("li", {
|
|
27556
|
-
role: "presentation",
|
|
27557
|
-
...rest,
|
|
27558
|
-
children: [jsx("span", {
|
|
27559
|
-
id: groupId,
|
|
27560
|
-
role: "presentation",
|
|
27561
|
-
"aria-hidden": "true",
|
|
27562
|
-
style: {
|
|
27563
|
-
display: "contents"
|
|
27564
|
-
},
|
|
27565
|
-
children: typeof label === "string" ? jsx("span", {
|
|
27566
|
-
className: "navi_suggestion_group_label",
|
|
27567
|
-
children: label
|
|
27568
|
-
}) : label
|
|
27569
|
-
}), jsx("ul", {
|
|
27570
|
-
role: "group",
|
|
27571
|
-
"aria-labelledby": groupId,
|
|
27572
|
-
style: {
|
|
27573
|
-
margin: 0,
|
|
27574
|
-
padding: 0,
|
|
27575
|
-
listStyle: "none"
|
|
27576
|
-
},
|
|
27577
|
-
children: children
|
|
27578
|
-
})]
|
|
27579
|
-
});
|
|
27580
|
-
};
|
|
27581
|
-
|
|
27582
|
-
const RadioList = props => {
|
|
27583
|
-
const uiStateController = useUIGroupStateController(props, "radio_list", {
|
|
27584
|
-
childComponentType: "radio",
|
|
27585
|
-
aggregateChildStates: childUIStateControllers => {
|
|
27586
|
-
let activeValue;
|
|
27587
|
-
for (const childUIStateController of childUIStateControllers) {
|
|
27588
|
-
if (childUIStateController.uiState) {
|
|
27589
|
-
activeValue = childUIStateController.uiState;
|
|
27590
|
-
break;
|
|
27591
|
-
}
|
|
27592
|
-
}
|
|
27593
|
-
return activeValue;
|
|
27594
|
-
}
|
|
27595
|
-
});
|
|
27596
|
-
const uiState = useUIState(uiStateController);
|
|
27597
|
-
const radioList = renderActionableComponent(props, {
|
|
27598
|
-
Basic: RadioListBasic,
|
|
27599
|
-
WithAction: RadioListWithAction
|
|
27600
|
-
});
|
|
27601
|
-
return jsx(UIStateControllerContext.Provider, {
|
|
27602
|
-
value: uiStateController,
|
|
27603
|
-
children: jsx(UIStateContext.Provider, {
|
|
27604
|
-
value: uiState,
|
|
27605
|
-
children: radioList
|
|
27606
|
-
})
|
|
27607
|
-
});
|
|
27211
|
+
});
|
|
27212
|
+
const uiState = useUIState(uiStateController);
|
|
27213
|
+
const radioList = renderActionableComponent(props, {
|
|
27214
|
+
Basic: RadioListBasic,
|
|
27215
|
+
WithAction: RadioListWithAction
|
|
27216
|
+
});
|
|
27217
|
+
return jsx(UIStateControllerContext.Provider, {
|
|
27218
|
+
value: uiStateController,
|
|
27219
|
+
children: jsx(UIStateContext.Provider, {
|
|
27220
|
+
value: uiState,
|
|
27221
|
+
children: radioList
|
|
27222
|
+
})
|
|
27223
|
+
});
|
|
27608
27224
|
};
|
|
27609
27225
|
const Radio = InputRadio;
|
|
27610
27226
|
const RadioListBasic = props => {
|
|
@@ -27653,12 +27269,208 @@ const RadioListBasic = props => {
|
|
|
27653
27269
|
})
|
|
27654
27270
|
})
|
|
27655
27271
|
});
|
|
27656
|
-
};
|
|
27657
|
-
const RadioListWithAction = props => {
|
|
27658
|
-
const uiStateController = useContext(UIStateControllerContext);
|
|
27659
|
-
const uiState = useContext(UIStateContext);
|
|
27272
|
+
};
|
|
27273
|
+
const RadioListWithAction = props => {
|
|
27274
|
+
const uiStateController = useContext(UIStateControllerContext);
|
|
27275
|
+
const uiState = useContext(UIStateContext);
|
|
27276
|
+
const {
|
|
27277
|
+
action,
|
|
27278
|
+
onCancel,
|
|
27279
|
+
onActionPrevented,
|
|
27280
|
+
onActionStart,
|
|
27281
|
+
onActionAbort,
|
|
27282
|
+
onActionError,
|
|
27283
|
+
onActionEnd,
|
|
27284
|
+
actionErrorEffect,
|
|
27285
|
+
loading,
|
|
27286
|
+
children,
|
|
27287
|
+
...rest
|
|
27288
|
+
} = props;
|
|
27289
|
+
const innerRef = useRef();
|
|
27290
|
+
const [boundAction] = useActionBoundToOneParam(action, uiState);
|
|
27291
|
+
const {
|
|
27292
|
+
loading: actionLoading
|
|
27293
|
+
} = useActionStatus(boundAction);
|
|
27294
|
+
const executeAction = useExecuteAction(innerRef, {
|
|
27295
|
+
errorEffect: actionErrorEffect
|
|
27296
|
+
});
|
|
27297
|
+
const [actionRequester, setActionRequester] = useState(null);
|
|
27298
|
+
useActionEvents(innerRef, {
|
|
27299
|
+
onCancel: (e, reason) => {
|
|
27300
|
+
uiStateController.resetUIState(e);
|
|
27301
|
+
onCancel?.(e, reason);
|
|
27302
|
+
},
|
|
27303
|
+
onPrevented: onActionPrevented,
|
|
27304
|
+
onAction: actionEvent => {
|
|
27305
|
+
setActionRequester(actionEvent.detail.requester);
|
|
27306
|
+
executeAction(actionEvent);
|
|
27307
|
+
},
|
|
27308
|
+
onStart: onActionStart,
|
|
27309
|
+
onAbort: e => {
|
|
27310
|
+
uiStateController.resetUIState(e);
|
|
27311
|
+
onActionAbort?.(e);
|
|
27312
|
+
},
|
|
27313
|
+
onError: e => {
|
|
27314
|
+
uiStateController.resetUIState(e);
|
|
27315
|
+
onActionError?.(e);
|
|
27316
|
+
},
|
|
27317
|
+
onEnd: e => {
|
|
27318
|
+
onActionEnd?.(e);
|
|
27319
|
+
}
|
|
27320
|
+
});
|
|
27321
|
+
return jsx(RadioListBasic, {
|
|
27322
|
+
"data-action": boundAction,
|
|
27323
|
+
...rest,
|
|
27324
|
+
ref: innerRef,
|
|
27325
|
+
onChange: e => {
|
|
27326
|
+
const radio = e.target;
|
|
27327
|
+
const radioListContainer = innerRef.current;
|
|
27328
|
+
requestAction(radioListContainer, boundAction, {
|
|
27329
|
+
event: e,
|
|
27330
|
+
requester: radio,
|
|
27331
|
+
actionOrigin: "action_prop"
|
|
27332
|
+
});
|
|
27333
|
+
},
|
|
27334
|
+
loading: loading || actionLoading,
|
|
27335
|
+
children: jsx(LoadingElementContext.Provider, {
|
|
27336
|
+
value: actionRequester,
|
|
27337
|
+
children: children
|
|
27338
|
+
})
|
|
27339
|
+
});
|
|
27340
|
+
};
|
|
27341
|
+
|
|
27342
|
+
const useRefArray = (items, keyFromItem) => {
|
|
27343
|
+
const refMapRef = useRef(new Map());
|
|
27344
|
+
const previousKeySetRef = useRef(new Set());
|
|
27345
|
+
|
|
27346
|
+
return useMemo(() => {
|
|
27347
|
+
const refMap = refMapRef.current;
|
|
27348
|
+
const previousKeySet = previousKeySetRef.current;
|
|
27349
|
+
const currentKeySet = new Set();
|
|
27350
|
+
const refArray = [];
|
|
27351
|
+
|
|
27352
|
+
for (let i = 0; i < items.length; i++) {
|
|
27353
|
+
const item = items[i];
|
|
27354
|
+
const key = keyFromItem(item);
|
|
27355
|
+
currentKeySet.add(key);
|
|
27356
|
+
|
|
27357
|
+
const refForKey = refMap.get(key);
|
|
27358
|
+
if (refForKey) {
|
|
27359
|
+
refArray[i] = refForKey;
|
|
27360
|
+
} else {
|
|
27361
|
+
const newRef = createRef();
|
|
27362
|
+
refMap.set(key, newRef);
|
|
27363
|
+
refArray[i] = newRef;
|
|
27364
|
+
}
|
|
27365
|
+
}
|
|
27366
|
+
|
|
27367
|
+
for (const key of previousKeySet) {
|
|
27368
|
+
if (!currentKeySet.has(key)) {
|
|
27369
|
+
refMap.delete(key);
|
|
27370
|
+
}
|
|
27371
|
+
}
|
|
27372
|
+
previousKeySetRef.current = currentKeySet;
|
|
27373
|
+
|
|
27374
|
+
return refArray;
|
|
27375
|
+
}, [items]);
|
|
27376
|
+
};
|
|
27377
|
+
|
|
27378
|
+
installImportMetaCssBuild(import.meta);const useNavState = () => {};
|
|
27379
|
+
const css$g = /* css */`
|
|
27380
|
+
.navi_select[data-readonly] {
|
|
27381
|
+
pointer-events: none;
|
|
27382
|
+
}
|
|
27383
|
+
`;
|
|
27384
|
+
const Select = forwardRef((props, ref) => {
|
|
27385
|
+
import.meta.css = [css$g, "@jsenv/navi/src/field/select.jsx"];
|
|
27386
|
+
const select = renderActionableComponent(props, ref);
|
|
27387
|
+
return select;
|
|
27388
|
+
});
|
|
27389
|
+
const SelectControlled = forwardRef((props, ref) => {
|
|
27390
|
+
const {
|
|
27391
|
+
name,
|
|
27392
|
+
value,
|
|
27393
|
+
loading,
|
|
27394
|
+
disabled,
|
|
27395
|
+
readOnly,
|
|
27396
|
+
children,
|
|
27397
|
+
...rest
|
|
27398
|
+
} = props;
|
|
27399
|
+
const innerRef = useRef();
|
|
27400
|
+
useImperativeHandle(ref, () => innerRef.current);
|
|
27401
|
+
const selectElement = jsx("select", {
|
|
27402
|
+
className: "navi_select",
|
|
27403
|
+
ref: innerRef,
|
|
27404
|
+
"data-readonly": readOnly && !disabled ? "" : undefined,
|
|
27405
|
+
onKeyDown: e => {
|
|
27406
|
+
if (readOnly) {
|
|
27407
|
+
e.preventDefault();
|
|
27408
|
+
}
|
|
27409
|
+
},
|
|
27410
|
+
...rest,
|
|
27411
|
+
children: children.map(child => {
|
|
27412
|
+
const {
|
|
27413
|
+
label,
|
|
27414
|
+
readOnly: childReadOnly,
|
|
27415
|
+
disabled: childDisabled,
|
|
27416
|
+
loading: childLoading,
|
|
27417
|
+
value: childValue,
|
|
27418
|
+
...childRest
|
|
27419
|
+
} = child;
|
|
27420
|
+
return jsx("option", {
|
|
27421
|
+
name: name,
|
|
27422
|
+
value: childValue,
|
|
27423
|
+
selected: childValue === value,
|
|
27424
|
+
readOnly: readOnly || childReadOnly,
|
|
27425
|
+
disabled: disabled || childDisabled,
|
|
27426
|
+
loading: loading || childLoading,
|
|
27427
|
+
...childRest,
|
|
27428
|
+
children: label
|
|
27429
|
+
}, childValue);
|
|
27430
|
+
})
|
|
27431
|
+
});
|
|
27432
|
+
return jsx(LoaderBackground, {
|
|
27433
|
+
loading: loading,
|
|
27434
|
+
color: "light-dark(#355fcc, #3b82f6)",
|
|
27435
|
+
inset: -1,
|
|
27436
|
+
children: selectElement
|
|
27437
|
+
});
|
|
27438
|
+
});
|
|
27439
|
+
forwardRef((props, ref) => {
|
|
27440
|
+
const {
|
|
27441
|
+
value: initialValue,
|
|
27442
|
+
id,
|
|
27443
|
+
children,
|
|
27444
|
+
...rest
|
|
27445
|
+
} = props;
|
|
27446
|
+
const innerRef = useRef();
|
|
27447
|
+
useImperativeHandle(ref, () => innerRef.current);
|
|
27448
|
+
const [navState, setNavState] = useNavState();
|
|
27449
|
+
const valueAtStart = navState === undefined ? initialValue : navState;
|
|
27450
|
+
const [value, setValue] = useState(valueAtStart);
|
|
27451
|
+
useEffect(() => {
|
|
27452
|
+
setNavState(value);
|
|
27453
|
+
}, [value]);
|
|
27454
|
+
return jsx(SelectControlled, {
|
|
27455
|
+
ref: innerRef,
|
|
27456
|
+
value: value,
|
|
27457
|
+
onChange: event => {
|
|
27458
|
+
const select = event.target;
|
|
27459
|
+
const selectedValue = select.value;
|
|
27460
|
+
setValue(selectedValue);
|
|
27461
|
+
},
|
|
27462
|
+
...rest,
|
|
27463
|
+
children: children
|
|
27464
|
+
});
|
|
27465
|
+
});
|
|
27466
|
+
forwardRef((props, ref) => {
|
|
27660
27467
|
const {
|
|
27468
|
+
id,
|
|
27469
|
+
name,
|
|
27470
|
+
value: externalValue,
|
|
27471
|
+
valueSignal,
|
|
27661
27472
|
action,
|
|
27473
|
+
children,
|
|
27662
27474
|
onCancel,
|
|
27663
27475
|
onActionPrevented,
|
|
27664
27476
|
onActionStart,
|
|
@@ -27666,272 +27478,693 @@ const RadioListWithAction = props => {
|
|
|
27666
27478
|
onActionError,
|
|
27667
27479
|
onActionEnd,
|
|
27668
27480
|
actionErrorEffect,
|
|
27669
|
-
loading,
|
|
27670
|
-
children,
|
|
27671
27481
|
...rest
|
|
27672
27482
|
} = props;
|
|
27673
27483
|
const innerRef = useRef();
|
|
27674
|
-
|
|
27484
|
+
useImperativeHandle(ref, () => innerRef.current);
|
|
27485
|
+
const [navState, setNavState, resetNavState] = useNavState();
|
|
27486
|
+
const [boundAction, value, setValue, initialValue] = useActionBoundToOneParam(action, name);
|
|
27675
27487
|
const {
|
|
27676
27488
|
loading: actionLoading
|
|
27677
27489
|
} = useActionStatus(boundAction);
|
|
27678
27490
|
const executeAction = useExecuteAction(innerRef, {
|
|
27679
27491
|
errorEffect: actionErrorEffect
|
|
27680
27492
|
});
|
|
27681
|
-
|
|
27493
|
+
useEffect(() => {
|
|
27494
|
+
setNavState(value);
|
|
27495
|
+
}, [value]);
|
|
27496
|
+
const actionRequesterRef = useRef(null);
|
|
27682
27497
|
useActionEvents(innerRef, {
|
|
27683
27498
|
onCancel: (e, reason) => {
|
|
27684
|
-
|
|
27499
|
+
resetNavState();
|
|
27500
|
+
setValue(initialValue);
|
|
27685
27501
|
onCancel?.(e, reason);
|
|
27686
27502
|
},
|
|
27687
27503
|
onPrevented: onActionPrevented,
|
|
27688
27504
|
onAction: actionEvent => {
|
|
27689
|
-
|
|
27505
|
+
actionRequesterRef.current = actionEvent.detail.requester;
|
|
27690
27506
|
executeAction(actionEvent);
|
|
27691
27507
|
},
|
|
27692
27508
|
onStart: onActionStart,
|
|
27693
27509
|
onAbort: e => {
|
|
27694
|
-
|
|
27510
|
+
setValue(initialValue);
|
|
27695
27511
|
onActionAbort?.(e);
|
|
27696
27512
|
},
|
|
27697
|
-
onError:
|
|
27698
|
-
|
|
27699
|
-
onActionError?.(
|
|
27513
|
+
onError: error => {
|
|
27514
|
+
setValue(initialValue);
|
|
27515
|
+
onActionError?.(error);
|
|
27700
27516
|
},
|
|
27701
|
-
onEnd:
|
|
27702
|
-
|
|
27517
|
+
onEnd: () => {
|
|
27518
|
+
resetNavState();
|
|
27519
|
+
onActionEnd?.();
|
|
27703
27520
|
}
|
|
27704
27521
|
});
|
|
27705
|
-
|
|
27706
|
-
|
|
27707
|
-
...rest,
|
|
27522
|
+
const childRefArray = useRefArray(children, child => child.value);
|
|
27523
|
+
return jsx(SelectControlled, {
|
|
27708
27524
|
ref: innerRef,
|
|
27709
|
-
|
|
27710
|
-
|
|
27525
|
+
name: name,
|
|
27526
|
+
value: value,
|
|
27527
|
+
"data-action": boundAction,
|
|
27528
|
+
onChange: event => {
|
|
27529
|
+
const select = event.target;
|
|
27530
|
+
const selectedValue = select.value;
|
|
27531
|
+
setValue(selectedValue);
|
|
27711
27532
|
const radioListContainer = innerRef.current;
|
|
27533
|
+
const optionSelected = select.querySelector(`option[value="${selectedValue}"]`);
|
|
27712
27534
|
requestAction(radioListContainer, boundAction, {
|
|
27713
|
-
event
|
|
27714
|
-
requester:
|
|
27715
|
-
actionOrigin: "action_prop"
|
|
27535
|
+
event,
|
|
27536
|
+
requester: optionSelected
|
|
27716
27537
|
});
|
|
27717
27538
|
},
|
|
27718
|
-
|
|
27719
|
-
children:
|
|
27720
|
-
|
|
27721
|
-
|
|
27539
|
+
...rest,
|
|
27540
|
+
children: children.map((child, i) => {
|
|
27541
|
+
const childRef = childRefArray[i];
|
|
27542
|
+
return {
|
|
27543
|
+
...child,
|
|
27544
|
+
ref: childRef,
|
|
27545
|
+
loading: child.loading || actionLoading && actionRequesterRef.current === childRef.current,
|
|
27546
|
+
readOnly: child.readOnly || actionLoading
|
|
27547
|
+
};
|
|
27722
27548
|
})
|
|
27723
27549
|
});
|
|
27550
|
+
});
|
|
27551
|
+
|
|
27552
|
+
const createItemTracker = () => {
|
|
27553
|
+
const ItemTrackerContext = createContext();
|
|
27554
|
+
const useItemTrackerProvider = () => {
|
|
27555
|
+
const itemsRef = useRef([]);
|
|
27556
|
+
const items = itemsRef.current;
|
|
27557
|
+
const itemCountRef = useRef(0);
|
|
27558
|
+
const tracker = useMemo(() => {
|
|
27559
|
+
const ItemTrackerProvider = ({
|
|
27560
|
+
children
|
|
27561
|
+
}) => {
|
|
27562
|
+
// Reset on each render to start fresh
|
|
27563
|
+
tracker.reset();
|
|
27564
|
+
return jsx(ItemTrackerContext.Provider, {
|
|
27565
|
+
value: tracker,
|
|
27566
|
+
children: children
|
|
27567
|
+
});
|
|
27568
|
+
};
|
|
27569
|
+
ItemTrackerProvider.items = items;
|
|
27570
|
+
return {
|
|
27571
|
+
ItemTrackerProvider,
|
|
27572
|
+
items,
|
|
27573
|
+
registerItem: data => {
|
|
27574
|
+
const index = itemCountRef.current++;
|
|
27575
|
+
items[index] = data;
|
|
27576
|
+
return index;
|
|
27577
|
+
},
|
|
27578
|
+
getItem: index => {
|
|
27579
|
+
return items[index];
|
|
27580
|
+
},
|
|
27581
|
+
getAllItems: () => {
|
|
27582
|
+
return items;
|
|
27583
|
+
},
|
|
27584
|
+
reset: () => {
|
|
27585
|
+
items.length = 0;
|
|
27586
|
+
itemCountRef.current = 0;
|
|
27587
|
+
}
|
|
27588
|
+
};
|
|
27589
|
+
}, []);
|
|
27590
|
+
return tracker.ItemTrackerProvider;
|
|
27591
|
+
};
|
|
27592
|
+
const useTrackItem = data => {
|
|
27593
|
+
const tracker = useContext(ItemTrackerContext);
|
|
27594
|
+
if (!tracker) {
|
|
27595
|
+
throw new Error("useTrackItem must be used within SimpleItemTrackerProvider");
|
|
27596
|
+
}
|
|
27597
|
+
return tracker.registerItem(data);
|
|
27598
|
+
};
|
|
27599
|
+
const useTrackedItem = index => {
|
|
27600
|
+
const trackedItems = useTrackedItems();
|
|
27601
|
+
const item = trackedItems[index];
|
|
27602
|
+
return item;
|
|
27603
|
+
};
|
|
27604
|
+
const useTrackedItems = () => {
|
|
27605
|
+
const tracker = useContext(ItemTrackerContext);
|
|
27606
|
+
if (!tracker) {
|
|
27607
|
+
throw new Error("useTrackedItems must be used within SimpleItemTrackerProvider");
|
|
27608
|
+
}
|
|
27609
|
+
return tracker.items;
|
|
27610
|
+
};
|
|
27611
|
+
return [useItemTrackerProvider, useTrackItem, useTrackedItem, useTrackedItems];
|
|
27724
27612
|
};
|
|
27725
27613
|
|
|
27726
|
-
const
|
|
27727
|
-
const refMapRef = useRef(new Map());
|
|
27728
|
-
const previousKeySetRef = useRef(new Set());
|
|
27614
|
+
installImportMetaCssBuild(import.meta);const [useSuggestionItemTrackerProvider, useTrackSuggestion] = createItemTracker();
|
|
27729
27615
|
|
|
27730
|
-
|
|
27731
|
-
|
|
27732
|
-
|
|
27733
|
-
|
|
27734
|
-
|
|
27616
|
+
/**
|
|
27617
|
+
* SuggestionList + Suggestion: a composable accessible listbox.
|
|
27618
|
+
*
|
|
27619
|
+
* Usage:
|
|
27620
|
+
* <SuggestionList id="my-list" value={selected} onChange={setSelected}>
|
|
27621
|
+
* <Suggestion value="a">Option A</Suggestion>
|
|
27622
|
+
* <Suggestion value="b">Option B</Suggestion>
|
|
27623
|
+
* </SuggestionList>
|
|
27624
|
+
*
|
|
27625
|
+
* CSS vars on .navi_suggestion_list:
|
|
27626
|
+
* --suggestion-list-border-radius, --suggestion-list-border-width, --suggestion-list-border-color, --suggestion-list-background-color, --suggestion-list-max-height
|
|
27627
|
+
*
|
|
27628
|
+
* CSS vars on .navi_suggestion:
|
|
27629
|
+
* --suggestion-padding, --suggestion-color, --suggestion-background-color, --suggestion-font-weight
|
|
27630
|
+
* --suggestion-color-hover, --suggestion-background-color-hover
|
|
27631
|
+
* --suggestion-color-pointed, --suggestion-background-color-pointed
|
|
27632
|
+
* --suggestion-color-selected, --suggestion-background-color-selected, --suggestion-font-weight-selected
|
|
27633
|
+
* --suggestion-color-pointed-selected, --suggestion-background-color-pointed-selected
|
|
27634
|
+
* --suggestion-color-highlight, --suggestion-background-color-highlight
|
|
27635
|
+
*
|
|
27636
|
+
* CSS vars on .navi_suggestion_group_label:
|
|
27637
|
+
* --suggestion-group-label-padding, --suggestion-group-label-color, --suggestion-group-label-font-size, --suggestion-group-label-font-weight
|
|
27638
|
+
*/
|
|
27639
|
+
|
|
27640
|
+
const css$f = /* css */`
|
|
27641
|
+
@layer navi {
|
|
27642
|
+
.navi_suggestion_list {
|
|
27643
|
+
--suggestion-list-border-radius: 4px;
|
|
27644
|
+
--suggestion-list-border-width: 1px;
|
|
27645
|
+
--suggestion-list-border-color: light-dark(#ccc, #555);
|
|
27646
|
+
--suggestion-list-border-style: solid;
|
|
27647
|
+
--suggestion-list-background-color: light-dark(#fff, #1e1e1e);
|
|
27648
|
+
--suggestion-list-max-height: 220px;
|
|
27649
|
+
}
|
|
27650
|
+
.navi_suggestion {
|
|
27651
|
+
--suggestion-padding: 8px 12px;
|
|
27652
|
+
--suggestion-color: inherit;
|
|
27653
|
+
--suggestion-font-weight: inherit;
|
|
27654
|
+
|
|
27655
|
+
/* Hover (mouse) */
|
|
27656
|
+
--suggestion-color-hover: var(--suggestion-color);
|
|
27657
|
+
--suggestion-background-color-hover: light-dark(#f5f5f5, #2a2a2a);
|
|
27658
|
+
|
|
27659
|
+
/* Pointed (keyboard navigation position) */
|
|
27660
|
+
--suggestion-color-pointed: var(--suggestion-color);
|
|
27661
|
+
--suggestion-background-color-pointed: light-dark(#e8f0fe, #1c3a6e);
|
|
27662
|
+
|
|
27663
|
+
/* Selected */
|
|
27664
|
+
--suggestion-color-selected: light-dark(#1a73e8, #7baaf7);
|
|
27665
|
+
--suggestion-background-color-selected: light-dark(#e8f0fe, #1c3a6e);
|
|
27666
|
+
--suggestion-font-weight-selected: 500;
|
|
27667
|
+
|
|
27668
|
+
/* Highlight (CSS Highlight API match) */
|
|
27669
|
+
--suggestion-color-highlight: inherit;
|
|
27670
|
+
--suggestion-background-color-highlight: #ffe066;
|
|
27671
|
+
--suggestion-color-pointed-selected: var(--suggestion-color-selected);
|
|
27672
|
+
--suggestion-background-color-pointed-selected: light-dark(
|
|
27673
|
+
#d2e3fc,
|
|
27674
|
+
#174ea6
|
|
27675
|
+
);
|
|
27676
|
+
}
|
|
27677
|
+
}
|
|
27678
|
+
|
|
27679
|
+
.navi_suggestion_list {
|
|
27680
|
+
--x-border-radius: var(--suggestion-list-border-radius);
|
|
27681
|
+
--x-border-width: var(--suggestion-list-border-width);
|
|
27682
|
+
--x-border-color: var(--suggestion-list-border-color);
|
|
27683
|
+
--x-border-style: var(--suggestion-list-border-style);
|
|
27684
|
+
--x-background-color: var(--suggestion-list-background-color);
|
|
27685
|
+
width: fit-content;
|
|
27686
|
+
max-width: 100%;
|
|
27687
|
+
|
|
27688
|
+
max-height: var(--suggestion-list-max-height);
|
|
27689
|
+
background-color: var(--x-background-color);
|
|
27690
|
+
border: var(--x-border-width) var(--x-border-style) var(--x-border-color);
|
|
27691
|
+
border-radius: var(--x-border-radius);
|
|
27692
|
+
transition: opacity 0.2s ease;
|
|
27693
|
+
overflow: auto;
|
|
27694
|
+
|
|
27695
|
+
/* Popover reset — browser adds border, background, padding, margin by default */
|
|
27696
|
+
&[popover] {
|
|
27697
|
+
position: absolute;
|
|
27698
|
+
inset: unset;
|
|
27699
|
+
min-width: var(--suggestion-list-anchor-width, 0px);
|
|
27700
|
+
max-width: 95vw;
|
|
27701
|
+
margin: 0;
|
|
27702
|
+
padding: 0;
|
|
27703
|
+
/* border: none; */
|
|
27704
|
+
}
|
|
27705
|
+
&[data-anchor-hidden] {
|
|
27706
|
+
opacity: 0;
|
|
27707
|
+
pointer-events: none;
|
|
27708
|
+
}
|
|
27709
|
+
}
|
|
27710
|
+
|
|
27711
|
+
.navi_suggestion_listbox {
|
|
27712
|
+
box-sizing: border-box;
|
|
27713
|
+
width: max-content;
|
|
27714
|
+
min-width: 100%;
|
|
27715
|
+
margin: 0;
|
|
27716
|
+
padding: 0;
|
|
27717
|
+
list-style: none;
|
|
27718
|
+
}
|
|
27719
|
+
::highlight(navi-suggestion-match) {
|
|
27720
|
+
color: var(--suggestion-color-highlight);
|
|
27721
|
+
background-color: var(--suggestion-background-color-highlight);
|
|
27722
|
+
}
|
|
27723
|
+
.navi_suggestion {
|
|
27724
|
+
--x-color: var(--suggestion-color);
|
|
27725
|
+
--x-background-color: var(--suggestion-background-color);
|
|
27726
|
+
--x-font-weight: var(--suggestion-font-weight);
|
|
27727
|
+
display: flex;
|
|
27728
|
+
box-sizing: border-box;
|
|
27729
|
+
width: max-content;
|
|
27730
|
+
min-width: 100%;
|
|
27731
|
+
|
|
27732
|
+
padding: var(--suggestion-padding);
|
|
27733
|
+
flex-direction: column;
|
|
27734
|
+
color: var(--x-color);
|
|
27735
|
+
font-weight: var(--x-font-weight);
|
|
27736
|
+
background-color: var(--x-background-color);
|
|
27737
|
+
cursor: pointer;
|
|
27738
|
+
user-select: none;
|
|
27735
27739
|
|
|
27736
|
-
|
|
27737
|
-
|
|
27738
|
-
|
|
27739
|
-
|
|
27740
|
+
&:hover {
|
|
27741
|
+
--x-color: var(--suggestion-color-hover);
|
|
27742
|
+
--x-background-color: var(--suggestion-background-color-hover);
|
|
27743
|
+
}
|
|
27740
27744
|
|
|
27741
|
-
|
|
27742
|
-
|
|
27743
|
-
|
|
27744
|
-
} else {
|
|
27745
|
-
const newRef = createRef();
|
|
27746
|
-
refMap.set(key, newRef);
|
|
27747
|
-
refArray[i] = newRef;
|
|
27748
|
-
}
|
|
27745
|
+
&[data-pointed] {
|
|
27746
|
+
--x-color: var(--suggestion-color-pointed);
|
|
27747
|
+
--x-background-color: var(--suggestion-background-color-pointed);
|
|
27749
27748
|
}
|
|
27750
27749
|
|
|
27751
|
-
|
|
27752
|
-
|
|
27753
|
-
|
|
27754
|
-
|
|
27750
|
+
&[data-selected] {
|
|
27751
|
+
--x-color: var(--suggestion-color-selected);
|
|
27752
|
+
--x-background-color: var(--suggestion-background-color-selected);
|
|
27753
|
+
--x-font-weight: var(--suggestion-font-weight-selected);
|
|
27755
27754
|
}
|
|
27756
|
-
previousKeySetRef.current = currentKeySet;
|
|
27757
27755
|
|
|
27758
|
-
|
|
27759
|
-
|
|
27760
|
-
|
|
27756
|
+
&[data-pointed][data-selected] {
|
|
27757
|
+
--x-color: var(--suggestion-color-pointed-selected);
|
|
27758
|
+
--x-background-color: var(--suggestion-background-color-pointed-selected);
|
|
27759
|
+
}
|
|
27760
|
+
}
|
|
27761
|
+
.navi_suggestion_group_label {
|
|
27762
|
+
position: sticky;
|
|
27763
|
+
top: 0;
|
|
27764
|
+
z-index: 1;
|
|
27765
|
+
display: block;
|
|
27766
|
+
background-color: var(
|
|
27767
|
+
--suggestion-group-label-background-color,
|
|
27768
|
+
var(--suggestion-list-background-color)
|
|
27769
|
+
);
|
|
27770
|
+
user-select: none;
|
|
27761
27771
|
|
|
27762
|
-
|
|
27763
|
-
|
|
27764
|
-
|
|
27765
|
-
|
|
27772
|
+
&[data-default-label] {
|
|
27773
|
+
padding: 4px 12px 2px;
|
|
27774
|
+
color: light-dark(#888, #aaa);
|
|
27775
|
+
font-weight: 600;
|
|
27776
|
+
font-size: 0.75em;
|
|
27777
|
+
text-transform: uppercase;
|
|
27778
|
+
letter-spacing: 0.05em;
|
|
27779
|
+
}
|
|
27780
|
+
}
|
|
27781
|
+
.navi_suggestion_list_empty {
|
|
27782
|
+
display: none;
|
|
27783
|
+
padding: var(--suggestion-padding);
|
|
27784
|
+
color: var(--suggestion-group-label-color);
|
|
27785
|
+
font-size: 0.9em;
|
|
27786
|
+
text-align: center;
|
|
27787
|
+
user-select: none;
|
|
27788
|
+
}
|
|
27789
|
+
/* Show the empty state only when there are no visible suggestions */
|
|
27790
|
+
.navi_suggestion_list:not(:has([role="option"]:not([hidden]))) {
|
|
27791
|
+
.navi_suggestion_list_empty {
|
|
27792
|
+
display: block;
|
|
27793
|
+
}
|
|
27766
27794
|
}
|
|
27767
27795
|
`;
|
|
27768
|
-
const
|
|
27769
|
-
|
|
27770
|
-
|
|
27771
|
-
|
|
27772
|
-
|
|
27773
|
-
|
|
27774
|
-
|
|
27775
|
-
|
|
27776
|
-
|
|
27777
|
-
|
|
27778
|
-
|
|
27779
|
-
|
|
27780
|
-
|
|
27781
|
-
|
|
27782
|
-
|
|
27783
|
-
|
|
27784
|
-
|
|
27785
|
-
|
|
27786
|
-
|
|
27787
|
-
|
|
27788
|
-
|
|
27789
|
-
|
|
27790
|
-
|
|
27796
|
+
const SuggestionListStyleCSSVars = {
|
|
27797
|
+
borderRadius: "--suggestion-list-border-radius",
|
|
27798
|
+
borderWidth: "--suggestion-list-border-width",
|
|
27799
|
+
borderColor: "--suggestion-list-border-color",
|
|
27800
|
+
backgroundColor: "--suggestion-list-background-color",
|
|
27801
|
+
maxHeight: "--suggestion-list-max-height"
|
|
27802
|
+
};
|
|
27803
|
+
const SuggestionStyleCSSVars = {
|
|
27804
|
+
"padding": "--suggestion-padding",
|
|
27805
|
+
"color": "--suggestion-color",
|
|
27806
|
+
"backgroundColor": "--suggestion-background-color",
|
|
27807
|
+
"fontWeight": "--suggestion-font-weight",
|
|
27808
|
+
":-navi-pointed": {
|
|
27809
|
+
color: "--suggestion-color-pointed",
|
|
27810
|
+
backgroundColor: "--suggestion-background-color-pointed"
|
|
27811
|
+
},
|
|
27812
|
+
":hover": {
|
|
27813
|
+
color: "--suggestion-color-hover",
|
|
27814
|
+
backgroundColor: "--suggestion-background-color-hover"
|
|
27815
|
+
},
|
|
27816
|
+
":-navi-selected": {
|
|
27817
|
+
color: "--suggestion-color-selected",
|
|
27818
|
+
backgroundColor: "--suggestion-background-color-selected",
|
|
27819
|
+
fontWeight: "--suggestion-font-weight-selected"
|
|
27820
|
+
},
|
|
27821
|
+
"::highlight": {
|
|
27822
|
+
color: "--suggestion-color-highlight",
|
|
27823
|
+
backgroundColor: "--suggestion-background-color-highlight"
|
|
27824
|
+
}
|
|
27825
|
+
};
|
|
27826
|
+
|
|
27827
|
+
/**
|
|
27828
|
+
* Context OptionList provides downward to its Option children.
|
|
27829
|
+
*/
|
|
27830
|
+
const SuggestionListContext = createContext(null);
|
|
27831
|
+
const SuggestionList = ({
|
|
27832
|
+
popover,
|
|
27833
|
+
onChange: onChangeProp,
|
|
27834
|
+
highlight,
|
|
27835
|
+
emptyState = "No results",
|
|
27836
|
+
children,
|
|
27837
|
+
...rest
|
|
27838
|
+
}) => {
|
|
27839
|
+
import.meta.css = [css$f, "@jsenv/navi/src/field/suggestion_list.jsx"];
|
|
27840
|
+
const ItemTrackerProvider = useSuggestionItemTrackerProvider();
|
|
27841
|
+
const [pointedValue, setPointedValue] = useState(null);
|
|
27842
|
+
const pointedValueRef = useRef(null);
|
|
27843
|
+
pointedValueRef.current = pointedValue;
|
|
27844
|
+
const ownId = useId();
|
|
27845
|
+
const id = rest.id ?? ownId;
|
|
27846
|
+
const defaultRef = useRef(null);
|
|
27847
|
+
const ref = rest.ref || defaultRef;
|
|
27848
|
+
useLayoutEffect(() => {
|
|
27849
|
+
if (!CSS.highlights) {
|
|
27850
|
+
return undefined;
|
|
27851
|
+
}
|
|
27852
|
+
if (!highlight) {
|
|
27853
|
+
CSS.highlights.delete("navi-suggestion-match");
|
|
27854
|
+
return undefined;
|
|
27855
|
+
}
|
|
27856
|
+
const listEl = ref.current;
|
|
27857
|
+
if (!listEl) {
|
|
27858
|
+
return undefined;
|
|
27859
|
+
}
|
|
27860
|
+
const ranges = [];
|
|
27861
|
+
const lowerHighlight = highlight.toLowerCase();
|
|
27862
|
+
for (const suggestionEl of listEl.querySelectorAll("[role='option']")) {
|
|
27863
|
+
const walker = document.createTreeWalker(suggestionEl, NodeFilter.SHOW_TEXT);
|
|
27864
|
+
let node;
|
|
27865
|
+
while (node = walker.nextNode()) {
|
|
27866
|
+
const text = node.textContent;
|
|
27867
|
+
const lowerText = text.toLowerCase();
|
|
27868
|
+
let index = lowerText.indexOf(lowerHighlight);
|
|
27869
|
+
while (index !== -1) {
|
|
27870
|
+
const range = new Range();
|
|
27871
|
+
range.setStart(node, index);
|
|
27872
|
+
range.setEnd(node, index + highlight.length);
|
|
27873
|
+
ranges.push(range);
|
|
27874
|
+
index = lowerText.indexOf(lowerHighlight, index + 1);
|
|
27875
|
+
}
|
|
27876
|
+
}
|
|
27877
|
+
}
|
|
27878
|
+
if (ranges.length === 0) {
|
|
27879
|
+
CSS.highlights.delete("navi-suggestion-match");
|
|
27880
|
+
} else {
|
|
27881
|
+
CSS.highlights.set("navi-suggestion-match", new Highlight(...ranges));
|
|
27882
|
+
}
|
|
27883
|
+
return () => {
|
|
27884
|
+
CSS.highlights.delete("navi-suggestion-match");
|
|
27885
|
+
};
|
|
27886
|
+
}, [highlight, children]);
|
|
27887
|
+
const effectiveOnChange = popover ? value => {
|
|
27888
|
+
onChangeProp?.(value);
|
|
27889
|
+
ref.current?.dispatchEvent(new CustomEvent("navi_suggestion_list_selected", {
|
|
27890
|
+
detail: {
|
|
27891
|
+
value
|
|
27892
|
+
},
|
|
27893
|
+
bubbles: true
|
|
27894
|
+
}));
|
|
27895
|
+
} : onChangeProp;
|
|
27896
|
+
const onChangeRef = useRef(effectiveOnChange);
|
|
27897
|
+
onChangeRef.current = effectiveOnChange;
|
|
27898
|
+
const navigate = direction => {
|
|
27899
|
+
const values = ItemTrackerProvider.items.filter(item => !item.hidden).map(item => item.value);
|
|
27900
|
+
if (values.length === 0) {
|
|
27901
|
+
return false;
|
|
27902
|
+
}
|
|
27903
|
+
const current = pointedValueRef.current;
|
|
27904
|
+
if (direction === "down") {
|
|
27905
|
+
const idx = current === null ? -1 : values.indexOf(current);
|
|
27906
|
+
setPointedValue(values[idx < values.length - 1 ? idx + 1 : idx]);
|
|
27907
|
+
} else if (direction === "up") {
|
|
27908
|
+
const idx = current === null ? -1 : values.indexOf(current);
|
|
27909
|
+
setPointedValue(values[idx > 0 ? idx - 1 : 0]);
|
|
27910
|
+
} else if (direction === "first") {
|
|
27911
|
+
setPointedValue(values[0]);
|
|
27912
|
+
} else if (direction === "last") {
|
|
27913
|
+
setPointedValue(values[values.length - 1]);
|
|
27914
|
+
}
|
|
27915
|
+
return true;
|
|
27916
|
+
};
|
|
27917
|
+
|
|
27918
|
+
// Listen for commands dispatched by a linked Input (combobox mode)
|
|
27919
|
+
const noopRef = useRef(null);
|
|
27920
|
+
useEffect(() => {
|
|
27921
|
+
if (!popover || !ref.current) {
|
|
27922
|
+
return undefined;
|
|
27923
|
+
}
|
|
27924
|
+
const el = ref.current;
|
|
27925
|
+
let positionEffectCleanup = null;
|
|
27926
|
+
const positionPopover = anchor => {
|
|
27927
|
+
const anchorRect = anchor.getBoundingClientRect();
|
|
27928
|
+
el.style.setProperty("--suggestion-list-anchor-width", `${anchorRect.width}px`);
|
|
27929
|
+
const minLeft = 1;
|
|
27930
|
+
const {
|
|
27931
|
+
left,
|
|
27932
|
+
top
|
|
27933
|
+
} = pickPositionRelativeTo(el, anchor, {
|
|
27934
|
+
positionPreference: "below",
|
|
27935
|
+
minLeft
|
|
27936
|
+
});
|
|
27937
|
+
el.style.top = `${top}px`;
|
|
27938
|
+
const popoverRect = el.getBoundingClientRect();
|
|
27939
|
+
const maxWidth = parseFloat(getComputedStyle(el).maxWidth);
|
|
27940
|
+
if (!isNaN(maxWidth) && popoverRect.width >= maxWidth - 1) {
|
|
27941
|
+
const viewportWidth = document.documentElement.clientWidth;
|
|
27942
|
+
const centeredLeft = (viewportWidth - popoverRect.width) / 2;
|
|
27943
|
+
el.style.left = `${Math.max(centeredLeft, minLeft)}px`;
|
|
27944
|
+
} else {
|
|
27945
|
+
el.style.left = `${Math.max(left, minLeft)}px`;
|
|
27946
|
+
}
|
|
27947
|
+
};
|
|
27948
|
+
const onOpen = e => {
|
|
27949
|
+
const anchor = e.detail?.anchor;
|
|
27950
|
+
el.showPopover();
|
|
27951
|
+
if (anchor) {
|
|
27952
|
+
positionEffectCleanup = visibleRectEffect(anchor, ({
|
|
27953
|
+
visibilityRatio
|
|
27954
|
+
}) => {
|
|
27955
|
+
if (visibilityRatio <= 0.2) {
|
|
27956
|
+
el.setAttribute("data-anchor-hidden", "");
|
|
27957
|
+
return;
|
|
27958
|
+
}
|
|
27959
|
+
el.removeAttribute("data-anchor-hidden");
|
|
27960
|
+
positionPopover(anchor);
|
|
27961
|
+
});
|
|
27962
|
+
}
|
|
27963
|
+
};
|
|
27964
|
+
const onClose = () => {
|
|
27965
|
+
if (positionEffectCleanup) {
|
|
27966
|
+
positionEffectCleanup.disconnect();
|
|
27967
|
+
positionEffectCleanup = null;
|
|
27968
|
+
}
|
|
27969
|
+
el.removeAttribute("data-anchor-hidden");
|
|
27970
|
+
el.dispatchEvent(new CustomEvent("navi_suggestion_list_clear"));
|
|
27971
|
+
el.hidePopover();
|
|
27972
|
+
};
|
|
27973
|
+
const onNavigate = e => {
|
|
27974
|
+
navigate(e.detail.direction);
|
|
27975
|
+
};
|
|
27976
|
+
const onConfirm = e => {
|
|
27977
|
+
const current = pointedValueRef.current;
|
|
27978
|
+
if (current !== null) {
|
|
27979
|
+
onChangeRef.current?.(current);
|
|
27791
27980
|
e.preventDefault();
|
|
27792
27981
|
}
|
|
27793
|
-
}
|
|
27982
|
+
};
|
|
27983
|
+
const onClear = () => {
|
|
27984
|
+
setPointedValue(null);
|
|
27985
|
+
};
|
|
27986
|
+
el.addEventListener("navi_suggestion_list_open", onOpen);
|
|
27987
|
+
el.addEventListener("navi_suggestion_list_close", onClose);
|
|
27988
|
+
el.addEventListener("navi_suggestion_list_navigate", onNavigate);
|
|
27989
|
+
el.addEventListener("navi_suggestion_list_confirm", onConfirm);
|
|
27990
|
+
el.addEventListener("navi_suggestion_list_clear", onClear);
|
|
27991
|
+
return () => {
|
|
27992
|
+
el.removeEventListener("navi_suggestion_list_open", onOpen);
|
|
27993
|
+
el.removeEventListener("navi_suggestion_list_close", onClose);
|
|
27994
|
+
el.removeEventListener("navi_suggestion_list_navigate", onNavigate);
|
|
27995
|
+
el.removeEventListener("navi_suggestion_list_confirm", onConfirm);
|
|
27996
|
+
el.removeEventListener("navi_suggestion_list_clear", onClear);
|
|
27997
|
+
if (positionEffectCleanup) {
|
|
27998
|
+
positionEffectCleanup.disconnect();
|
|
27999
|
+
}
|
|
28000
|
+
};
|
|
28001
|
+
}, [popover]);
|
|
28002
|
+
useKeyboardShortcuts(popover ? noopRef : ref, [{
|
|
28003
|
+
key: "arrowdown",
|
|
28004
|
+
description: "Point to next suggestion",
|
|
28005
|
+
handler: () => navigate("down")
|
|
28006
|
+
}, {
|
|
28007
|
+
key: "arrowup",
|
|
28008
|
+
description: "Point to previous suggestion",
|
|
28009
|
+
handler: () => navigate("up")
|
|
28010
|
+
}, {
|
|
28011
|
+
key: "home",
|
|
28012
|
+
description: "Point to first suggestion",
|
|
28013
|
+
handler: () => navigate("first")
|
|
28014
|
+
}, {
|
|
28015
|
+
key: "end",
|
|
28016
|
+
description: "Point to last suggestion",
|
|
28017
|
+
handler: () => navigate("last")
|
|
28018
|
+
}, {
|
|
28019
|
+
key: "enter",
|
|
28020
|
+
description: "Confirm pointed suggestion",
|
|
28021
|
+
handler: () => {
|
|
28022
|
+
const current = pointedValueRef.current;
|
|
28023
|
+
if (current === null) {
|
|
28024
|
+
return false;
|
|
28025
|
+
}
|
|
28026
|
+
onChangeRef.current?.(current);
|
|
28027
|
+
return true;
|
|
28028
|
+
}
|
|
28029
|
+
}, {
|
|
28030
|
+
key: "escape",
|
|
28031
|
+
description: "Clear pointed suggestion",
|
|
28032
|
+
handler: () => {
|
|
28033
|
+
setPointedValue(null);
|
|
28034
|
+
return true;
|
|
28035
|
+
}
|
|
28036
|
+
}]);
|
|
28037
|
+
const suggestionListContext = {
|
|
28038
|
+
pointedValue,
|
|
28039
|
+
setPointedValue,
|
|
28040
|
+
onSelect: effectiveOnChange
|
|
28041
|
+
};
|
|
28042
|
+
return jsx(Box, {
|
|
28043
|
+
ref: ref,
|
|
28044
|
+
id: id,
|
|
28045
|
+
popover: popover ? "manual" : undefined,
|
|
28046
|
+
tabIndex: popover ? -1 : 0,
|
|
27794
28047
|
...rest,
|
|
27795
|
-
|
|
27796
|
-
|
|
27797
|
-
|
|
27798
|
-
|
|
27799
|
-
|
|
27800
|
-
|
|
27801
|
-
|
|
27802
|
-
|
|
27803
|
-
|
|
27804
|
-
|
|
27805
|
-
|
|
27806
|
-
|
|
27807
|
-
|
|
27808
|
-
|
|
27809
|
-
|
|
27810
|
-
loading: loading || childLoading,
|
|
27811
|
-
...childRest,
|
|
27812
|
-
children: label
|
|
27813
|
-
}, childValue);
|
|
28048
|
+
baseClassName: "navi_suggestion_list",
|
|
28049
|
+
children: jsx(Box, {
|
|
28050
|
+
as: "ul",
|
|
28051
|
+
role: "listbox",
|
|
28052
|
+
baseClassName: "navi_suggestion_listbox",
|
|
28053
|
+
styleCSSVars: SuggestionListStyleCSSVars,
|
|
28054
|
+
children: jsxs(SuggestionListContext.Provider, {
|
|
28055
|
+
value: suggestionListContext,
|
|
28056
|
+
children: [jsx(ItemTrackerProvider, {
|
|
28057
|
+
children: children
|
|
28058
|
+
}), emptyState && jsx("li", {
|
|
28059
|
+
className: "navi_suggestion_list_empty",
|
|
28060
|
+
children: emptyState
|
|
28061
|
+
})]
|
|
28062
|
+
})
|
|
27814
28063
|
})
|
|
27815
28064
|
});
|
|
27816
|
-
|
|
27817
|
-
|
|
27818
|
-
|
|
27819
|
-
|
|
27820
|
-
|
|
27821
|
-
|
|
27822
|
-
|
|
27823
|
-
|
|
27824
|
-
|
|
27825
|
-
|
|
27826
|
-
|
|
27827
|
-
|
|
27828
|
-
|
|
27829
|
-
|
|
27830
|
-
|
|
27831
|
-
|
|
27832
|
-
|
|
27833
|
-
const valueAtStart = navState === undefined ? initialValue : navState;
|
|
27834
|
-
const [value, setValue] = useState(valueAtStart);
|
|
27835
|
-
useEffect(() => {
|
|
27836
|
-
setNavState(value);
|
|
27837
|
-
}, [value]);
|
|
27838
|
-
return jsx(SelectControlled, {
|
|
27839
|
-
ref: innerRef,
|
|
27840
|
-
value: value,
|
|
27841
|
-
onChange: event => {
|
|
27842
|
-
const select = event.target;
|
|
27843
|
-
const selectedValue = select.value;
|
|
27844
|
-
setValue(selectedValue);
|
|
27845
|
-
},
|
|
27846
|
-
...rest,
|
|
27847
|
-
children: children
|
|
28065
|
+
};
|
|
28066
|
+
const SUGGESTION_PSEUDO_CLASSES = [":-navi-pointed", ":-navi-selected"];
|
|
28067
|
+
const SUGGESTION_PSEUDO_ELEMENTS = ["::highlight"];
|
|
28068
|
+
const Suggestion = ({
|
|
28069
|
+
value,
|
|
28070
|
+
selected,
|
|
28071
|
+
hidden,
|
|
28072
|
+
children,
|
|
28073
|
+
...rest
|
|
28074
|
+
}) => {
|
|
28075
|
+
import.meta.css = [css$f, "@jsenv/navi/src/field/suggestion_list.jsx"];
|
|
28076
|
+
const suggestionId = useId();
|
|
28077
|
+
const id = rest.id || suggestionId;
|
|
28078
|
+
useTrackSuggestion({
|
|
28079
|
+
value,
|
|
28080
|
+
suggestionId: id,
|
|
28081
|
+
hidden
|
|
27848
28082
|
});
|
|
27849
|
-
});
|
|
27850
|
-
forwardRef((props, ref) => {
|
|
27851
|
-
const {
|
|
27852
|
-
id,
|
|
27853
|
-
name,
|
|
27854
|
-
value: externalValue,
|
|
27855
|
-
valueSignal,
|
|
27856
|
-
action,
|
|
27857
|
-
children,
|
|
27858
|
-
onCancel,
|
|
27859
|
-
onActionPrevented,
|
|
27860
|
-
onActionStart,
|
|
27861
|
-
onActionAbort,
|
|
27862
|
-
onActionError,
|
|
27863
|
-
onActionEnd,
|
|
27864
|
-
actionErrorEffect,
|
|
27865
|
-
...rest
|
|
27866
|
-
} = props;
|
|
27867
|
-
const innerRef = useRef();
|
|
27868
|
-
useImperativeHandle(ref, () => innerRef.current);
|
|
27869
|
-
const [navState, setNavState, resetNavState] = useNavState();
|
|
27870
|
-
const [boundAction, value, setValue, initialValue] = useActionBoundToOneParam(action, name);
|
|
27871
28083
|
const {
|
|
27872
|
-
|
|
27873
|
-
|
|
27874
|
-
|
|
27875
|
-
|
|
27876
|
-
|
|
28084
|
+
pointedValue,
|
|
28085
|
+
setPointedValue,
|
|
28086
|
+
onSelect
|
|
28087
|
+
} = useContext(SuggestionListContext);
|
|
28088
|
+
const isPointed = pointedValue === value;
|
|
28089
|
+
const suggestionRef = useRef(null);
|
|
27877
28090
|
useEffect(() => {
|
|
27878
|
-
|
|
27879
|
-
|
|
27880
|
-
|
|
27881
|
-
|
|
27882
|
-
|
|
27883
|
-
|
|
27884
|
-
|
|
27885
|
-
|
|
28091
|
+
const suggestionEl = suggestionRef.current;
|
|
28092
|
+
if (isPointed && suggestionEl) {
|
|
28093
|
+
suggestionEl.scrollIntoView({
|
|
28094
|
+
block: "nearest"
|
|
28095
|
+
});
|
|
28096
|
+
}
|
|
28097
|
+
}, [isPointed]);
|
|
28098
|
+
return jsx(Box, {
|
|
28099
|
+
as: "li",
|
|
28100
|
+
ref: suggestionRef,
|
|
28101
|
+
baseClassName: "navi_suggestion",
|
|
28102
|
+
id: suggestionId,
|
|
28103
|
+
role: "option",
|
|
28104
|
+
"aria-selected": selected,
|
|
28105
|
+
"aria-hidden": hidden ? true : undefined,
|
|
28106
|
+
hidden: hidden,
|
|
28107
|
+
basePseudoState: {
|
|
28108
|
+
":-navi-pointed": isPointed,
|
|
28109
|
+
":-navi-selected": selected
|
|
27886
28110
|
},
|
|
27887
|
-
|
|
27888
|
-
|
|
27889
|
-
|
|
27890
|
-
|
|
28111
|
+
pseudoClasses: SUGGESTION_PSEUDO_CLASSES,
|
|
28112
|
+
pseudoElements: SUGGESTION_PSEUDO_ELEMENTS,
|
|
28113
|
+
styleCSSVars: SuggestionStyleCSSVars,
|
|
28114
|
+
onMouseEnter: () => {
|
|
28115
|
+
if (!hidden) {
|
|
28116
|
+
setPointedValue(value);
|
|
28117
|
+
}
|
|
27891
28118
|
},
|
|
27892
|
-
|
|
27893
|
-
|
|
27894
|
-
|
|
27895
|
-
|
|
28119
|
+
onMouseLeave: () => {
|
|
28120
|
+
if (!hidden) {
|
|
28121
|
+
setPointedValue(null);
|
|
28122
|
+
}
|
|
27896
28123
|
},
|
|
27897
|
-
|
|
27898
|
-
|
|
27899
|
-
|
|
28124
|
+
onMouseDown: e => {
|
|
28125
|
+
if (hidden || e.button !== 0) {
|
|
28126
|
+
return;
|
|
28127
|
+
}
|
|
28128
|
+
onSelect?.(value);
|
|
27900
28129
|
},
|
|
27901
|
-
|
|
27902
|
-
|
|
27903
|
-
onActionEnd?.();
|
|
27904
|
-
}
|
|
28130
|
+
...rest,
|
|
28131
|
+
children: children
|
|
27905
28132
|
});
|
|
27906
|
-
|
|
27907
|
-
|
|
27908
|
-
|
|
27909
|
-
|
|
27910
|
-
|
|
27911
|
-
|
|
27912
|
-
|
|
27913
|
-
|
|
27914
|
-
|
|
27915
|
-
|
|
27916
|
-
const radioListContainer = innerRef.current;
|
|
27917
|
-
const optionSelected = select.querySelector(`option[value="${selectedValue}"]`);
|
|
27918
|
-
requestAction(radioListContainer, boundAction, {
|
|
27919
|
-
event,
|
|
27920
|
-
requester: optionSelected
|
|
27921
|
-
});
|
|
27922
|
-
},
|
|
28133
|
+
};
|
|
28134
|
+
const SuggestionGroup = ({
|
|
28135
|
+
label,
|
|
28136
|
+
children,
|
|
28137
|
+
...rest
|
|
28138
|
+
}) => {
|
|
28139
|
+
import.meta.css = [css$f, "@jsenv/navi/src/field/suggestion_list.jsx"];
|
|
28140
|
+
const groupId = useId();
|
|
28141
|
+
return jsxs("li", {
|
|
28142
|
+
role: "presentation",
|
|
27923
28143
|
...rest,
|
|
27924
|
-
children:
|
|
27925
|
-
|
|
27926
|
-
|
|
27927
|
-
|
|
27928
|
-
|
|
27929
|
-
|
|
27930
|
-
|
|
27931
|
-
|
|
27932
|
-
|
|
28144
|
+
children: [jsx("span", {
|
|
28145
|
+
id: groupId,
|
|
28146
|
+
role: "presentation",
|
|
28147
|
+
"aria-hidden": "true",
|
|
28148
|
+
style: {
|
|
28149
|
+
display: "contents"
|
|
28150
|
+
},
|
|
28151
|
+
children: jsx("span", {
|
|
28152
|
+
className: "navi_suggestion_group_label",
|
|
28153
|
+
"data-default-label": typeof label === "string" ? "" : undefined,
|
|
28154
|
+
children: label
|
|
28155
|
+
})
|
|
28156
|
+
}), jsx("ul", {
|
|
28157
|
+
role: "group",
|
|
28158
|
+
"aria-labelledby": groupId,
|
|
28159
|
+
style: {
|
|
28160
|
+
margin: 0,
|
|
28161
|
+
padding: 0,
|
|
28162
|
+
listStyle: "none"
|
|
28163
|
+
},
|
|
28164
|
+
children: children
|
|
28165
|
+
})]
|
|
27933
28166
|
});
|
|
27934
|
-
}
|
|
28167
|
+
};
|
|
27935
28168
|
|
|
27936
28169
|
const TableSelectionContext = createContext();
|
|
27937
28170
|
const useTableSelectionContextValue = (
|