@jsenv/navi 0.23.9 → 0.24.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/jsenv_navi.js +1079 -362
- package/dist/jsenv_navi.js.map +103 -58
- package/package.json +10 -10
package/dist/jsenv_navi.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { installImportMetaCssBuild } from "./jsenv_navi_side_effects.js";
|
|
2
|
-
import { isValidElement, h, createContext, toChildArray, render, createRef, cloneElement } from "preact";
|
|
2
|
+
import { isValidElement, h, createContext, options, toChildArray, render, createRef, cloneElement } from "preact";
|
|
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";
|
|
@@ -7226,6 +7226,12 @@ const PSEUDO_CLASSES = {
|
|
|
7226
7226
|
attribute: "data-invalid",
|
|
7227
7227
|
test: (el) => el.matches(":invalid"),
|
|
7228
7228
|
},
|
|
7229
|
+
":-navi-highlighted": {
|
|
7230
|
+
attribute: "data-highlighted",
|
|
7231
|
+
},
|
|
7232
|
+
":-navi-selected": {
|
|
7233
|
+
attribute: "data-selected",
|
|
7234
|
+
},
|
|
7229
7235
|
":-navi-loading": {
|
|
7230
7236
|
attribute: "data-loading",
|
|
7231
7237
|
},
|
|
@@ -7515,6 +7521,111 @@ const updateStyle = (element, style, preventInitialTransition) => {
|
|
|
7515
7521
|
styleKeySetWeakMap.set(element, styleKeySet);
|
|
7516
7522
|
};
|
|
7517
7523
|
|
|
7524
|
+
// Implementation notes:
|
|
7525
|
+
//
|
|
7526
|
+
// options.__r fires before each component render — we capture the current
|
|
7527
|
+
// component instance (vnode.__c) so useEarlyDOMEffect can register itself.
|
|
7528
|
+
//
|
|
7529
|
+
// options.__c (commitRoot) fires after refs are assigned and before any
|
|
7530
|
+
// useLayoutEffect runs. We flush all pending effects there.
|
|
7531
|
+
// The DOM node is read from component.__v.__e (vnode → root DOM node),
|
|
7532
|
+
// which Preact sets during diffing, before options.__c fires.
|
|
7533
|
+
//
|
|
7534
|
+
// stateMap (WeakMap) stores { cleanup, deps } per component instance.
|
|
7535
|
+
// It's auto-GC'd when a component is destroyed; options.unmount also
|
|
7536
|
+
// deletes entries eagerly to release cleanup functions sooner.
|
|
7537
|
+
//
|
|
7538
|
+
// pendingMap (Map) holds effects registered during the current render pass.
|
|
7539
|
+
// It is always fully cleared in options.__c — bounded to one commit, no leak.
|
|
7540
|
+
|
|
7541
|
+
/**
|
|
7542
|
+
* Like useLayoutEffect, but runs before any layout effect in the commit —
|
|
7543
|
+
* including those of descendant components.
|
|
7544
|
+
*
|
|
7545
|
+
* Use this when a parent needs to mutate the DOM (e.g. apply styles) so that
|
|
7546
|
+
* children can read those mutations in their own useLayoutEffect.
|
|
7547
|
+
*
|
|
7548
|
+
* The DOM node of the component is passed as the first argument to fn.
|
|
7549
|
+
* The effect is skipped if no DOM node is found (e.g. on a fragment root).
|
|
7550
|
+
*
|
|
7551
|
+
* Supports deps and cleanup return, same as useLayoutEffect.
|
|
7552
|
+
*/
|
|
7553
|
+
const useEarlyDOMEffect = (fn, deps) => {
|
|
7554
|
+
const component = _currentComponent;
|
|
7555
|
+
if (component) {
|
|
7556
|
+
pendingMap.set(component, { fn, deps });
|
|
7557
|
+
}
|
|
7558
|
+
};
|
|
7559
|
+
|
|
7560
|
+
// Populated during render, consumed + cleared in options.__c each commit.
|
|
7561
|
+
const pendingMap = new Map(); // component → { fn, deps, ref }
|
|
7562
|
+
|
|
7563
|
+
// Persists across commits. WeakMap → no leak when component is destroyed.
|
|
7564
|
+
const stateMap = new WeakMap(); // component → { cleanup, deps }
|
|
7565
|
+
|
|
7566
|
+
let _currentComponent = null;
|
|
7567
|
+
const _prevBeforeRender = options.__r;
|
|
7568
|
+
options.__r = (vnode) => {
|
|
7569
|
+
_currentComponent = vnode.__c;
|
|
7570
|
+
if (_prevBeforeRender) {
|
|
7571
|
+
_prevBeforeRender(vnode);
|
|
7572
|
+
}
|
|
7573
|
+
};
|
|
7574
|
+
|
|
7575
|
+
const _prevCommit = options.__c;
|
|
7576
|
+
options.__c = (root, commitQueue) => {
|
|
7577
|
+
for (const [component, { fn, deps }] of pendingMap) {
|
|
7578
|
+
// component.__v is the component's vnode; __e is its root DOM node.
|
|
7579
|
+
// Both are set during diff, before options.__c fires.
|
|
7580
|
+
const element = component.__v && component.__v.__e;
|
|
7581
|
+
if (!element) {
|
|
7582
|
+
continue;
|
|
7583
|
+
}
|
|
7584
|
+
const prev = stateMap.get(component);
|
|
7585
|
+
const prevDeps = prev ? prev.deps : undefined;
|
|
7586
|
+
let depsChanged;
|
|
7587
|
+
if (!prevDeps || !deps || prevDeps.length !== deps.length) {
|
|
7588
|
+
depsChanged = true;
|
|
7589
|
+
} else {
|
|
7590
|
+
for (let i = 0; i < deps.length; i++) {
|
|
7591
|
+
if (!Object.is(deps[i], prevDeps[i])) {
|
|
7592
|
+
depsChanged = true;
|
|
7593
|
+
break;
|
|
7594
|
+
}
|
|
7595
|
+
}
|
|
7596
|
+
}
|
|
7597
|
+
if (depsChanged) {
|
|
7598
|
+
if (prev && prev.cleanup) {
|
|
7599
|
+
prev.cleanup();
|
|
7600
|
+
}
|
|
7601
|
+
const result = fn(element);
|
|
7602
|
+
const cleanup = typeof result === "function" ? result : undefined;
|
|
7603
|
+
stateMap.set(component, { cleanup, deps });
|
|
7604
|
+
}
|
|
7605
|
+
}
|
|
7606
|
+
pendingMap.clear();
|
|
7607
|
+
if (_prevCommit) {
|
|
7608
|
+
_prevCommit(root, commitQueue);
|
|
7609
|
+
}
|
|
7610
|
+
};
|
|
7611
|
+
|
|
7612
|
+
const _prevUnmount = options.unmount;
|
|
7613
|
+
options.unmount = (vnode) => {
|
|
7614
|
+
const component = vnode.__c;
|
|
7615
|
+
if (component) {
|
|
7616
|
+
const state = stateMap.get(component);
|
|
7617
|
+
if (state && state.cleanup) {
|
|
7618
|
+
state.cleanup();
|
|
7619
|
+
}
|
|
7620
|
+
// stateMap is a WeakMap so the entry is GC'd automatically,
|
|
7621
|
+
// but deleting explicitly releases the cleanup fn sooner.
|
|
7622
|
+
stateMap.delete(component);
|
|
7623
|
+
}
|
|
7624
|
+
if (_prevUnmount) {
|
|
7625
|
+
_prevUnmount(vnode);
|
|
7626
|
+
}
|
|
7627
|
+
};
|
|
7628
|
+
|
|
7518
7629
|
installImportMetaCssBuild(import.meta);/**
|
|
7519
7630
|
* Box - A Swiss Army Knife for Layout
|
|
7520
7631
|
*
|
|
@@ -7539,6 +7650,33 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
7539
7650
|
* ## Spacing & Sizing
|
|
7540
7651
|
*
|
|
7541
7652
|
* Props for margin, padding, gap, width, height, expand, shrink, and more.
|
|
7653
|
+
*
|
|
7654
|
+
* ## Pseudo-class Styles
|
|
7655
|
+
*
|
|
7656
|
+
* The `style` prop supports pseudo-class keys alongside regular CSS properties.
|
|
7657
|
+
* This lets you express hover, focus, and custom interaction states in one object,
|
|
7658
|
+
* without writing CSS or adding class names:
|
|
7659
|
+
*
|
|
7660
|
+
* ```jsx
|
|
7661
|
+
* <Box
|
|
7662
|
+
* style={{
|
|
7663
|
+
* backgroundColor: "blue",
|
|
7664
|
+
* ":-navi:pressed": {
|
|
7665
|
+
* backgroundColor: "darkblue",
|
|
7666
|
+
* },
|
|
7667
|
+
* ":hover": {
|
|
7668
|
+
* backgroundColor: "lightblue",
|
|
7669
|
+
* },
|
|
7670
|
+
* }}
|
|
7671
|
+
* />
|
|
7672
|
+
* ```
|
|
7673
|
+
*
|
|
7674
|
+
* Styles are applied directly to the DOM (not via Preact's style prop) for two reasons:
|
|
7675
|
+
* 1. **Pseudo-class support**: reacting to `:hover`, `:focus`, or custom states like
|
|
7676
|
+
* `:-navi:pressed` without re-rendering the component on every pseudo state change.
|
|
7677
|
+
* 2. **Correct initial render**: pseudo-class state must be read from the DOM node at
|
|
7678
|
+
* mount time. Preact's style prop runs before the DOM exists, so the right initial
|
|
7679
|
+
* style can only be determined once the node is available.
|
|
7542
7680
|
*/
|
|
7543
7681
|
import.meta.css = [/* css */`
|
|
7544
7682
|
[navi-box-flow="inline"] {
|
|
@@ -7930,42 +8068,36 @@ const Box = props => {
|
|
|
7930
8068
|
visitProp(styleValue, styleName, styleContext, boxStyles, "style");
|
|
7931
8069
|
}
|
|
7932
8070
|
}
|
|
7933
|
-
|
|
7934
|
-
const boxEl = ref.current;
|
|
7935
|
-
applyStyle(boxEl, boxStyles, state, boxPseudoNamedStyles, preventInitialTransition);
|
|
7936
|
-
}, styleDeps);
|
|
7937
|
-
const finalStyleDeps = [pseudoStateSelector, innerPseudoState, updateStyle];
|
|
8071
|
+
styleDeps.push(pseudoStateSelector, innerPseudoState);
|
|
7938
8072
|
let innerPseudoClasses;
|
|
7939
8073
|
if (pseudoClassesFromStyleSet.size) {
|
|
7940
8074
|
innerPseudoClasses = [...pseudoClasses];
|
|
7941
8075
|
if (pseudoClasses !== PSEUDO_CLASSES_DEFAULT) {
|
|
7942
|
-
|
|
8076
|
+
styleDeps.push(...pseudoClasses);
|
|
7943
8077
|
}
|
|
7944
8078
|
for (const key of pseudoClassesFromStyleSet) {
|
|
7945
8079
|
innerPseudoClasses.push(key);
|
|
7946
|
-
|
|
8080
|
+
styleDeps.push(key);
|
|
7947
8081
|
}
|
|
7948
8082
|
} else {
|
|
7949
8083
|
innerPseudoClasses = pseudoClasses;
|
|
7950
8084
|
if (pseudoClasses !== PSEUDO_CLASSES_DEFAULT) {
|
|
7951
|
-
|
|
8085
|
+
styleDeps.push(...pseudoClasses);
|
|
7952
8086
|
}
|
|
7953
8087
|
}
|
|
7954
|
-
|
|
7955
|
-
const boxEl = ref.current;
|
|
7956
|
-
if (!boxEl) {
|
|
7957
|
-
return null;
|
|
7958
|
-
}
|
|
8088
|
+
useEarlyDOMEffect(boxEl => {
|
|
7959
8089
|
const pseudoStateEl = pseudoStateSelector ? boxEl.querySelector(pseudoStateSelector) : boxEl;
|
|
7960
8090
|
const visualEl = visualSelector ? boxEl.querySelector(visualSelector) : null;
|
|
7961
8091
|
return initPseudoStyles(pseudoStateEl, {
|
|
7962
8092
|
pseudoClasses: innerPseudoClasses,
|
|
7963
8093
|
pseudoState: innerPseudoState,
|
|
7964
|
-
effect:
|
|
8094
|
+
effect: state => {
|
|
8095
|
+
applyStyle(boxEl, boxStyles, state, boxPseudoNamedStyles, preventInitialTransition);
|
|
8096
|
+
},
|
|
7965
8097
|
elementToImpact: boxEl,
|
|
7966
8098
|
elementListeningPseudoState: visualEl === pseudoStateEl ? null : visualEl
|
|
7967
8099
|
});
|
|
7968
|
-
},
|
|
8100
|
+
}, styleDeps);
|
|
7969
8101
|
}
|
|
7970
8102
|
|
|
7971
8103
|
// When hasChildFunction is used it means
|
|
@@ -13884,7 +14016,7 @@ const RouteActive = ({
|
|
|
13884
14016
|
const routeAction = (
|
|
13885
14017
|
route,
|
|
13886
14018
|
action,
|
|
13887
|
-
paramsEffect = () =>
|
|
14019
|
+
paramsEffect = () => true,
|
|
13888
14020
|
options = {},
|
|
13889
14021
|
) => {
|
|
13890
14022
|
const actionBoundToRoute = actionRunEffect(
|
|
@@ -15656,7 +15788,7 @@ installImportMetaCssBuild(import.meta);
|
|
|
15656
15788
|
* - Centers in viewport when no anchor element provided or anchor is too big
|
|
15657
15789
|
*/
|
|
15658
15790
|
|
|
15659
|
-
|
|
15791
|
+
const css$w = /* css */`
|
|
15660
15792
|
@layer navi {
|
|
15661
15793
|
.navi_callout {
|
|
15662
15794
|
--callout-success-color: #4caf50;
|
|
@@ -15798,7 +15930,7 @@ import.meta.css = [/* css */`
|
|
|
15798
15930
|
}
|
|
15799
15931
|
}
|
|
15800
15932
|
}
|
|
15801
|
-
|
|
15933
|
+
`;
|
|
15802
15934
|
|
|
15803
15935
|
/**
|
|
15804
15936
|
* Shows a callout attached to the specified element
|
|
@@ -15830,6 +15962,7 @@ const openCallout = (message, {
|
|
|
15830
15962
|
showErrorStack,
|
|
15831
15963
|
debug = false
|
|
15832
15964
|
} = {}) => {
|
|
15965
|
+
import.meta.css = [css$w, "@jsenv/navi/src/field/validation/callout/callout.js"];
|
|
15833
15966
|
const callout = {
|
|
15834
15967
|
opened: true,
|
|
15835
15968
|
close: null,
|
|
@@ -15989,8 +16122,14 @@ const openCallout = (message, {
|
|
|
15989
16122
|
}
|
|
15990
16123
|
allowWheelThrough(calloutElement, anchorElement);
|
|
15991
16124
|
anchorElement.setAttribute("data-callout", calloutId);
|
|
16125
|
+
dispatchCalloutCustomElement(anchorElement, new CustomEvent("navi_callout_open", {
|
|
16126
|
+
bubbles: true
|
|
16127
|
+
}));
|
|
15992
16128
|
addTeardown(() => {
|
|
15993
16129
|
anchorElement.removeAttribute("data-callout");
|
|
16130
|
+
dispatchCalloutCustomElement(anchorElement, new CustomEvent("navi_callout_close", {
|
|
16131
|
+
bubbles: true
|
|
16132
|
+
}));
|
|
15994
16133
|
});
|
|
15995
16134
|
addStatusEffect(status => {
|
|
15996
16135
|
if (!status) {
|
|
@@ -16621,6 +16760,20 @@ const generateSvgWithoutArrow = (width, height) => {
|
|
|
16621
16760
|
/>
|
|
16622
16761
|
</svg>`;
|
|
16623
16762
|
};
|
|
16763
|
+
const dispatchCalloutCustomElement = (anchorElement, customEvent) => {
|
|
16764
|
+
let targetElement;
|
|
16765
|
+
const visualSelector = anchorElement.getAttribute("data-visual-selector");
|
|
16766
|
+
if (visualSelector) {
|
|
16767
|
+
const visualElement = anchorElement.querySelector(visualSelector);
|
|
16768
|
+
if (visualElement) {
|
|
16769
|
+
targetElement = visualElement;
|
|
16770
|
+
}
|
|
16771
|
+
} else {
|
|
16772
|
+
targetElement = anchorElement;
|
|
16773
|
+
}
|
|
16774
|
+
console.log("dispatch on", targetElement, "event", customEvent);
|
|
16775
|
+
targetElement.dispatchEvent(customEvent);
|
|
16776
|
+
};
|
|
16624
16777
|
|
|
16625
16778
|
/**
|
|
16626
16779
|
* Creates a live mirror of a source DOM element that automatically stays in sync.
|
|
@@ -16996,6 +17149,56 @@ CONSTRAINT_ATTRIBUTE_SET.add("data-special-charset");
|
|
|
16996
17149
|
CONSTRAINT_ATTRIBUTE_SET.add("data-min-special-char");
|
|
16997
17150
|
CONSTRAINT_ATTRIBUTE_SET.add("data-min-special-char-message");
|
|
16998
17151
|
|
|
17152
|
+
const ONE_OF_CONSTRAINT = {
|
|
17153
|
+
name: "one_of",
|
|
17154
|
+
messageAttribute: "data-one-of-message",
|
|
17155
|
+
check: (field) => {
|
|
17156
|
+
const oneOf = field.getAttribute("data-one-of");
|
|
17157
|
+
if (!oneOf) {
|
|
17158
|
+
return null;
|
|
17159
|
+
}
|
|
17160
|
+
const fieldValue = field.value;
|
|
17161
|
+
if (!fieldValue) {
|
|
17162
|
+
return null;
|
|
17163
|
+
}
|
|
17164
|
+
const listEl = document.querySelector(oneOf);
|
|
17165
|
+
if (!listEl) {
|
|
17166
|
+
console.warn(
|
|
17167
|
+
`One of constraint: could not find element for selector "${oneOf}"`,
|
|
17168
|
+
);
|
|
17169
|
+
return null;
|
|
17170
|
+
}
|
|
17171
|
+
const allowedValues = collectAllowedValues(listEl);
|
|
17172
|
+
if (allowedValues.size === 0) {
|
|
17173
|
+
return null;
|
|
17174
|
+
}
|
|
17175
|
+
if (allowedValues.has(fieldValue)) {
|
|
17176
|
+
return null;
|
|
17177
|
+
}
|
|
17178
|
+
const message = field.getAttribute("data-one-of-message");
|
|
17179
|
+
if (message) {
|
|
17180
|
+
return message;
|
|
17181
|
+
}
|
|
17182
|
+
return `Veuillez choisir une valeur parmi les suggestions.`;
|
|
17183
|
+
},
|
|
17184
|
+
};
|
|
17185
|
+
CONSTRAINT_ATTRIBUTE_SET.add("data-one-of");
|
|
17186
|
+
CONSTRAINT_ATTRIBUTE_SET.add("data-one-of-message");
|
|
17187
|
+
|
|
17188
|
+
const collectAllowedValues = (listEl) => {
|
|
17189
|
+
const values = new Set();
|
|
17190
|
+
for (const optionEl of listEl.querySelectorAll("[role='option']")) {
|
|
17191
|
+
const value =
|
|
17192
|
+
optionEl.dataset.value ??
|
|
17193
|
+
optionEl.getAttribute("value") ??
|
|
17194
|
+
optionEl.textContent.trim();
|
|
17195
|
+
if (value) {
|
|
17196
|
+
values.add(value);
|
|
17197
|
+
}
|
|
17198
|
+
}
|
|
17199
|
+
return values;
|
|
17200
|
+
};
|
|
17201
|
+
|
|
16999
17202
|
const READONLY_CONSTRAINT = {
|
|
17000
17203
|
name: "readonly",
|
|
17001
17204
|
messageAttribute: "data-readonly-message",
|
|
@@ -17748,6 +17951,7 @@ const NAVI_CONSTRAINT_SET = new Set([
|
|
|
17748
17951
|
MIN_UPPER_LETTER_CONSTRAINT,
|
|
17749
17952
|
MIN_LOWER_LETTER_CONSTRAINT,
|
|
17750
17953
|
SAME_AS_CONSTRAINT,
|
|
17954
|
+
ONE_OF_CONSTRAINT,
|
|
17751
17955
|
READONLY_CONSTRAINT,
|
|
17752
17956
|
]);
|
|
17753
17957
|
const DEFAULT_CONSTRAINT_SET = new Set([
|
|
@@ -20166,7 +20370,7 @@ const selectByTextStrings = (element, range, startText, endText) => {
|
|
|
20166
20370
|
};
|
|
20167
20371
|
|
|
20168
20372
|
installImportMetaCssBuild(import.meta);/* eslint-disable jsenv/no-unknown-params */
|
|
20169
|
-
const css$
|
|
20373
|
+
const css$v = /* css */`
|
|
20170
20374
|
@layer navi {
|
|
20171
20375
|
.navi_text {
|
|
20172
20376
|
&[data-skeleton] {
|
|
@@ -20475,7 +20679,7 @@ const shouldInjectSpacingBetween = (left, right) => {
|
|
|
20475
20679
|
};
|
|
20476
20680
|
const OverflowPinnedElementContext = createContext(null);
|
|
20477
20681
|
const Text = props => {
|
|
20478
|
-
import.meta.css = [css$
|
|
20682
|
+
import.meta.css = [css$v, "@jsenv/navi/src/text/text.jsx"];
|
|
20479
20683
|
if (props.loading || props.skeleton) {
|
|
20480
20684
|
return jsx(TextSkeleton, {
|
|
20481
20685
|
...props
|
|
@@ -20671,7 +20875,7 @@ const TextBasic = ({
|
|
|
20671
20875
|
});
|
|
20672
20876
|
};
|
|
20673
20877
|
|
|
20674
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
20878
|
+
installImportMetaCssBuild(import.meta);const css$u = /* css */`
|
|
20675
20879
|
.navi_text_anchor {
|
|
20676
20880
|
vertical-align: baseline;
|
|
20677
20881
|
user-select: none;
|
|
@@ -20706,7 +20910,7 @@ const TextAnchor = ({
|
|
|
20706
20910
|
textSize,
|
|
20707
20911
|
lineLayout
|
|
20708
20912
|
}) => {
|
|
20709
|
-
import.meta.css = [css$
|
|
20913
|
+
import.meta.css = [css$u, "@jsenv/navi/src/text/text_anchor.jsx"];
|
|
20710
20914
|
const anchorRef = useRef();
|
|
20711
20915
|
useLayoutEffect(() => {
|
|
20712
20916
|
const anchorEl = anchorRef.current;
|
|
@@ -20801,7 +21005,7 @@ const computeTopOffset = ({
|
|
|
20801
21005
|
};
|
|
20802
21006
|
const charTopCanvas = document.createElement("canvas");
|
|
20803
21007
|
|
|
20804
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
21008
|
+
installImportMetaCssBuild(import.meta);const css$t = /* css */`
|
|
20805
21009
|
@layer navi {
|
|
20806
21010
|
/* Ensure data attributes from box.jsx can win to update display */
|
|
20807
21011
|
.navi_icon {
|
|
@@ -20874,7 +21078,7 @@ const Icon = ({
|
|
|
20874
21078
|
lineLayout,
|
|
20875
21079
|
...props
|
|
20876
21080
|
}) => {
|
|
20877
|
-
import.meta.css = [css$
|
|
21081
|
+
import.meta.css = [css$t, "@jsenv/navi/src/text/icon.jsx"];
|
|
20878
21082
|
const innerChildren = href ? jsx("svg", {
|
|
20879
21083
|
width: "100%",
|
|
20880
21084
|
height: "100%",
|
|
@@ -20955,6 +21159,109 @@ const Icon = ({
|
|
|
20955
21159
|
});
|
|
20956
21160
|
};
|
|
20957
21161
|
|
|
21162
|
+
/**
|
|
21163
|
+
* Toggles a `data-dark-background` attribute on the referenced element based on its
|
|
21164
|
+
* computed background color. Pair it with a CSS variable to get automatic
|
|
21165
|
+
* light/dark text without hard-coding colors:
|
|
21166
|
+
*
|
|
21167
|
+
* ```css
|
|
21168
|
+
* .my-element {
|
|
21169
|
+
* --color-contrasting: black;
|
|
21170
|
+
* &[data-dark-background] {
|
|
21171
|
+
* --color-contrasting: white;
|
|
21172
|
+
* }
|
|
21173
|
+
* color: var(--color-contrasting);
|
|
21174
|
+
* }
|
|
21175
|
+
* ```
|
|
21176
|
+
*
|
|
21177
|
+
* - `data-dark-background` is **set** when the background is dark enough that white text
|
|
21178
|
+
* provides better (or equal) contrast.
|
|
21179
|
+
* - `data-dark-background` is **absent** when black text is the better choice.
|
|
21180
|
+
*
|
|
21181
|
+
* @param {import("preact").RefObject} ref - Ref to the element that receives
|
|
21182
|
+
* the `data-dark-background` attribute and is also passed to `contrastColor` for
|
|
21183
|
+
* resolving CSS variables.
|
|
21184
|
+
* @param {object} [options]
|
|
21185
|
+
* @param {string} [options.backgroundElementSelector] - CSS selector relative
|
|
21186
|
+
* to `ref.current` pointing to a child element whose `background-color`
|
|
21187
|
+
* should be tested instead of the element itself. Useful when the element
|
|
21188
|
+
* has a transparent background but contains a coloured child (e.g. a fill
|
|
21189
|
+
* bar inside a track).
|
|
21190
|
+
*/
|
|
21191
|
+
|
|
21192
|
+
const useDarkBackgroundAttribute = (
|
|
21193
|
+
ref,
|
|
21194
|
+
deps = [],
|
|
21195
|
+
{
|
|
21196
|
+
backgroundElementSelector,
|
|
21197
|
+
attributeName = "data-dark-background",
|
|
21198
|
+
hardcoded = {},
|
|
21199
|
+
} = {},
|
|
21200
|
+
) => {
|
|
21201
|
+
const innerDeps = [
|
|
21202
|
+
...deps,
|
|
21203
|
+
// ref can change is the component pass a different ref on different render based on some logic
|
|
21204
|
+
// (can be used to control which element backgroundColor is being checked by switching the ref to another element)
|
|
21205
|
+
ref,
|
|
21206
|
+
// backgroundElementSelector can change if the component pass a different selector on different render based on some logic
|
|
21207
|
+
// (can be used to control which element backgroundColor is being checked by switching the selector to point to another element)
|
|
21208
|
+
backgroundElementSelector,
|
|
21209
|
+
];
|
|
21210
|
+
|
|
21211
|
+
const hardcodedMap = new Map();
|
|
21212
|
+
for (const key of Object.keys(hardcoded)) {
|
|
21213
|
+
const value = hardcoded[key];
|
|
21214
|
+
innerDeps.push(key, value);
|
|
21215
|
+
const colorString = normalizeColorString(key);
|
|
21216
|
+
hardcodedMap.set(colorString, value);
|
|
21217
|
+
}
|
|
21218
|
+
|
|
21219
|
+
useLayoutEffect(() => {
|
|
21220
|
+
const el = ref.current;
|
|
21221
|
+
if (!el) {
|
|
21222
|
+
return undefined;
|
|
21223
|
+
}
|
|
21224
|
+
let elementToCheck = el;
|
|
21225
|
+
if (backgroundElementSelector) {
|
|
21226
|
+
elementToCheck = el.querySelector(backgroundElementSelector);
|
|
21227
|
+
if (!elementToCheck) {
|
|
21228
|
+
return undefined;
|
|
21229
|
+
}
|
|
21230
|
+
}
|
|
21231
|
+
const updateAttribute = () => {
|
|
21232
|
+
const computedStyle = getComputedStyle(elementToCheck);
|
|
21233
|
+
const backgroundColor = computedStyle.backgroundColor;
|
|
21234
|
+
if (!backgroundColor) {
|
|
21235
|
+
el.removeAttribute(attributeName);
|
|
21236
|
+
return;
|
|
21237
|
+
}
|
|
21238
|
+
const backgroundColorString = normalizeColorString(backgroundColor, el);
|
|
21239
|
+
const hardcodedContrast = hardcodedMap.get(backgroundColorString);
|
|
21240
|
+
const contrastingColor =
|
|
21241
|
+
hardcodedContrast || contrastColor(backgroundColor, el);
|
|
21242
|
+
if (contrastingColor === "white") {
|
|
21243
|
+
el.setAttribute(attributeName, "");
|
|
21244
|
+
} else {
|
|
21245
|
+
el.removeAttribute(attributeName);
|
|
21246
|
+
}
|
|
21247
|
+
};
|
|
21248
|
+
updateAttribute();
|
|
21249
|
+
el.addEventListener(NAVI_PSEUDO_STATE_CUSTOM_EVENT, updateAttribute);
|
|
21250
|
+
return () => {
|
|
21251
|
+
el.removeEventListener(NAVI_PSEUDO_STATE_CUSTOM_EVENT, updateAttribute);
|
|
21252
|
+
el.removeAttribute(attributeName);
|
|
21253
|
+
};
|
|
21254
|
+
}, innerDeps);
|
|
21255
|
+
};
|
|
21256
|
+
|
|
21257
|
+
const normalizeColorString = (color, el) => {
|
|
21258
|
+
const colorRgba = resolveCSSColor(color, el);
|
|
21259
|
+
if (!colorRgba) {
|
|
21260
|
+
return "";
|
|
21261
|
+
}
|
|
21262
|
+
return String(colorRgba);
|
|
21263
|
+
};
|
|
21264
|
+
|
|
20958
21265
|
const useFormEvents = (
|
|
20959
21266
|
elementRef,
|
|
20960
21267
|
{
|
|
@@ -21477,7 +21784,7 @@ const useUIState = (uiStateController) => {
|
|
|
21477
21784
|
};
|
|
21478
21785
|
|
|
21479
21786
|
installImportMetaCssBuild(import.meta);/* eslint-disable jsenv/no-unknown-params */
|
|
21480
|
-
const css$
|
|
21787
|
+
const css$s = /* css */`
|
|
21481
21788
|
@layer navi {
|
|
21482
21789
|
.navi_button {
|
|
21483
21790
|
--button-outline-width: 1px;
|
|
@@ -21559,8 +21866,6 @@ const css$r = /* css */`
|
|
|
21559
21866
|
--x-button-background-color: var(--button-background-color);
|
|
21560
21867
|
--x-button-color: var(--button-color);
|
|
21561
21868
|
--x-button-cursor: var(--button-cursor);
|
|
21562
|
-
|
|
21563
|
-
position: relative;
|
|
21564
21869
|
box-sizing: border-box;
|
|
21565
21870
|
aspect-ratio: inherit;
|
|
21566
21871
|
padding: 0;
|
|
@@ -21573,6 +21878,10 @@ const css$r = /* css */`
|
|
|
21573
21878
|
touch-action: manipulation;
|
|
21574
21879
|
user-select: none;
|
|
21575
21880
|
|
|
21881
|
+
&[data-dark-background] {
|
|
21882
|
+
--button-color: white;
|
|
21883
|
+
}
|
|
21884
|
+
|
|
21576
21885
|
&[data-icon] {
|
|
21577
21886
|
--button-padding: 0;
|
|
21578
21887
|
}
|
|
@@ -21741,7 +22050,7 @@ const css$r = /* css */`
|
|
|
21741
22050
|
}
|
|
21742
22051
|
`;
|
|
21743
22052
|
const Button = props => {
|
|
21744
|
-
import.meta.css = [css$
|
|
22053
|
+
import.meta.css = [css$s, "@jsenv/navi/src/field/button.jsx"];
|
|
21745
22054
|
return renderActionableComponent(props, {
|
|
21746
22055
|
Basic: ButtonBasicDispatch,
|
|
21747
22056
|
WithAction: ButtonWithAction,
|
|
@@ -21855,6 +22164,7 @@ const ButtonBasic = props => {
|
|
|
21855
22164
|
innerTarget = target === undefined ? isSameSite ? undefined : "_blank" : target;
|
|
21856
22165
|
innerRel = rel === undefined ? isSameSite ? undefined : "noopener noreferrer" : rel;
|
|
21857
22166
|
}
|
|
22167
|
+
useDarkBackgroundAttribute(ref, [innerLoading, innerDisabled, innerReadOnly]);
|
|
21858
22168
|
const renderButtonContent = buttonProps => {
|
|
21859
22169
|
return jsxs(Text, {
|
|
21860
22170
|
...buttonProps,
|
|
@@ -22217,104 +22527,6 @@ const Title = props => {
|
|
|
22217
22527
|
});
|
|
22218
22528
|
};
|
|
22219
22529
|
|
|
22220
|
-
/**
|
|
22221
|
-
* Toggles a `data-dark-background` attribute on the referenced element based on its
|
|
22222
|
-
* computed background color. Pair it with a CSS variable to get automatic
|
|
22223
|
-
* light/dark text without hard-coding colors:
|
|
22224
|
-
*
|
|
22225
|
-
* ```css
|
|
22226
|
-
* .my-element {
|
|
22227
|
-
* --color-contrasting: black;
|
|
22228
|
-
* &[data-dark-background] {
|
|
22229
|
-
* --color-contrasting: white;
|
|
22230
|
-
* }
|
|
22231
|
-
* color: var(--color-contrasting);
|
|
22232
|
-
* }
|
|
22233
|
-
* ```
|
|
22234
|
-
*
|
|
22235
|
-
* - `data-dark-background` is **set** when the background is dark enough that white text
|
|
22236
|
-
* provides better (or equal) contrast.
|
|
22237
|
-
* - `data-dark-background` is **absent** when black text is the better choice.
|
|
22238
|
-
*
|
|
22239
|
-
* @param {import("preact").RefObject} ref - Ref to the element that receives
|
|
22240
|
-
* the `data-dark-background` attribute and is also passed to `contrastColor` for
|
|
22241
|
-
* resolving CSS variables.
|
|
22242
|
-
* @param {object} [options]
|
|
22243
|
-
* @param {string} [options.backgroundElementSelector] - CSS selector relative
|
|
22244
|
-
* to `ref.current` pointing to a child element whose `background-color`
|
|
22245
|
-
* should be tested instead of the element itself. Useful when the element
|
|
22246
|
-
* has a transparent background but contains a coloured child (e.g. a fill
|
|
22247
|
-
* bar inside a track).
|
|
22248
|
-
*/
|
|
22249
|
-
|
|
22250
|
-
const useDarkBackgroundAttribute = (
|
|
22251
|
-
ref,
|
|
22252
|
-
deps = [],
|
|
22253
|
-
{
|
|
22254
|
-
backgroundElementSelector,
|
|
22255
|
-
attributeName = "data-dark-background",
|
|
22256
|
-
hardcoded = {},
|
|
22257
|
-
} = {},
|
|
22258
|
-
) => {
|
|
22259
|
-
const innerDeps = [
|
|
22260
|
-
...deps,
|
|
22261
|
-
// ref can change is the component pass a different ref on different render based on some logic
|
|
22262
|
-
// (can be used to control which element backgroundColor is being checked by switching the ref to another element)
|
|
22263
|
-
ref,
|
|
22264
|
-
// backgroundElementSelector can change if the component pass a different selector on different render based on some logic
|
|
22265
|
-
// (can be used to control which element backgroundColor is being checked by switching the selector to point to another element)
|
|
22266
|
-
backgroundElementSelector,
|
|
22267
|
-
];
|
|
22268
|
-
|
|
22269
|
-
const hardcodedMap = new Map();
|
|
22270
|
-
for (const key of Object.keys(hardcoded)) {
|
|
22271
|
-
const value = hardcoded[key];
|
|
22272
|
-
innerDeps.push(key, value);
|
|
22273
|
-
const colorString = normalizeColorString(key);
|
|
22274
|
-
hardcodedMap.set(colorString, value);
|
|
22275
|
-
}
|
|
22276
|
-
|
|
22277
|
-
useLayoutEffect(() => {
|
|
22278
|
-
const el = ref.current;
|
|
22279
|
-
if (!el) {
|
|
22280
|
-
return null;
|
|
22281
|
-
}
|
|
22282
|
-
let elementToCheck = el;
|
|
22283
|
-
if (backgroundElementSelector) {
|
|
22284
|
-
elementToCheck = el.querySelector(backgroundElementSelector);
|
|
22285
|
-
if (!elementToCheck) {
|
|
22286
|
-
return null;
|
|
22287
|
-
}
|
|
22288
|
-
}
|
|
22289
|
-
const computedStyle = getComputedStyle(elementToCheck);
|
|
22290
|
-
const backgroundColor = computedStyle.backgroundColor;
|
|
22291
|
-
if (!backgroundColor) {
|
|
22292
|
-
el.removeAttribute(attributeName);
|
|
22293
|
-
return null;
|
|
22294
|
-
}
|
|
22295
|
-
const backgroundColorString = normalizeColorString(backgroundColor, el);
|
|
22296
|
-
const hardcodedContrast = hardcodedMap.get(backgroundColorString);
|
|
22297
|
-
const contrastingColor =
|
|
22298
|
-
hardcodedContrast || contrastColor(backgroundColor, el);
|
|
22299
|
-
if (contrastingColor === "white") {
|
|
22300
|
-
el.setAttribute(attributeName, "");
|
|
22301
|
-
return () => {
|
|
22302
|
-
el.removeAttribute(attributeName);
|
|
22303
|
-
};
|
|
22304
|
-
}
|
|
22305
|
-
el.removeAttribute(attributeName);
|
|
22306
|
-
return null;
|
|
22307
|
-
}, innerDeps);
|
|
22308
|
-
};
|
|
22309
|
-
|
|
22310
|
-
const normalizeColorString = (color, el) => {
|
|
22311
|
-
const colorRgba = resolveCSSColor(color, el);
|
|
22312
|
-
if (!colorRgba) {
|
|
22313
|
-
return "";
|
|
22314
|
-
}
|
|
22315
|
-
return String(colorRgba);
|
|
22316
|
-
};
|
|
22317
|
-
|
|
22318
22530
|
/**
|
|
22319
22531
|
* Hook that reactively checks if a URL is visited.
|
|
22320
22532
|
* Re-renders when the visited URL set changes.
|
|
@@ -22370,7 +22582,7 @@ const useDimColorWhen = (elementRef, shouldDim) => {
|
|
|
22370
22582
|
};
|
|
22371
22583
|
|
|
22372
22584
|
installImportMetaCssBuild(import.meta);/* eslint-disable jsenv/no-unknown-params */
|
|
22373
|
-
const css$
|
|
22585
|
+
const css$r = /* css */`
|
|
22374
22586
|
@layer navi {
|
|
22375
22587
|
.navi_link {
|
|
22376
22588
|
--link-border-radius: unset;
|
|
@@ -22442,10 +22654,22 @@ const css$q = /* css */`
|
|
|
22442
22654
|
|
|
22443
22655
|
position: relative;
|
|
22444
22656
|
aspect-ratio: inherit;
|
|
22445
|
-
padding-top: max(
|
|
22446
|
-
|
|
22447
|
-
|
|
22448
|
-
|
|
22657
|
+
padding-top: max(
|
|
22658
|
+
var(--x-link-padding-top),
|
|
22659
|
+
var(--link-loading-outline-size)
|
|
22660
|
+
);
|
|
22661
|
+
padding-right: max(
|
|
22662
|
+
var(--x-link-padding-right),
|
|
22663
|
+
var(--link-loading-outline-size)
|
|
22664
|
+
);
|
|
22665
|
+
padding-bottom: max(
|
|
22666
|
+
var(--x-link-padding-bottom),
|
|
22667
|
+
var(--link-loading-outline-size)
|
|
22668
|
+
);
|
|
22669
|
+
padding-left: max(
|
|
22670
|
+
var(--x-link-padding-left),
|
|
22671
|
+
var(--link-loading-outline-size)
|
|
22672
|
+
);
|
|
22449
22673
|
color: var(--x-link-color);
|
|
22450
22674
|
text-decoration: var(--x-link-text-decoration);
|
|
22451
22675
|
background: var(--x-link-background);
|
|
@@ -22712,13 +22936,10 @@ Object.assign(PSEUDO_CLASSES, {
|
|
|
22712
22936
|
},
|
|
22713
22937
|
":-navi-href-current": {
|
|
22714
22938
|
attribute: "data-href-current"
|
|
22715
|
-
},
|
|
22716
|
-
":-navi-selected": {
|
|
22717
|
-
attribute: "data-selected"
|
|
22718
22939
|
}
|
|
22719
22940
|
});
|
|
22720
22941
|
const Link = props => {
|
|
22721
|
-
import.meta.css = [css$
|
|
22942
|
+
import.meta.css = [css$r, "@jsenv/navi/src/nav/link/link.jsx"];
|
|
22722
22943
|
return renderActionableComponent(props, {
|
|
22723
22944
|
Basic: LinkBasic,
|
|
22724
22945
|
WithAction: LinkWithAction
|
|
@@ -22980,7 +23201,7 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
22980
23201
|
* TabList component with support for horizontal and vertical layouts
|
|
22981
23202
|
* https://dribbble.com/search/tabs
|
|
22982
23203
|
*/
|
|
22983
|
-
const css$
|
|
23204
|
+
const css$q = /* css */`
|
|
22984
23205
|
@layer navi {
|
|
22985
23206
|
.navi_nav {
|
|
22986
23207
|
--nav-border: none;
|
|
@@ -23116,7 +23337,7 @@ const Nav = ({
|
|
|
23116
23337
|
panelBorderConnection,
|
|
23117
23338
|
...props
|
|
23118
23339
|
}) => {
|
|
23119
|
-
import.meta.css = [css$
|
|
23340
|
+
import.meta.css = [css$q, "@jsenv/navi/src/nav/link/nav.jsx"];
|
|
23120
23341
|
children = toChildArray(children);
|
|
23121
23342
|
return jsx(Box, {
|
|
23122
23343
|
as: "nav",
|
|
@@ -23164,7 +23385,7 @@ const useFocusGroup = (
|
|
|
23164
23385
|
|
|
23165
23386
|
installImportMetaCssBuild(import.meta);const rightArrowPath = "M680-480L360-160l-80-80 240-240-240-240 80-80 320 320z";
|
|
23166
23387
|
const downArrowPath = "M480-280L160-600l80-80 240 240 240-240 80 80-320 320z";
|
|
23167
|
-
const css$
|
|
23388
|
+
const css$p = /* css */`
|
|
23168
23389
|
.navi_summary_marker {
|
|
23169
23390
|
width: 1em;
|
|
23170
23391
|
height: 1em;
|
|
@@ -23249,7 +23470,7 @@ const SummaryMarker = ({
|
|
|
23249
23470
|
open,
|
|
23250
23471
|
loading
|
|
23251
23472
|
}) => {
|
|
23252
|
-
import.meta.css = [css$
|
|
23473
|
+
import.meta.css = [css$p, "@jsenv/navi/src/field/details/summary_marker.jsx"];
|
|
23253
23474
|
const showLoading = useDebounceTrue(loading, 300);
|
|
23254
23475
|
const mountedRef = useRef(false);
|
|
23255
23476
|
const prevOpenRef = useRef(open);
|
|
@@ -23303,7 +23524,7 @@ const SummaryMarker = ({
|
|
|
23303
23524
|
});
|
|
23304
23525
|
};
|
|
23305
23526
|
|
|
23306
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
23527
|
+
installImportMetaCssBuild(import.meta);const css$o = /* css */`
|
|
23307
23528
|
.navi_details {
|
|
23308
23529
|
position: relative;
|
|
23309
23530
|
z-index: 1;
|
|
@@ -23340,7 +23561,7 @@ installImportMetaCssBuild(import.meta);const css$n = /* css */`
|
|
|
23340
23561
|
}
|
|
23341
23562
|
`;
|
|
23342
23563
|
const Details = props => {
|
|
23343
|
-
import.meta.css = [css$
|
|
23564
|
+
import.meta.css = [css$o, "@jsenv/navi/src/field/details/details.jsx"];
|
|
23344
23565
|
const {
|
|
23345
23566
|
value = "on",
|
|
23346
23567
|
persists
|
|
@@ -23655,7 +23876,7 @@ const fieldPropSet = new Set([
|
|
|
23655
23876
|
"data-testid",
|
|
23656
23877
|
]);
|
|
23657
23878
|
|
|
23658
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
23879
|
+
installImportMetaCssBuild(import.meta);const css$n = /* css */`
|
|
23659
23880
|
@layer navi {
|
|
23660
23881
|
label {
|
|
23661
23882
|
&[data-interactive] {
|
|
@@ -23687,7 +23908,7 @@ const reportDisabledToLabel = value => {
|
|
|
23687
23908
|
};
|
|
23688
23909
|
const LabelPseudoClasses = [":hover", ":active", ":focus", ":focus-visible", ":read-only", ":disabled", ":-navi-loading"];
|
|
23689
23910
|
const Label = props => {
|
|
23690
|
-
import.meta.css = [css$
|
|
23911
|
+
import.meta.css = [css$n, "@jsenv/navi/src/field/label.jsx"];
|
|
23691
23912
|
const {
|
|
23692
23913
|
readOnly,
|
|
23693
23914
|
disabled,
|
|
@@ -23721,7 +23942,7 @@ const Label = props => {
|
|
|
23721
23942
|
});
|
|
23722
23943
|
};
|
|
23723
23944
|
|
|
23724
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
23945
|
+
installImportMetaCssBuild(import.meta);const css$m = /* css */`
|
|
23725
23946
|
@layer navi {
|
|
23726
23947
|
.navi_checkbox {
|
|
23727
23948
|
--margin: 3px 3px 3px 4px;
|
|
@@ -24048,7 +24269,7 @@ installImportMetaCssBuild(import.meta);const css$l = /* css */`
|
|
|
24048
24269
|
}
|
|
24049
24270
|
`;
|
|
24050
24271
|
const InputCheckbox = props => {
|
|
24051
|
-
import.meta.css = [css$
|
|
24272
|
+
import.meta.css = [css$m, "@jsenv/navi/src/field/input_checkbox.jsx"];
|
|
24052
24273
|
const {
|
|
24053
24274
|
value = "on"
|
|
24054
24275
|
} = props;
|
|
@@ -24462,7 +24683,7 @@ forwardRef((props, ref) => {
|
|
|
24462
24683
|
});
|
|
24463
24684
|
});
|
|
24464
24685
|
|
|
24465
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
24686
|
+
installImportMetaCssBuild(import.meta);const css$l = /* css */`
|
|
24466
24687
|
@layer navi {
|
|
24467
24688
|
.navi_radio {
|
|
24468
24689
|
--margin: 3px 3px 0 5px;
|
|
@@ -24755,7 +24976,7 @@ installImportMetaCssBuild(import.meta);const css$k = /* css */`
|
|
|
24755
24976
|
}
|
|
24756
24977
|
`;
|
|
24757
24978
|
const InputRadio = props => {
|
|
24758
|
-
import.meta.css = [css$
|
|
24979
|
+
import.meta.css = [css$l, "@jsenv/navi/src/field/input_radio.jsx"];
|
|
24759
24980
|
const {
|
|
24760
24981
|
value = "on"
|
|
24761
24982
|
} = props;
|
|
@@ -25001,7 +25222,7 @@ const InputRadioWithAction = () => {
|
|
|
25001
25222
|
throw new Error(`<Input type="radio" /> with an action make no sense. Use <RadioList action={something} /> instead`);
|
|
25002
25223
|
};
|
|
25003
25224
|
|
|
25004
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
25225
|
+
installImportMetaCssBuild(import.meta);const css$k = /* css */`
|
|
25005
25226
|
@layer navi {
|
|
25006
25227
|
.navi_input_range {
|
|
25007
25228
|
--border-radius: 6px;
|
|
@@ -25210,7 +25431,7 @@ installImportMetaCssBuild(import.meta);const css$j = /* css */`
|
|
|
25210
25431
|
}
|
|
25211
25432
|
`;
|
|
25212
25433
|
const InputRange = props => {
|
|
25213
|
-
import.meta.css = [css$
|
|
25434
|
+
import.meta.css = [css$k, "@jsenv/navi/src/field/input_range.jsx"];
|
|
25214
25435
|
const uiStateController = useUIStateController(props, "input");
|
|
25215
25436
|
const uiState = useUIState(uiStateController);
|
|
25216
25437
|
const input = renderActionableComponent(props, {
|
|
@@ -25480,24 +25701,8 @@ const SearchSvg = () => jsx("svg", {
|
|
|
25480
25701
|
})
|
|
25481
25702
|
});
|
|
25482
25703
|
|
|
25483
|
-
installImportMetaCssBuild(import.meta)
|
|
25484
|
-
|
|
25485
|
-
*
|
|
25486
|
-
* Supports:
|
|
25487
|
-
* - text (default)
|
|
25488
|
-
* - password
|
|
25489
|
-
* - hidden
|
|
25490
|
-
* - email
|
|
25491
|
-
* - url
|
|
25492
|
-
* - search
|
|
25493
|
-
* - tel
|
|
25494
|
-
* - etc.
|
|
25495
|
-
*
|
|
25496
|
-
* For non-textual inputs, specialized components will be used:
|
|
25497
|
-
* - <InputCheckbox /> for type="checkbox"
|
|
25498
|
-
* - <InputRadio /> for type="radio"
|
|
25499
|
-
*/
|
|
25500
|
-
const css$i = /* css */`
|
|
25704
|
+
installImportMetaCssBuild(import.meta);/* eslint-disable jsenv/no-unknown-params */
|
|
25705
|
+
const css$j = /* css */`
|
|
25501
25706
|
@layer navi {
|
|
25502
25707
|
.navi_input {
|
|
25503
25708
|
--border-radius: 2px;
|
|
@@ -25709,7 +25914,7 @@ const css$i = /* css */`
|
|
|
25709
25914
|
}
|
|
25710
25915
|
`;
|
|
25711
25916
|
const InputTextual = props => {
|
|
25712
|
-
import.meta.css = [css$
|
|
25917
|
+
import.meta.css = [css$j, "@jsenv/navi/src/field/input_textual.jsx"];
|
|
25713
25918
|
const uiStateController = useUIStateController(props, "input");
|
|
25714
25919
|
const uiState = useUIState(uiStateController);
|
|
25715
25920
|
const input = renderActionableComponent(props, {
|
|
@@ -25782,6 +25987,197 @@ Object.assign(PSEUDO_CLASSES, {
|
|
|
25782
25987
|
const InputPseudoElements = ["::-navi-loader"];
|
|
25783
25988
|
const InputChildPropSet = new Set([...fieldPropSet]);
|
|
25784
25989
|
const InputTextualBasic = props => {
|
|
25990
|
+
if (props.combobox) {
|
|
25991
|
+
return jsx(InputTextualCombobox, {
|
|
25992
|
+
...props
|
|
25993
|
+
});
|
|
25994
|
+
}
|
|
25995
|
+
return jsx(InputTextualPlain, {
|
|
25996
|
+
...props
|
|
25997
|
+
});
|
|
25998
|
+
};
|
|
25999
|
+
const InputTextualCombobox = ({
|
|
26000
|
+
combobox,
|
|
26001
|
+
onInput,
|
|
26002
|
+
onFocus,
|
|
26003
|
+
onBlur,
|
|
26004
|
+
...rest
|
|
26005
|
+
}) => {
|
|
26006
|
+
const defaultRef = useRef();
|
|
26007
|
+
const ref = rest.ref || defaultRef;
|
|
26008
|
+
const [comboboxOpen, setComboboxOpen] = useState(false);
|
|
26009
|
+
const comboboxOpenRef = useRef(false);
|
|
26010
|
+
comboboxOpenRef.current = comboboxOpen;
|
|
26011
|
+
const showPopover = e => {
|
|
26012
|
+
if (comboboxOpenRef.current) {
|
|
26013
|
+
return;
|
|
26014
|
+
}
|
|
26015
|
+
console.debug(`showPopover (e.type:${e.type})`);
|
|
26016
|
+
const popoverEl = document.getElementById(combobox);
|
|
26017
|
+
positionPopover();
|
|
26018
|
+
popoverEl.showPopover();
|
|
26019
|
+
comboboxOpenRef.current = true;
|
|
26020
|
+
setComboboxOpen(true);
|
|
26021
|
+
window.addEventListener("scroll", positionPopover, {
|
|
26022
|
+
capture: true,
|
|
26023
|
+
passive: true
|
|
26024
|
+
});
|
|
26025
|
+
};
|
|
26026
|
+
const hidePopover = e => {
|
|
26027
|
+
if (!comboboxOpenRef.current) {
|
|
26028
|
+
return;
|
|
26029
|
+
}
|
|
26030
|
+
console.debug(`hidePopover (e.type:${e.type})`);
|
|
26031
|
+
comboboxOpenRef.current = false;
|
|
26032
|
+
setComboboxOpen(false);
|
|
26033
|
+
window.removeEventListener("scroll", positionPopover, {
|
|
26034
|
+
capture: true
|
|
26035
|
+
});
|
|
26036
|
+
const popoverEl = document.getElementById(combobox);
|
|
26037
|
+
if (popoverEl) {
|
|
26038
|
+
popoverEl.dispatchEvent(new CustomEvent("combobox-clear"));
|
|
26039
|
+
popoverEl.hidePopover();
|
|
26040
|
+
}
|
|
26041
|
+
setComboboxOpen(false);
|
|
26042
|
+
};
|
|
26043
|
+
const positionPopover = () => {
|
|
26044
|
+
const input = ref.current;
|
|
26045
|
+
const rect = input.getBoundingClientRect();
|
|
26046
|
+
const popoverEl = document.getElementById(combobox);
|
|
26047
|
+
if (popoverEl) {
|
|
26048
|
+
popoverEl.style.top = `${rect.bottom + 2}px`;
|
|
26049
|
+
popoverEl.style.left = `${rect.left}px`;
|
|
26050
|
+
popoverEl.style.width = `${rect.width}px`;
|
|
26051
|
+
}
|
|
26052
|
+
};
|
|
26053
|
+
const dispatchToOptionList = customEvent => {
|
|
26054
|
+
const popoverEl = document.getElementById(combobox);
|
|
26055
|
+
if (!popoverEl) {
|
|
26056
|
+
return false;
|
|
26057
|
+
}
|
|
26058
|
+
popoverEl.dispatchEvent(customEvent);
|
|
26059
|
+
return customEvent.defaultPrevented;
|
|
26060
|
+
};
|
|
26061
|
+
useKeyboardShortcuts(ref, [{
|
|
26062
|
+
key: "arrowdown",
|
|
26063
|
+
description: "Open popover and highlight next option",
|
|
26064
|
+
handler: e => {
|
|
26065
|
+
showPopover(e);
|
|
26066
|
+
const popoverEl = document.getElementById(combobox);
|
|
26067
|
+
if (!popoverEl) {
|
|
26068
|
+
return false;
|
|
26069
|
+
}
|
|
26070
|
+
popoverEl.dispatchEvent(new CustomEvent("combobox-navigate", {
|
|
26071
|
+
detail: {
|
|
26072
|
+
direction: "down"
|
|
26073
|
+
}
|
|
26074
|
+
}));
|
|
26075
|
+
return true;
|
|
26076
|
+
}
|
|
26077
|
+
}, {
|
|
26078
|
+
key: "arrowup",
|
|
26079
|
+
description: "Open popover and highlight previous option",
|
|
26080
|
+
handler: e => {
|
|
26081
|
+
showPopover(e);
|
|
26082
|
+
return dispatchToOptionList(new CustomEvent("combobox-navigate", {
|
|
26083
|
+
detail: {
|
|
26084
|
+
direction: "up"
|
|
26085
|
+
}
|
|
26086
|
+
}));
|
|
26087
|
+
}
|
|
26088
|
+
}, {
|
|
26089
|
+
key: "home",
|
|
26090
|
+
description: "Highlight first option",
|
|
26091
|
+
handler: () => {
|
|
26092
|
+
if (!comboboxOpenRef.current) {
|
|
26093
|
+
return false;
|
|
26094
|
+
}
|
|
26095
|
+
return dispatchToOptionList(new CustomEvent("combobox-navigate", {
|
|
26096
|
+
detail: {
|
|
26097
|
+
direction: "first"
|
|
26098
|
+
}
|
|
26099
|
+
}));
|
|
26100
|
+
}
|
|
26101
|
+
}, {
|
|
26102
|
+
key: "end",
|
|
26103
|
+
description: "Highlight last option",
|
|
26104
|
+
handler: () => {
|
|
26105
|
+
if (!comboboxOpenRef.current) {
|
|
26106
|
+
return false;
|
|
26107
|
+
}
|
|
26108
|
+
return dispatchToOptionList(new CustomEvent("combobox-navigate", {
|
|
26109
|
+
detail: {
|
|
26110
|
+
direction: "last"
|
|
26111
|
+
}
|
|
26112
|
+
}));
|
|
26113
|
+
}
|
|
26114
|
+
}, {
|
|
26115
|
+
key: "enter",
|
|
26116
|
+
description: "Confirm highlighted option",
|
|
26117
|
+
handler: () => {
|
|
26118
|
+
if (!comboboxOpenRef.current) {
|
|
26119
|
+
return false;
|
|
26120
|
+
}
|
|
26121
|
+
return dispatchToOptionList(new CustomEvent("combobox-confirm", {
|
|
26122
|
+
cancelable: true
|
|
26123
|
+
}));
|
|
26124
|
+
}
|
|
26125
|
+
}, {
|
|
26126
|
+
key: "escape",
|
|
26127
|
+
description: "Close popover",
|
|
26128
|
+
handler: e => {
|
|
26129
|
+
if (!comboboxOpenRef.current) {
|
|
26130
|
+
return false;
|
|
26131
|
+
}
|
|
26132
|
+
hidePopover(e);
|
|
26133
|
+
return true;
|
|
26134
|
+
}
|
|
26135
|
+
}]);
|
|
26136
|
+
useEffect(() => {
|
|
26137
|
+
const inputEl = ref.current;
|
|
26138
|
+
const popoverEl = document.getElementById(combobox);
|
|
26139
|
+
if (!popoverEl) {
|
|
26140
|
+
return undefined;
|
|
26141
|
+
}
|
|
26142
|
+
const onSelected = e => {
|
|
26143
|
+
inputEl.value = e.detail.value;
|
|
26144
|
+
inputEl.dispatchEvent(new Event("input", {
|
|
26145
|
+
bubbles: true
|
|
26146
|
+
}));
|
|
26147
|
+
hidePopover(e);
|
|
26148
|
+
};
|
|
26149
|
+
popoverEl.addEventListener("combobox-selected", onSelected);
|
|
26150
|
+
return () => {
|
|
26151
|
+
popoverEl.removeEventListener("combobox-selected", onSelected);
|
|
26152
|
+
};
|
|
26153
|
+
}, [combobox]);
|
|
26154
|
+
return jsx(InputTextualPlain, {
|
|
26155
|
+
ref: ref,
|
|
26156
|
+
role: "combobox",
|
|
26157
|
+
autoComplete: "off",
|
|
26158
|
+
"aria-controls": combobox,
|
|
26159
|
+
"aria-haspopup": "listbox",
|
|
26160
|
+
"aria-expanded": comboboxOpen,
|
|
26161
|
+
"aria-autocomplete": "list",
|
|
26162
|
+
onnavi_callout_open: e => {
|
|
26163
|
+
hidePopover(e);
|
|
26164
|
+
},
|
|
26165
|
+
onFocus: e => {
|
|
26166
|
+
onFocus?.(e);
|
|
26167
|
+
showPopover(e);
|
|
26168
|
+
},
|
|
26169
|
+
onBlur: e => {
|
|
26170
|
+
onBlur?.(e);
|
|
26171
|
+
hidePopover(e);
|
|
26172
|
+
},
|
|
26173
|
+
onInput: e => {
|
|
26174
|
+
onInput?.(e);
|
|
26175
|
+
showPopover(e);
|
|
26176
|
+
},
|
|
26177
|
+
...rest
|
|
26178
|
+
});
|
|
26179
|
+
};
|
|
26180
|
+
const InputTextualPlain = props => {
|
|
25785
26181
|
const contextReadOnly = useContext(ReadOnlyContext);
|
|
25786
26182
|
const contextDisabled = useContext(DisabledContext);
|
|
25787
26183
|
const contextLoading = useContext(LoadingContext);
|
|
@@ -25819,6 +26215,9 @@ const InputTextualBasic = props => {
|
|
|
25819
26215
|
const innerOnInput = useStableCallback(onInput);
|
|
25820
26216
|
const autoId = useId();
|
|
25821
26217
|
const innerId = rest.id || autoId;
|
|
26218
|
+
const {
|
|
26219
|
+
...remainingRest
|
|
26220
|
+
} = remainingProps;
|
|
25822
26221
|
const renderInput = inputProps => {
|
|
25823
26222
|
return jsx(Box, {
|
|
25824
26223
|
...inputProps,
|
|
@@ -25887,7 +26286,7 @@ const InputTextualBasic = props => {
|
|
|
25887
26286
|
baseChildPropSet: InputChildPropSet,
|
|
25888
26287
|
"data-start-icon": innerIcon ? "" : undefined,
|
|
25889
26288
|
"data-end-icon": cancelButton ? "" : undefined,
|
|
25890
|
-
...
|
|
26289
|
+
...remainingRest,
|
|
25891
26290
|
ref: undefined,
|
|
25892
26291
|
children: [jsx(LoaderBackground, {
|
|
25893
26292
|
loading: innerLoading,
|
|
@@ -26079,7 +26478,7 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
26079
26478
|
* This means an editable thing MUST have a parent with position relative that wraps the content and the eventual editable input
|
|
26080
26479
|
*
|
|
26081
26480
|
*/
|
|
26082
|
-
const css$
|
|
26481
|
+
const css$i = /* css */`
|
|
26083
26482
|
.navi_editable_wrapper {
|
|
26084
26483
|
--inset-top: 0px;
|
|
26085
26484
|
--inset-right: 0px;
|
|
@@ -26128,7 +26527,7 @@ const useEditionController = () => {
|
|
|
26128
26527
|
};
|
|
26129
26528
|
};
|
|
26130
26529
|
const Editable = props => {
|
|
26131
|
-
import.meta.css = [css$
|
|
26530
|
+
import.meta.css = [css$i, "@jsenv/navi/src/field/edition/editable.jsx"];
|
|
26132
26531
|
let {
|
|
26133
26532
|
children,
|
|
26134
26533
|
action,
|
|
@@ -26539,7 +26938,7 @@ const FormWithAction = props => {
|
|
|
26539
26938
|
// form.dispatchEvent(customEvent);
|
|
26540
26939
|
// };
|
|
26541
26940
|
|
|
26542
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
26941
|
+
installImportMetaCssBuild(import.meta);const css$h = /* css */`
|
|
26543
26942
|
.navi_group {
|
|
26544
26943
|
--border-width: 1px;
|
|
26545
26944
|
|
|
@@ -26636,7 +27035,7 @@ const Group = ({
|
|
|
26636
27035
|
vertical = row,
|
|
26637
27036
|
...props
|
|
26638
27037
|
}) => {
|
|
26639
|
-
import.meta.css = [css$
|
|
27038
|
+
import.meta.css = [css$h, "@jsenv/navi/src/field/group.jsx"];
|
|
26640
27039
|
if (typeof borderWidth === "string") {
|
|
26641
27040
|
borderWidth = parseFloat(borderWidth);
|
|
26642
27041
|
}
|
|
@@ -26654,6 +27053,386 @@ const Group = ({
|
|
|
26654
27053
|
});
|
|
26655
27054
|
};
|
|
26656
27055
|
|
|
27056
|
+
const createItemTracker = () => {
|
|
27057
|
+
const ItemTrackerContext = createContext();
|
|
27058
|
+
const useItemTrackerProvider = () => {
|
|
27059
|
+
const itemsRef = useRef([]);
|
|
27060
|
+
const items = itemsRef.current;
|
|
27061
|
+
const itemCountRef = useRef(0);
|
|
27062
|
+
const tracker = useMemo(() => {
|
|
27063
|
+
const ItemTrackerProvider = ({
|
|
27064
|
+
children
|
|
27065
|
+
}) => {
|
|
27066
|
+
// Reset on each render to start fresh
|
|
27067
|
+
tracker.reset();
|
|
27068
|
+
return jsx(ItemTrackerContext.Provider, {
|
|
27069
|
+
value: tracker,
|
|
27070
|
+
children: children
|
|
27071
|
+
});
|
|
27072
|
+
};
|
|
27073
|
+
ItemTrackerProvider.items = items;
|
|
27074
|
+
return {
|
|
27075
|
+
ItemTrackerProvider,
|
|
27076
|
+
items,
|
|
27077
|
+
registerItem: data => {
|
|
27078
|
+
const index = itemCountRef.current++;
|
|
27079
|
+
items[index] = data;
|
|
27080
|
+
return index;
|
|
27081
|
+
},
|
|
27082
|
+
getItem: index => {
|
|
27083
|
+
return items[index];
|
|
27084
|
+
},
|
|
27085
|
+
getAllItems: () => {
|
|
27086
|
+
return items;
|
|
27087
|
+
},
|
|
27088
|
+
reset: () => {
|
|
27089
|
+
items.length = 0;
|
|
27090
|
+
itemCountRef.current = 0;
|
|
27091
|
+
}
|
|
27092
|
+
};
|
|
27093
|
+
}, []);
|
|
27094
|
+
return tracker.ItemTrackerProvider;
|
|
27095
|
+
};
|
|
27096
|
+
const useTrackItem = data => {
|
|
27097
|
+
const tracker = useContext(ItemTrackerContext);
|
|
27098
|
+
if (!tracker) {
|
|
27099
|
+
throw new Error("useTrackItem must be used within SimpleItemTrackerProvider");
|
|
27100
|
+
}
|
|
27101
|
+
return tracker.registerItem(data);
|
|
27102
|
+
};
|
|
27103
|
+
const useTrackedItem = index => {
|
|
27104
|
+
const trackedItems = useTrackedItems();
|
|
27105
|
+
const item = trackedItems[index];
|
|
27106
|
+
return item;
|
|
27107
|
+
};
|
|
27108
|
+
const useTrackedItems = () => {
|
|
27109
|
+
const tracker = useContext(ItemTrackerContext);
|
|
27110
|
+
if (!tracker) {
|
|
27111
|
+
throw new Error("useTrackedItems must be used within SimpleItemTrackerProvider");
|
|
27112
|
+
}
|
|
27113
|
+
return tracker.items;
|
|
27114
|
+
};
|
|
27115
|
+
return [useItemTrackerProvider, useTrackItem, useTrackedItem, useTrackedItems];
|
|
27116
|
+
};
|
|
27117
|
+
|
|
27118
|
+
installImportMetaCssBuild(import.meta);const [useOptionItemTrackerProvider, useTrackOption] = createItemTracker();
|
|
27119
|
+
|
|
27120
|
+
/**
|
|
27121
|
+
* OptionList + Option: a composable accessible listbox.
|
|
27122
|
+
*
|
|
27123
|
+
* Usage:
|
|
27124
|
+
* <OptionList id="my-list" value={selected} onChange={setSelected}>
|
|
27125
|
+
* <Option value="a">Option A</Option>
|
|
27126
|
+
* <Option value="b">Option B</Option>
|
|
27127
|
+
* </OptionList>
|
|
27128
|
+
*
|
|
27129
|
+
* CSS vars on .navi_option_list:
|
|
27130
|
+
* --border-radius, --border-width, --border-color, --background-color, --max-height
|
|
27131
|
+
*
|
|
27132
|
+
* CSS vars on .navi_option:
|
|
27133
|
+
* --padding, --color, --background-color, --font-weight
|
|
27134
|
+
* --color-hover, --background-color-hover
|
|
27135
|
+
* --color-highlighted, --background-color-highlighted
|
|
27136
|
+
* --color-selected, --background-color-selected, --font-weight-selected
|
|
27137
|
+
* --color-highlighted-selected, --background-color-highlighted-selected
|
|
27138
|
+
*/
|
|
27139
|
+
|
|
27140
|
+
const css$g = /* css */`
|
|
27141
|
+
@layer navi {
|
|
27142
|
+
.navi_option_list {
|
|
27143
|
+
--border-radius: 4px;
|
|
27144
|
+
--border-width: 1px;
|
|
27145
|
+
--border-color: light-dark(#ccc, #555);
|
|
27146
|
+
--background-color: light-dark(#fff, #1e1e1e);
|
|
27147
|
+
--max-height: 220px;
|
|
27148
|
+
}
|
|
27149
|
+
.navi_option {
|
|
27150
|
+
--padding: 8px 12px;
|
|
27151
|
+
--color: inherit;
|
|
27152
|
+
--background-color: transparent;
|
|
27153
|
+
--font-weight: inherit;
|
|
27154
|
+
|
|
27155
|
+
/* Hover (mouse) */
|
|
27156
|
+
--color-hover: var(--color);
|
|
27157
|
+
--background-color-hover: light-dark(#f5f5f5, #2a2a2a);
|
|
27158
|
+
|
|
27159
|
+
/* Highlighted (keyboard navigation cursor) */
|
|
27160
|
+
--color-highlighted: var(--color);
|
|
27161
|
+
--background-color-highlighted: light-dark(#e8f0fe, #1c3a6e);
|
|
27162
|
+
|
|
27163
|
+
/* Selected */
|
|
27164
|
+
--color-selected: light-dark(#1a73e8, #7baaf7);
|
|
27165
|
+
--background-color-selected: light-dark(#e8f0fe, #1c3a6e);
|
|
27166
|
+
--font-weight-selected: 500;
|
|
27167
|
+
|
|
27168
|
+
/* Highlighted + selected */
|
|
27169
|
+
--color-highlighted-selected: var(--color-selected);
|
|
27170
|
+
--background-color-highlighted-selected: light-dark(#d2e3fc, #174ea6);
|
|
27171
|
+
}
|
|
27172
|
+
}
|
|
27173
|
+
|
|
27174
|
+
.navi_option_list {
|
|
27175
|
+
--x-border-radius: var(--border-radius);
|
|
27176
|
+
--x-border-width: var(--border-width);
|
|
27177
|
+
--x-border-color: var(--border-color);
|
|
27178
|
+
--x-background-color: var(--background-color);
|
|
27179
|
+
box-sizing: border-box;
|
|
27180
|
+
max-height: var(--max-height);
|
|
27181
|
+
|
|
27182
|
+
margin: 0;
|
|
27183
|
+
padding: 0;
|
|
27184
|
+
list-style: none;
|
|
27185
|
+
background-color: var(--x-background-color);
|
|
27186
|
+
border: var(--x-border-width) solid var(--x-border-color);
|
|
27187
|
+
border-radius: var(--x-border-radius);
|
|
27188
|
+
outline: none;
|
|
27189
|
+
overflow-y: auto;
|
|
27190
|
+
|
|
27191
|
+
/* Popover reset — browser adds border, background, padding, margin by default */
|
|
27192
|
+
&[popover] {
|
|
27193
|
+
position: fixed;
|
|
27194
|
+
inset: unset;
|
|
27195
|
+
margin: 0;
|
|
27196
|
+
padding: 0;
|
|
27197
|
+
border: none;
|
|
27198
|
+
}
|
|
27199
|
+
}
|
|
27200
|
+
.navi_option {
|
|
27201
|
+
--x-color: var(--color);
|
|
27202
|
+
--x-background-color: var(--background-color);
|
|
27203
|
+
--x-font-weight: var(--font-weight);
|
|
27204
|
+
|
|
27205
|
+
padding: var(--padding);
|
|
27206
|
+
color: var(--x-color);
|
|
27207
|
+
font-weight: var(--x-font-weight);
|
|
27208
|
+
background-color: var(--x-background-color);
|
|
27209
|
+
cursor: pointer;
|
|
27210
|
+
user-select: none;
|
|
27211
|
+
|
|
27212
|
+
&:hover {
|
|
27213
|
+
--x-color: var(--color-hover);
|
|
27214
|
+
--x-background-color: var(--background-color-hover);
|
|
27215
|
+
}
|
|
27216
|
+
|
|
27217
|
+
&[data-highlighted] {
|
|
27218
|
+
--x-color: var(--color-highlighted);
|
|
27219
|
+
--x-background-color: var(--background-color-highlighted);
|
|
27220
|
+
}
|
|
27221
|
+
|
|
27222
|
+
&[data-selected] {
|
|
27223
|
+
--x-color: var(--color-selected);
|
|
27224
|
+
--x-background-color: var(--background-color-selected);
|
|
27225
|
+
--x-font-weight: var(--font-weight-selected);
|
|
27226
|
+
}
|
|
27227
|
+
|
|
27228
|
+
&[data-highlighted][data-selected] {
|
|
27229
|
+
--x-color: var(--color-highlighted-selected);
|
|
27230
|
+
--x-background-color: var(--background-color-highlighted-selected);
|
|
27231
|
+
}
|
|
27232
|
+
}
|
|
27233
|
+
`;
|
|
27234
|
+
|
|
27235
|
+
/**
|
|
27236
|
+
* Context OptionList provides downward to its Option children.
|
|
27237
|
+
*/
|
|
27238
|
+
const OptionListContext = createContext(null);
|
|
27239
|
+
const OptionList = ({
|
|
27240
|
+
popover,
|
|
27241
|
+
onChange: onChangeProp,
|
|
27242
|
+
children,
|
|
27243
|
+
...rest
|
|
27244
|
+
}) => {
|
|
27245
|
+
import.meta.css = [css$g, "@jsenv/navi/src/field/option_list.jsx"];
|
|
27246
|
+
const ItemTrackerProvider = useOptionItemTrackerProvider();
|
|
27247
|
+
const [highlightedValue, setHighlightedValue] = useState(null);
|
|
27248
|
+
const highlightedValueRef = useRef(null);
|
|
27249
|
+
highlightedValueRef.current = highlightedValue;
|
|
27250
|
+
const ownId = useId();
|
|
27251
|
+
const id = rest.id ?? ownId;
|
|
27252
|
+
const listRef = useRef(null);
|
|
27253
|
+
const effectiveOnChange = popover ? value => {
|
|
27254
|
+
onChangeProp?.(value);
|
|
27255
|
+
listRef.current?.dispatchEvent(new CustomEvent("combobox-selected", {
|
|
27256
|
+
detail: {
|
|
27257
|
+
value
|
|
27258
|
+
},
|
|
27259
|
+
bubbles: true
|
|
27260
|
+
}));
|
|
27261
|
+
} : onChangeProp;
|
|
27262
|
+
const onChangeRef = useRef(effectiveOnChange);
|
|
27263
|
+
onChangeRef.current = effectiveOnChange;
|
|
27264
|
+
const navigate = direction => {
|
|
27265
|
+
const values = ItemTrackerProvider.items.filter(item => !item.hidden).map(item => item.value);
|
|
27266
|
+
if (values.length === 0) {
|
|
27267
|
+
return false;
|
|
27268
|
+
}
|
|
27269
|
+
const current = highlightedValueRef.current;
|
|
27270
|
+
if (direction === "down") {
|
|
27271
|
+
const idx = current === null ? -1 : values.indexOf(current);
|
|
27272
|
+
setHighlightedValue(values[idx < values.length - 1 ? idx + 1 : idx]);
|
|
27273
|
+
} else if (direction === "up") {
|
|
27274
|
+
const idx = current === null ? -1 : values.indexOf(current);
|
|
27275
|
+
setHighlightedValue(values[idx > 0 ? idx - 1 : 0]);
|
|
27276
|
+
} else if (direction === "first") {
|
|
27277
|
+
setHighlightedValue(values[0]);
|
|
27278
|
+
} else if (direction === "last") {
|
|
27279
|
+
setHighlightedValue(values[values.length - 1]);
|
|
27280
|
+
}
|
|
27281
|
+
return true;
|
|
27282
|
+
};
|
|
27283
|
+
|
|
27284
|
+
// Listen for commands dispatched by a linked Input (combobox mode)
|
|
27285
|
+
const noopRef = useRef(null);
|
|
27286
|
+
useEffect(() => {
|
|
27287
|
+
if (!popover || !listRef.current) {
|
|
27288
|
+
return undefined;
|
|
27289
|
+
}
|
|
27290
|
+
const el = listRef.current;
|
|
27291
|
+
const onNavigate = e => {
|
|
27292
|
+
navigate(e.detail.direction);
|
|
27293
|
+
};
|
|
27294
|
+
const onConfirm = e => {
|
|
27295
|
+
const current = highlightedValueRef.current;
|
|
27296
|
+
if (current !== null) {
|
|
27297
|
+
onChangeRef.current?.(current);
|
|
27298
|
+
e.preventDefault();
|
|
27299
|
+
}
|
|
27300
|
+
};
|
|
27301
|
+
const onClear = () => {
|
|
27302
|
+
setHighlightedValue(null);
|
|
27303
|
+
};
|
|
27304
|
+
el.addEventListener("combobox-navigate", onNavigate);
|
|
27305
|
+
el.addEventListener("combobox-confirm", onConfirm);
|
|
27306
|
+
el.addEventListener("combobox-clear", onClear);
|
|
27307
|
+
return () => {
|
|
27308
|
+
el.removeEventListener("combobox-navigate", onNavigate);
|
|
27309
|
+
el.removeEventListener("combobox-confirm", onConfirm);
|
|
27310
|
+
el.removeEventListener("combobox-clear", onClear);
|
|
27311
|
+
};
|
|
27312
|
+
}, [popover]);
|
|
27313
|
+
useKeyboardShortcuts(popover ? noopRef : listRef, [{
|
|
27314
|
+
key: "arrowdown",
|
|
27315
|
+
description: "Highlight next option",
|
|
27316
|
+
handler: () => navigate("down")
|
|
27317
|
+
}, {
|
|
27318
|
+
key: "arrowup",
|
|
27319
|
+
description: "Highlight previous option",
|
|
27320
|
+
handler: () => navigate("up")
|
|
27321
|
+
}, {
|
|
27322
|
+
key: "home",
|
|
27323
|
+
description: "Highlight first option",
|
|
27324
|
+
handler: () => navigate("first")
|
|
27325
|
+
}, {
|
|
27326
|
+
key: "end",
|
|
27327
|
+
description: "Highlight last option",
|
|
27328
|
+
handler: () => navigate("last")
|
|
27329
|
+
}, {
|
|
27330
|
+
key: "enter",
|
|
27331
|
+
description: "Select highlighted option",
|
|
27332
|
+
handler: () => {
|
|
27333
|
+
const current = highlightedValueRef.current;
|
|
27334
|
+
if (current === null) {
|
|
27335
|
+
return false;
|
|
27336
|
+
}
|
|
27337
|
+
onChangeRef.current?.(current);
|
|
27338
|
+
return true;
|
|
27339
|
+
}
|
|
27340
|
+
}, {
|
|
27341
|
+
key: "escape",
|
|
27342
|
+
description: "Clear highlighted option",
|
|
27343
|
+
handler: () => {
|
|
27344
|
+
setHighlightedValue(null);
|
|
27345
|
+
return true;
|
|
27346
|
+
}
|
|
27347
|
+
}]);
|
|
27348
|
+
const optionListContext = {
|
|
27349
|
+
highlightedValue,
|
|
27350
|
+
setHighlightedValue,
|
|
27351
|
+
onSelect: effectiveOnChange
|
|
27352
|
+
};
|
|
27353
|
+
return jsx(Box, {
|
|
27354
|
+
as: "ul",
|
|
27355
|
+
ref: listRef,
|
|
27356
|
+
id: id,
|
|
27357
|
+
role: "listbox",
|
|
27358
|
+
tabIndex: popover ? -1 : 0,
|
|
27359
|
+
popover: popover ? "manual" : undefined,
|
|
27360
|
+
...rest,
|
|
27361
|
+
baseClassName: "navi_option_list",
|
|
27362
|
+
children: jsx(OptionListContext.Provider, {
|
|
27363
|
+
value: optionListContext,
|
|
27364
|
+
children: jsx(ItemTrackerProvider, {
|
|
27365
|
+
children: children
|
|
27366
|
+
})
|
|
27367
|
+
})
|
|
27368
|
+
});
|
|
27369
|
+
};
|
|
27370
|
+
const OPTION_PSEUDO_CLASSES = [":-navi-highlighted", ":-navi-selected"];
|
|
27371
|
+
const Option = ({
|
|
27372
|
+
value,
|
|
27373
|
+
selected,
|
|
27374
|
+
hidden,
|
|
27375
|
+
children,
|
|
27376
|
+
...rest
|
|
27377
|
+
}) => {
|
|
27378
|
+
import.meta.css = [css$g, "@jsenv/navi/src/field/option_list.jsx"];
|
|
27379
|
+
const optionId = useId();
|
|
27380
|
+
const id = rest.id || optionId;
|
|
27381
|
+
useTrackOption({
|
|
27382
|
+
value,
|
|
27383
|
+
optionId: id,
|
|
27384
|
+
hidden
|
|
27385
|
+
});
|
|
27386
|
+
const {
|
|
27387
|
+
highlightedValue,
|
|
27388
|
+
setHighlightedValue,
|
|
27389
|
+
onSelect
|
|
27390
|
+
} = useContext(OptionListContext);
|
|
27391
|
+
const isHighlighted = highlightedValue === value;
|
|
27392
|
+
const optionRef = useRef(null);
|
|
27393
|
+
useEffect(() => {
|
|
27394
|
+
const optionEl = optionRef.current;
|
|
27395
|
+
if (isHighlighted && optionEl) {
|
|
27396
|
+
optionEl.scrollIntoView({
|
|
27397
|
+
block: "nearest"
|
|
27398
|
+
});
|
|
27399
|
+
}
|
|
27400
|
+
}, [isHighlighted]);
|
|
27401
|
+
return jsx(Box, {
|
|
27402
|
+
as: "li",
|
|
27403
|
+
ref: optionRef,
|
|
27404
|
+
baseClassName: "navi_option",
|
|
27405
|
+
id: optionId,
|
|
27406
|
+
role: "option",
|
|
27407
|
+
"aria-selected": selected,
|
|
27408
|
+
"aria-hidden": hidden ? true : undefined,
|
|
27409
|
+
hidden: hidden,
|
|
27410
|
+
basePseudoState: {
|
|
27411
|
+
":-navi-highlighted": isHighlighted,
|
|
27412
|
+
":-navi-selected": selected
|
|
27413
|
+
},
|
|
27414
|
+
pseudoClasses: OPTION_PSEUDO_CLASSES,
|
|
27415
|
+
onMouseEnter: () => {
|
|
27416
|
+
if (!hidden) {
|
|
27417
|
+
setHighlightedValue(value);
|
|
27418
|
+
}
|
|
27419
|
+
},
|
|
27420
|
+
onMouseLeave: () => {
|
|
27421
|
+
if (!hidden) {
|
|
27422
|
+
setHighlightedValue(null);
|
|
27423
|
+
}
|
|
27424
|
+
},
|
|
27425
|
+
onMouseDown: e => {
|
|
27426
|
+
if (hidden || e.button !== 0) {
|
|
27427
|
+
return;
|
|
27428
|
+
}
|
|
27429
|
+
onSelect?.(value);
|
|
27430
|
+
},
|
|
27431
|
+
...rest,
|
|
27432
|
+
children: children
|
|
27433
|
+
});
|
|
27434
|
+
};
|
|
27435
|
+
|
|
26657
27436
|
const RadioList = props => {
|
|
26658
27437
|
const uiStateController = useUIGroupStateController(props, "radio_list", {
|
|
26659
27438
|
childComponentType: "radio",
|
|
@@ -27124,6 +27903,131 @@ const filterTableSelection = (selection, predicate) => {
|
|
|
27124
27903
|
return matching;
|
|
27125
27904
|
};
|
|
27126
27905
|
|
|
27906
|
+
// https://github.com/reach/reach-ui/tree/b3d94d22811db6b5c0f272b9a7e2e3c1bb4699ae/packages/descendants
|
|
27907
|
+
// https://github.com/pacocoursey/use-descendants/tree/master
|
|
27908
|
+
|
|
27909
|
+
const createIsolatedItemTracker = () => {
|
|
27910
|
+
// Producer contexts (ref-based, no re-renders)
|
|
27911
|
+
const ProducerTrackerContext = createContext();
|
|
27912
|
+
const ProducerItemCountRefContext = createContext();
|
|
27913
|
+
const ProducerListRenderIdContext = createContext();
|
|
27914
|
+
|
|
27915
|
+
// Consumer contexts (state-based, re-renders)
|
|
27916
|
+
const ConsumerItemsContext = createContext();
|
|
27917
|
+
const useIsolatedItemTrackerProvider = () => {
|
|
27918
|
+
const itemsRef = useRef([]);
|
|
27919
|
+
const items = itemsRef.current;
|
|
27920
|
+
const itemCountRef = useRef();
|
|
27921
|
+
const itemTracker = useMemo(() => {
|
|
27922
|
+
// Snapshot taken by FlushSentinel after all producer children rendered.
|
|
27923
|
+
// Consumers read from this — always up-to-date within the same render pass.
|
|
27924
|
+
const itemsSnapshotRef = {
|
|
27925
|
+
current: items
|
|
27926
|
+
};
|
|
27927
|
+
const registerItem = (index, value) => {
|
|
27928
|
+
const hasValue = index in items;
|
|
27929
|
+
if (hasValue) {
|
|
27930
|
+
const currentValue = items[index];
|
|
27931
|
+
if (compareTwoJsValues(currentValue, value)) {
|
|
27932
|
+
return;
|
|
27933
|
+
}
|
|
27934
|
+
}
|
|
27935
|
+
items[index] = value;
|
|
27936
|
+
};
|
|
27937
|
+
const getProducerItem = itemIndex => {
|
|
27938
|
+
return items[itemIndex];
|
|
27939
|
+
};
|
|
27940
|
+
const ItemProducerProvider = ({
|
|
27941
|
+
children
|
|
27942
|
+
}) => {
|
|
27943
|
+
items.length = 0;
|
|
27944
|
+
itemCountRef.current = 0;
|
|
27945
|
+
const listRenderId = {};
|
|
27946
|
+
return jsx(ProducerItemCountRefContext.Provider, {
|
|
27947
|
+
value: itemCountRef,
|
|
27948
|
+
children: jsx(ProducerListRenderIdContext.Provider, {
|
|
27949
|
+
value: listRenderId,
|
|
27950
|
+
children: jsxs(ProducerTrackerContext.Provider, {
|
|
27951
|
+
value: itemTracker,
|
|
27952
|
+
children: [children, jsx(FlushSentinel, {})]
|
|
27953
|
+
})
|
|
27954
|
+
})
|
|
27955
|
+
});
|
|
27956
|
+
};
|
|
27957
|
+
|
|
27958
|
+
// Renders after all producer children (e.g. <Col>) have registered their
|
|
27959
|
+
// items. Taking a snapshot here guarantees the consumer sees the correct
|
|
27960
|
+
// item list within the same render pass, without any heuristic.
|
|
27961
|
+
const FlushSentinel = () => {
|
|
27962
|
+
itemsSnapshotRef.current = items;
|
|
27963
|
+
return null;
|
|
27964
|
+
};
|
|
27965
|
+
const ItemConsumerProvider = ({
|
|
27966
|
+
children
|
|
27967
|
+
}) => {
|
|
27968
|
+
// FlushSentinel (last child of ItemProducerProvider) already set
|
|
27969
|
+
// itemsSnapshotRef.current to the up-to-date items array before any
|
|
27970
|
+
// consumer rendered. Reading from the snapshot is always correct.
|
|
27971
|
+
return jsx(ConsumerItemsContext.Provider, {
|
|
27972
|
+
value: itemsSnapshotRef.current,
|
|
27973
|
+
children: children
|
|
27974
|
+
});
|
|
27975
|
+
};
|
|
27976
|
+
return {
|
|
27977
|
+
registerItem,
|
|
27978
|
+
getProducerItem,
|
|
27979
|
+
ItemProducerProvider,
|
|
27980
|
+
ItemConsumerProvider
|
|
27981
|
+
};
|
|
27982
|
+
}, []);
|
|
27983
|
+
const {
|
|
27984
|
+
ItemProducerProvider,
|
|
27985
|
+
ItemConsumerProvider
|
|
27986
|
+
} = itemTracker;
|
|
27987
|
+
return [ItemProducerProvider, ItemConsumerProvider, items];
|
|
27988
|
+
};
|
|
27989
|
+
|
|
27990
|
+
// Hook for producers to register items (ref-based, no re-renders)
|
|
27991
|
+
const useTrackIsolatedItem = data => {
|
|
27992
|
+
const listRenderId = useContext(ProducerListRenderIdContext);
|
|
27993
|
+
const itemCountRef = useContext(ProducerItemCountRefContext);
|
|
27994
|
+
const itemTracker = useContext(ProducerTrackerContext);
|
|
27995
|
+
const listRenderIdRef = useRef();
|
|
27996
|
+
const itemIndexRef = useRef();
|
|
27997
|
+
const dataRef = useRef();
|
|
27998
|
+
const prevListRenderId = listRenderIdRef.current;
|
|
27999
|
+
if (prevListRenderId === listRenderId) {
|
|
28000
|
+
const itemIndex = itemIndexRef.current;
|
|
28001
|
+
itemTracker.registerItem(itemIndex, data);
|
|
28002
|
+
dataRef.current = data;
|
|
28003
|
+
return itemIndex;
|
|
28004
|
+
}
|
|
28005
|
+
listRenderIdRef.current = listRenderId;
|
|
28006
|
+
const itemCount = itemCountRef.current;
|
|
28007
|
+
const itemIndex = itemCount;
|
|
28008
|
+
itemCountRef.current = itemIndex + 1;
|
|
28009
|
+
itemIndexRef.current = itemIndex;
|
|
28010
|
+
dataRef.current = data;
|
|
28011
|
+
itemTracker.registerItem(itemIndex, data);
|
|
28012
|
+
return itemIndex;
|
|
28013
|
+
};
|
|
28014
|
+
const useTrackedIsolatedItem = itemIndex => {
|
|
28015
|
+
const items = useTrackedIsolatedItems();
|
|
28016
|
+
const item = items[itemIndex];
|
|
28017
|
+
return item;
|
|
28018
|
+
};
|
|
28019
|
+
|
|
28020
|
+
// Hooks for consumers to read items (state-based, re-renders)
|
|
28021
|
+
const useTrackedIsolatedItems = () => {
|
|
28022
|
+
const consumerItems = useContext(ConsumerItemsContext);
|
|
28023
|
+
if (!consumerItems) {
|
|
28024
|
+
throw new Error("useTrackedIsolatedItems must be used within <ItemConsumerProvider />");
|
|
28025
|
+
}
|
|
28026
|
+
return consumerItems;
|
|
28027
|
+
};
|
|
28028
|
+
return [useIsolatedItemTrackerProvider, useTrackIsolatedItem, useTrackedIsolatedItem, useTrackedIsolatedItems];
|
|
28029
|
+
};
|
|
28030
|
+
|
|
27127
28031
|
const Z_INDEX_EDITING = 1; /* To go above neighbours, but should not be too big to stay under the sticky cells */
|
|
27128
28032
|
|
|
27129
28033
|
/* needed because cell uses position:relative, sticky must win even if before in DOM order */
|
|
@@ -27580,193 +28484,6 @@ const createTableAttributeSync = (table, tableClone) => {
|
|
|
27580
28484
|
return observer;
|
|
27581
28485
|
};
|
|
27582
28486
|
|
|
27583
|
-
// https://github.com/reach/reach-ui/tree/b3d94d22811db6b5c0f272b9a7e2e3c1bb4699ae/packages/descendants
|
|
27584
|
-
// https://github.com/pacocoursey/use-descendants/tree/master
|
|
27585
|
-
|
|
27586
|
-
const createIsolatedItemTracker = () => {
|
|
27587
|
-
// Producer contexts (ref-based, no re-renders)
|
|
27588
|
-
const ProducerTrackerContext = createContext();
|
|
27589
|
-
const ProducerItemCountRefContext = createContext();
|
|
27590
|
-
const ProducerListRenderIdContext = createContext();
|
|
27591
|
-
|
|
27592
|
-
// Consumer contexts (state-based, re-renders)
|
|
27593
|
-
const ConsumerItemsContext = createContext();
|
|
27594
|
-
const useIsolatedItemTrackerProvider = () => {
|
|
27595
|
-
const itemsRef = useRef([]);
|
|
27596
|
-
const items = itemsRef.current;
|
|
27597
|
-
const itemCountRef = useRef();
|
|
27598
|
-
const itemTracker = useMemo(() => {
|
|
27599
|
-
// Snapshot taken by FlushSentinel after all producer children rendered.
|
|
27600
|
-
// Consumers read from this — always up-to-date within the same render pass.
|
|
27601
|
-
const itemsSnapshotRef = {
|
|
27602
|
-
current: items
|
|
27603
|
-
};
|
|
27604
|
-
const registerItem = (index, value) => {
|
|
27605
|
-
const hasValue = index in items;
|
|
27606
|
-
if (hasValue) {
|
|
27607
|
-
const currentValue = items[index];
|
|
27608
|
-
if (compareTwoJsValues(currentValue, value)) {
|
|
27609
|
-
return;
|
|
27610
|
-
}
|
|
27611
|
-
}
|
|
27612
|
-
items[index] = value;
|
|
27613
|
-
};
|
|
27614
|
-
const getProducerItem = itemIndex => {
|
|
27615
|
-
return items[itemIndex];
|
|
27616
|
-
};
|
|
27617
|
-
const ItemProducerProvider = ({
|
|
27618
|
-
children
|
|
27619
|
-
}) => {
|
|
27620
|
-
items.length = 0;
|
|
27621
|
-
itemCountRef.current = 0;
|
|
27622
|
-
const listRenderId = {};
|
|
27623
|
-
return jsx(ProducerItemCountRefContext.Provider, {
|
|
27624
|
-
value: itemCountRef,
|
|
27625
|
-
children: jsx(ProducerListRenderIdContext.Provider, {
|
|
27626
|
-
value: listRenderId,
|
|
27627
|
-
children: jsxs(ProducerTrackerContext.Provider, {
|
|
27628
|
-
value: itemTracker,
|
|
27629
|
-
children: [children, jsx(FlushSentinel, {})]
|
|
27630
|
-
})
|
|
27631
|
-
})
|
|
27632
|
-
});
|
|
27633
|
-
};
|
|
27634
|
-
|
|
27635
|
-
// Renders after all producer children (e.g. <Col>) have registered their
|
|
27636
|
-
// items. Taking a snapshot here guarantees the consumer sees the correct
|
|
27637
|
-
// item list within the same render pass, without any heuristic.
|
|
27638
|
-
const FlushSentinel = () => {
|
|
27639
|
-
itemsSnapshotRef.current = items;
|
|
27640
|
-
return null;
|
|
27641
|
-
};
|
|
27642
|
-
const ItemConsumerProvider = ({
|
|
27643
|
-
children
|
|
27644
|
-
}) => {
|
|
27645
|
-
// FlushSentinel (last child of ItemProducerProvider) already set
|
|
27646
|
-
// itemsSnapshotRef.current to the up-to-date items array before any
|
|
27647
|
-
// consumer rendered. Reading from the snapshot is always correct.
|
|
27648
|
-
return jsx(ConsumerItemsContext.Provider, {
|
|
27649
|
-
value: itemsSnapshotRef.current,
|
|
27650
|
-
children: children
|
|
27651
|
-
});
|
|
27652
|
-
};
|
|
27653
|
-
return {
|
|
27654
|
-
registerItem,
|
|
27655
|
-
getProducerItem,
|
|
27656
|
-
ItemProducerProvider,
|
|
27657
|
-
ItemConsumerProvider
|
|
27658
|
-
};
|
|
27659
|
-
}, []);
|
|
27660
|
-
const {
|
|
27661
|
-
ItemProducerProvider,
|
|
27662
|
-
ItemConsumerProvider
|
|
27663
|
-
} = itemTracker;
|
|
27664
|
-
return [ItemProducerProvider, ItemConsumerProvider, items];
|
|
27665
|
-
};
|
|
27666
|
-
|
|
27667
|
-
// Hook for producers to register items (ref-based, no re-renders)
|
|
27668
|
-
const useTrackIsolatedItem = data => {
|
|
27669
|
-
const listRenderId = useContext(ProducerListRenderIdContext);
|
|
27670
|
-
const itemCountRef = useContext(ProducerItemCountRefContext);
|
|
27671
|
-
const itemTracker = useContext(ProducerTrackerContext);
|
|
27672
|
-
const listRenderIdRef = useRef();
|
|
27673
|
-
const itemIndexRef = useRef();
|
|
27674
|
-
const dataRef = useRef();
|
|
27675
|
-
const prevListRenderId = listRenderIdRef.current;
|
|
27676
|
-
if (prevListRenderId === listRenderId) {
|
|
27677
|
-
const itemIndex = itemIndexRef.current;
|
|
27678
|
-
itemTracker.registerItem(itemIndex, data);
|
|
27679
|
-
dataRef.current = data;
|
|
27680
|
-
return itemIndex;
|
|
27681
|
-
}
|
|
27682
|
-
listRenderIdRef.current = listRenderId;
|
|
27683
|
-
const itemCount = itemCountRef.current;
|
|
27684
|
-
const itemIndex = itemCount;
|
|
27685
|
-
itemCountRef.current = itemIndex + 1;
|
|
27686
|
-
itemIndexRef.current = itemIndex;
|
|
27687
|
-
dataRef.current = data;
|
|
27688
|
-
itemTracker.registerItem(itemIndex, data);
|
|
27689
|
-
return itemIndex;
|
|
27690
|
-
};
|
|
27691
|
-
const useTrackedIsolatedItem = itemIndex => {
|
|
27692
|
-
const items = useTrackedIsolatedItems();
|
|
27693
|
-
const item = items[itemIndex];
|
|
27694
|
-
return item;
|
|
27695
|
-
};
|
|
27696
|
-
|
|
27697
|
-
// Hooks for consumers to read items (state-based, re-renders)
|
|
27698
|
-
const useTrackedIsolatedItems = () => {
|
|
27699
|
-
const consumerItems = useContext(ConsumerItemsContext);
|
|
27700
|
-
if (!consumerItems) {
|
|
27701
|
-
throw new Error("useTrackedIsolatedItems must be used within <ItemConsumerProvider />");
|
|
27702
|
-
}
|
|
27703
|
-
return consumerItems;
|
|
27704
|
-
};
|
|
27705
|
-
return [useIsolatedItemTrackerProvider, useTrackIsolatedItem, useTrackedIsolatedItem, useTrackedIsolatedItems];
|
|
27706
|
-
};
|
|
27707
|
-
|
|
27708
|
-
const createItemTracker = () => {
|
|
27709
|
-
const ItemTrackerContext = createContext();
|
|
27710
|
-
const useItemTrackerProvider = () => {
|
|
27711
|
-
const itemsRef = useRef([]);
|
|
27712
|
-
const items = itemsRef.current;
|
|
27713
|
-
const itemCountRef = useRef(0);
|
|
27714
|
-
const tracker = useMemo(() => {
|
|
27715
|
-
const ItemTrackerProvider = ({
|
|
27716
|
-
children
|
|
27717
|
-
}) => {
|
|
27718
|
-
// Reset on each render to start fresh
|
|
27719
|
-
tracker.reset();
|
|
27720
|
-
return jsx(ItemTrackerContext.Provider, {
|
|
27721
|
-
value: tracker,
|
|
27722
|
-
children: children
|
|
27723
|
-
});
|
|
27724
|
-
};
|
|
27725
|
-
ItemTrackerProvider.items = items;
|
|
27726
|
-
return {
|
|
27727
|
-
ItemTrackerProvider,
|
|
27728
|
-
items,
|
|
27729
|
-
registerItem: data => {
|
|
27730
|
-
const index = itemCountRef.current++;
|
|
27731
|
-
items[index] = data;
|
|
27732
|
-
return index;
|
|
27733
|
-
},
|
|
27734
|
-
getItem: index => {
|
|
27735
|
-
return items[index];
|
|
27736
|
-
},
|
|
27737
|
-
getAllItems: () => {
|
|
27738
|
-
return items;
|
|
27739
|
-
},
|
|
27740
|
-
reset: () => {
|
|
27741
|
-
items.length = 0;
|
|
27742
|
-
itemCountRef.current = 0;
|
|
27743
|
-
}
|
|
27744
|
-
};
|
|
27745
|
-
}, []);
|
|
27746
|
-
return tracker.ItemTrackerProvider;
|
|
27747
|
-
};
|
|
27748
|
-
const useTrackItem = data => {
|
|
27749
|
-
const tracker = useContext(ItemTrackerContext);
|
|
27750
|
-
if (!tracker) {
|
|
27751
|
-
throw new Error("useTrackItem must be used within SimpleItemTrackerProvider");
|
|
27752
|
-
}
|
|
27753
|
-
return tracker.registerItem(data);
|
|
27754
|
-
};
|
|
27755
|
-
const useTrackedItem = index => {
|
|
27756
|
-
const trackedItems = useTrackedItems();
|
|
27757
|
-
const item = trackedItems[index];
|
|
27758
|
-
return item;
|
|
27759
|
-
};
|
|
27760
|
-
const useTrackedItems = () => {
|
|
27761
|
-
const tracker = useContext(ItemTrackerContext);
|
|
27762
|
-
if (!tracker) {
|
|
27763
|
-
throw new Error("useTrackedItems must be used within SimpleItemTrackerProvider");
|
|
27764
|
-
}
|
|
27765
|
-
return tracker.items;
|
|
27766
|
-
};
|
|
27767
|
-
return [useItemTrackerProvider, useTrackItem, useTrackedItem, useTrackedItems];
|
|
27768
|
-
};
|
|
27769
|
-
|
|
27770
28487
|
const TableSizeContext = createContext();
|
|
27771
28488
|
|
|
27772
28489
|
const useTableSizeContextValue = ({
|
|
@@ -32685,5 +33402,5 @@ const UserSvg = () => jsx("svg", {
|
|
|
32685
33402
|
})
|
|
32686
33403
|
});
|
|
32687
33404
|
|
|
32688
|
-
export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, Box, Button, ButtonCopyToClipboard, Caption, CheckSvg, Checkbox, CheckboxList, CloseSvg, Code, Col, Colgroup, ConstructionSvg, Details, DialogLayout, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, EyeClosedSvg, EyeSvg, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, Input, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, LinkCurrentSvg, Loading, MessageBox, Meter, Nav, Paragraph, Quantity, QuantityIntl, Radio, RadioList, Route, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, Select, SelectionContext, Separator, SettingsSvg, SidePanel, StarSvg, SummaryMarker, Svg, Table, TableCell, Tbody, Text, Thead, Title, Tr, UITransition, UserSvg, ViewportLayout, actionIntegratedVia, actionRunEffect, addCustomMessage, arraySignalMembership, compareTwoJsValues, createAction, createAvailableConstraint, createIntl, createRequestCanceller, createSelectionKeyboardShortcuts, enableDebugActions, enableDebugOnDocumentLoading, filterTableSelection, forwardActionRequested, installCustomConstraintValidation, isCellSelected, isColumnSelected, isRowSelected, localStorageSignal, navBack, navForward, navTo, openCallout, rawUrlPart, reload, removeCustomMessage, requestAction, rerunActions, resource, route, routeAction, setBaseUrl, setupRoutes, stateSignal, stopLoad, stringifyTableSelectionValue, syncOwnedResourceToSignals, syncResourceToSignals, updateActions, useActionStatus, useArraySignalMembership, useAsyncData, useCalloutClose, useCancelPrevious, useCellGridFromRows, useConstraintValidityState, useDarkBackgroundAttribute, useDependenciesDiff, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useKeyboardShortcuts, useNavState$1 as useNavState, useOrderedColumns, useRouteStatus, useRunOnMount, useSelectableElement, useSelectionController, useSidePanelClose, useSignalSync, useStateArray, useTitleLevel, useUrlSearchParam, valueInLocalStorage };
|
|
33405
|
+
export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, Box, Button, ButtonCopyToClipboard, Caption, CheckSvg, Checkbox, CheckboxList, CloseSvg, Code, Col, Colgroup, ConstructionSvg, Details, DialogLayout, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, EyeClosedSvg, EyeSvg, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, Input, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, LinkCurrentSvg, Loading, MessageBox, Meter, Nav, Option, OptionList, Paragraph, Quantity, QuantityIntl, Radio, RadioList, Route, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, Select, SelectionContext, Separator, SettingsSvg, SidePanel, StarSvg, SummaryMarker, Svg, Table, TableCell, Tbody, Text, Thead, Title, Tr, UITransition, UserSvg, ViewportLayout, actionIntegratedVia, actionRunEffect, addCustomMessage, arraySignalMembership, compareTwoJsValues, createAction, createAvailableConstraint, createIntl, createRequestCanceller, createSelectionKeyboardShortcuts, enableDebugActions, enableDebugOnDocumentLoading, filterTableSelection, forwardActionRequested, installCustomConstraintValidation, isCellSelected, isColumnSelected, isRowSelected, localStorageSignal, navBack, navForward, navTo, openCallout, rawUrlPart, reload, removeCustomMessage, requestAction, rerunActions, resource, route, routeAction, setBaseUrl, setupRoutes, stateSignal, stopLoad, stringifyTableSelectionValue, syncOwnedResourceToSignals, syncResourceToSignals, updateActions, useActionStatus, useArraySignalMembership, useAsyncData, useCalloutClose, useCancelPrevious, useCellGridFromRows, useConstraintValidityState, useDarkBackgroundAttribute, useDependenciesDiff, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useKeyboardShortcuts, useNavState$1 as useNavState, useOrderedColumns, useRouteStatus, useRunOnMount, useSelectableElement, useSelectionController, useSidePanelClose, useSignalSync, useStateArray, useTitleLevel, useUrlSearchParam, valueInLocalStorage };
|
|
32689
33406
|
//# sourceMappingURL=jsenv_navi.js.map
|