@jsenv/navi 0.18.16 → 0.18.18
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 +350 -113
- package/dist/jsenv_navi.js.map +38 -26
- package/dist/jsenv_navi_side_effects.js +1 -1
- package/dist/jsenv_navi_side_effects.js.map +1 -1
- package/package.json +2 -2
package/dist/jsenv_navi.js
CHANGED
|
@@ -3,7 +3,8 @@ import { isValidElement, createContext, toChildArray, render, createRef, cloneEl
|
|
|
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, hasCSSSizeUnit, resolveCSSSize, activeElementSignal, canInterceptKeys, initFocusGroup, elementIsFocusable,
|
|
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, hasCSSSizeUnit, resolveCSSSize, activeElementSignal, canInterceptKeys, initFocusGroup, elementIsFocusable, contrastColor, resolveColorLuminance, dragAfterThreshold, getScrollContainer, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement } from "@jsenv/dom";
|
|
7
|
+
export { contrastColor } from "@jsenv/dom";
|
|
7
8
|
import { prefixFirstAndIndentRemainingLines } from "@jsenv/humanize";
|
|
8
9
|
import { createValidity } from "@jsenv/validity";
|
|
9
10
|
import { createPortal, forwardRef } from "preact/compat";
|
|
@@ -6472,6 +6473,7 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
|
6472
6473
|
const PSEUDO_CLASSES_DEFAULT = [];
|
|
6473
6474
|
const PSEUDO_ELEMENTS_DEFAULT = [];
|
|
6474
6475
|
const STYLE_CSS_VARS_DEFAULT = {};
|
|
6476
|
+
const PROPS_CSS_VARS_DEFAULT = {};
|
|
6475
6477
|
const Box = props => {
|
|
6476
6478
|
const {
|
|
6477
6479
|
as = "div",
|
|
@@ -6481,6 +6483,7 @@ const Box = props => {
|
|
|
6481
6483
|
// style management
|
|
6482
6484
|
style,
|
|
6483
6485
|
styleCSSVars = STYLE_CSS_VARS_DEFAULT,
|
|
6486
|
+
propsCSSVars = PROPS_CSS_VARS_DEFAULT,
|
|
6484
6487
|
basePseudoState,
|
|
6485
6488
|
pseudoState,
|
|
6486
6489
|
// for demo purposes it's possible to control pseudo state from props
|
|
@@ -6614,16 +6617,20 @@ const Box = props => {
|
|
|
6614
6617
|
let boxPseudoNamedStyles = PSEUDO_NAMED_STYLES_DEFAULT;
|
|
6615
6618
|
const shouldForwardAllToChild = visualSelector && pseudoStateSelector;
|
|
6616
6619
|
const addStyle = (value, name, styleContext, stylesTarget, context) => {
|
|
6617
|
-
styleDeps.push(name, value); // impact box style -> add to deps
|
|
6618
|
-
const cssVar = styleContext.styleCSSVars[name];
|
|
6619
6620
|
const mergedValue = prepareStyleValue(stylesTarget[name], value, name, styleContext, context);
|
|
6621
|
+
const cssVar = styleContext.styleCSSVars[name];
|
|
6620
6622
|
if (cssVar) {
|
|
6621
|
-
|
|
6623
|
+
addCSSVar(mergedValue, cssVar, stylesTarget);
|
|
6622
6624
|
return true;
|
|
6623
6625
|
}
|
|
6626
|
+
styleDeps.push(name, value); // impact box style -> add to deps
|
|
6624
6627
|
stylesTarget[name] = mergedValue;
|
|
6625
6628
|
return false;
|
|
6626
6629
|
};
|
|
6630
|
+
const addCSSVar = (value, name, stylesTarget) => {
|
|
6631
|
+
styleDeps.push(name, value); // impact box style -> add to deps
|
|
6632
|
+
stylesTarget[name] = value;
|
|
6633
|
+
};
|
|
6627
6634
|
const addStyleMaybeForwarding = (value, name, styleContext, stylesTarget, context, visualChildPropStrategy) => {
|
|
6628
6635
|
if (!visualChildPropStrategy) {
|
|
6629
6636
|
addStyle(value, name, styleContext, stylesTarget, context);
|
|
@@ -6688,6 +6695,11 @@ const Box = props => {
|
|
|
6688
6695
|
addStyle(value, name, styleContext, boxStylesTarget, context);
|
|
6689
6696
|
return;
|
|
6690
6697
|
}
|
|
6698
|
+
const propCssVar = propsCSSVars[name];
|
|
6699
|
+
if (propCssVar) {
|
|
6700
|
+
addCSSVar(value, propCssVar, boxStylesTarget);
|
|
6701
|
+
return;
|
|
6702
|
+
}
|
|
6691
6703
|
const isPseudoStyle = styleOrigin === "pseudo_style";
|
|
6692
6704
|
if (isStyleProp(name)) {
|
|
6693
6705
|
// it's a style prop, we need first to check if we have css var to handle them
|
|
@@ -16350,9 +16362,9 @@ const listenInputValue = (
|
|
|
16350
16362
|
let timeout;
|
|
16351
16363
|
let debounceTimeout;
|
|
16352
16364
|
|
|
16353
|
-
const onAsyncEvent = (e) => {
|
|
16365
|
+
const onAsyncEvent = (e, options) => {
|
|
16354
16366
|
timeout = setTimeout(() => {
|
|
16355
|
-
onEvent(e);
|
|
16367
|
+
onEvent(e, options);
|
|
16356
16368
|
}, 0);
|
|
16357
16369
|
};
|
|
16358
16370
|
|
|
@@ -16431,13 +16443,16 @@ const listenInputValue = (
|
|
|
16431
16443
|
input.removeEventListener("focus", onEvent);
|
|
16432
16444
|
});
|
|
16433
16445
|
|
|
16434
|
-
|
|
16435
|
-
|
|
16436
|
-
|
|
16437
|
-
|
|
16438
|
-
|
|
16446
|
+
const onNaviDeleteContent = (e) => {
|
|
16447
|
+
// "navi_delete_content" behaves like an async event
|
|
16448
|
+
// a bit like form reset because
|
|
16449
|
+
// our action will be updated async after the component re-renders
|
|
16450
|
+
// and we need to wait that to happen to properly call action with the right value
|
|
16451
|
+
onAsyncEvent(e, { skipDebounce: true });
|
|
16452
|
+
};
|
|
16453
|
+
input.addEventListener("navi_delete_content", onNaviDeleteContent);
|
|
16439
16454
|
addTeardown(() => {
|
|
16440
|
-
input.removeEventListener("navi_delete_content",
|
|
16455
|
+
input.removeEventListener("navi_delete_content", onNaviDeleteContent);
|
|
16441
16456
|
});
|
|
16442
16457
|
return () => {
|
|
16443
16458
|
teardown();
|
|
@@ -22104,6 +22119,103 @@ const useConstraintValidityState = (ref) => {
|
|
|
22104
22119
|
return constraintValidityState;
|
|
22105
22120
|
};
|
|
22106
22121
|
|
|
22122
|
+
/**
|
|
22123
|
+
* Toggles a `data-dark-background` attribute on the referenced element based on its
|
|
22124
|
+
* computed background color. Pair it with a CSS variable to get automatic
|
|
22125
|
+
* light/dark text without hard-coding colors:
|
|
22126
|
+
*
|
|
22127
|
+
* ```css
|
|
22128
|
+
* .my-element {
|
|
22129
|
+
* --color-contrasting: black;
|
|
22130
|
+
* &[data-dark-background] {
|
|
22131
|
+
* --color-contrasting: white;
|
|
22132
|
+
* }
|
|
22133
|
+
* color: var(--color-contrasting);
|
|
22134
|
+
* }
|
|
22135
|
+
* ```
|
|
22136
|
+
*
|
|
22137
|
+
* - `data-dark-background` is **set** when the background is dark enough that white text
|
|
22138
|
+
* provides better (or equal) contrast.
|
|
22139
|
+
* - `data-dark-background` is **absent** when black text is the better choice.
|
|
22140
|
+
*
|
|
22141
|
+
* @param {import("preact").RefObject} ref - Ref to the element that receives
|
|
22142
|
+
* the `data-dark-background` attribute and is also passed to `contrastColor` for
|
|
22143
|
+
* resolving CSS variables.
|
|
22144
|
+
* @param {object} [options]
|
|
22145
|
+
* @param {string} [options.backgroundElementSelector] - CSS selector relative
|
|
22146
|
+
* to `ref.current` pointing to a child element whose `background-color`
|
|
22147
|
+
* should be tested instead of the element itself. Useful when the element
|
|
22148
|
+
* has a transparent background but contains a coloured child (e.g. a fill
|
|
22149
|
+
* bar inside a track).
|
|
22150
|
+
*/
|
|
22151
|
+
|
|
22152
|
+
const useDarkBackgroundAttribute = (
|
|
22153
|
+
ref,
|
|
22154
|
+
deps = [],
|
|
22155
|
+
{
|
|
22156
|
+
backgroundElementSelector,
|
|
22157
|
+
attributeName = "data-dark-background",
|
|
22158
|
+
hardcoded = {},
|
|
22159
|
+
} = {},
|
|
22160
|
+
) => {
|
|
22161
|
+
const innerDeps = [
|
|
22162
|
+
...deps,
|
|
22163
|
+
// ref can change is the component pass a different ref on different render based on some logic
|
|
22164
|
+
// (can be used to control which element backgroundColor is being checked by switching the ref to another element)
|
|
22165
|
+
ref,
|
|
22166
|
+
// backgroundElementSelector can change if the component pass a different selector on different render based on some logic
|
|
22167
|
+
// (can be used to control which element backgroundColor is being checked by switching the selector to point to another element)
|
|
22168
|
+
backgroundElementSelector,
|
|
22169
|
+
];
|
|
22170
|
+
|
|
22171
|
+
const hardcodedMap = new Map();
|
|
22172
|
+
for (const key of Object.keys(hardcoded)) {
|
|
22173
|
+
const value = hardcoded[key];
|
|
22174
|
+
innerDeps.push(key, value);
|
|
22175
|
+
const colorString = normalizeColorString(key);
|
|
22176
|
+
hardcodedMap.set(colorString, value);
|
|
22177
|
+
}
|
|
22178
|
+
|
|
22179
|
+
useLayoutEffect(() => {
|
|
22180
|
+
const el = ref.current;
|
|
22181
|
+
if (!el) {
|
|
22182
|
+
return null;
|
|
22183
|
+
}
|
|
22184
|
+
let elementToCheck = el;
|
|
22185
|
+
if (backgroundElementSelector) {
|
|
22186
|
+
elementToCheck = el.querySelector(backgroundElementSelector);
|
|
22187
|
+
if (!elementToCheck) {
|
|
22188
|
+
return null;
|
|
22189
|
+
}
|
|
22190
|
+
}
|
|
22191
|
+
const backgroundColor = getComputedStyle(elementToCheck).backgroundColor;
|
|
22192
|
+
if (!backgroundColor) {
|
|
22193
|
+
el.removeAttribute(attributeName);
|
|
22194
|
+
return null;
|
|
22195
|
+
}
|
|
22196
|
+
const backgroundColorString = normalizeColorString(backgroundColor, el);
|
|
22197
|
+
const hardcodedContrast = hardcodedMap.get(backgroundColorString);
|
|
22198
|
+
const contrastingColor =
|
|
22199
|
+
hardcodedContrast || contrastColor(backgroundColor, el);
|
|
22200
|
+
if (contrastingColor === "white") {
|
|
22201
|
+
el.setAttribute(attributeName, "");
|
|
22202
|
+
return () => {
|
|
22203
|
+
el.removeAttribute(attributeName);
|
|
22204
|
+
};
|
|
22205
|
+
}
|
|
22206
|
+
el.removeAttribute(attributeName);
|
|
22207
|
+
return null;
|
|
22208
|
+
}, innerDeps);
|
|
22209
|
+
};
|
|
22210
|
+
|
|
22211
|
+
const normalizeColorString = (color, el) => {
|
|
22212
|
+
const colorRgba = resolveCSSColor(color, el);
|
|
22213
|
+
if (!colorRgba) {
|
|
22214
|
+
return "";
|
|
22215
|
+
}
|
|
22216
|
+
return String(colorRgba);
|
|
22217
|
+
};
|
|
22218
|
+
|
|
22107
22219
|
installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
22108
22220
|
@layer navi {
|
|
22109
22221
|
label {
|
|
@@ -22111,7 +22223,7 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
|
22111
22223
|
cursor: pointer;
|
|
22112
22224
|
}
|
|
22113
22225
|
|
|
22114
|
-
&[data-
|
|
22226
|
+
&[data-readonly],
|
|
22115
22227
|
&[data-disabled] {
|
|
22116
22228
|
color: rgba(0, 0, 0, 0.5);
|
|
22117
22229
|
cursor: default;
|
|
@@ -22187,14 +22299,9 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
|
22187
22299
|
--accent-color: light-dark(#4476ff, #3b82f6);
|
|
22188
22300
|
--background-color-checked: var(--accent-color);
|
|
22189
22301
|
--border-color-checked: var(--accent-color);
|
|
22190
|
-
--checkmark-color
|
|
22191
|
-
--checkmark-color-dark: rgb(55, 55, 55);
|
|
22192
|
-
--checkmark-color: var(--checkmark-color-light);
|
|
22302
|
+
--checkmark-color: rgb(55, 55, 55);
|
|
22193
22303
|
--cursor: pointer;
|
|
22194
|
-
|
|
22195
|
-
--color-mix-light: black;
|
|
22196
|
-
--color-mix-dark: white;
|
|
22197
|
-
--color-mix: var(--color-mix-light);
|
|
22304
|
+
--color-mix: white;
|
|
22198
22305
|
|
|
22199
22306
|
/* Hover */
|
|
22200
22307
|
--border-color-hover: color-mix(in srgb, var(--border-color) 60%, black);
|
|
@@ -22288,9 +22395,9 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
|
22288
22395
|
black
|
|
22289
22396
|
);
|
|
22290
22397
|
|
|
22291
|
-
&[data-dark] {
|
|
22292
|
-
--color-mix:
|
|
22293
|
-
--checkmark-color:
|
|
22398
|
+
&[data-dark-background] {
|
|
22399
|
+
--color-mix: black;
|
|
22400
|
+
--checkmark-color: white;
|
|
22294
22401
|
}
|
|
22295
22402
|
}
|
|
22296
22403
|
}
|
|
@@ -22643,17 +22750,14 @@ const InputCheckboxBasic = props => {
|
|
|
22643
22750
|
});
|
|
22644
22751
|
const renderCheckboxMemoized = useCallback(renderCheckbox, [id, innerName, checked, innerRequired]);
|
|
22645
22752
|
const boxRef = useRef();
|
|
22646
|
-
|
|
22647
|
-
|
|
22648
|
-
|
|
22649
|
-
|
|
22650
|
-
|
|
22651
|
-
|
|
22652
|
-
naviCheckbox.removeAttribute("data-dark");
|
|
22653
|
-
} else {
|
|
22654
|
-
naviCheckbox.setAttribute("data-dark", "");
|
|
22753
|
+
useDarkBackgroundAttribute(boxRef, [accentColor, checked], {
|
|
22754
|
+
backgroundElementSelector: ".navi_checkbox_field",
|
|
22755
|
+
// the browser (chrome at least) prefer white in this scenario even if
|
|
22756
|
+
// the contrast is better with black, so we force it to white to match chrome behavior on this specific color
|
|
22757
|
+
hardcoded: {
|
|
22758
|
+
"#4476ff": "white"
|
|
22655
22759
|
}
|
|
22656
|
-
}
|
|
22760
|
+
});
|
|
22657
22761
|
return jsxs(Box, {
|
|
22658
22762
|
as: "span",
|
|
22659
22763
|
...remainingProps,
|
|
@@ -24417,7 +24521,7 @@ const InputTextualWithAction = props => {
|
|
|
24417
24521
|
onEnd: onActionEnd
|
|
24418
24522
|
});
|
|
24419
24523
|
return jsx(InputTextualBasic, {
|
|
24420
|
-
"data-action": boundAction.name,
|
|
24524
|
+
"data-action": boundAction.name || "anonymous",
|
|
24421
24525
|
"data-action-debounce": actionDebounce,
|
|
24422
24526
|
"data-action-after-change": actionAfterChange ? "" : undefined,
|
|
24423
24527
|
...rest,
|
|
@@ -28957,52 +29061,31 @@ const formatNumber = (value, { lang } = {}) => {
|
|
|
28957
29061
|
return new Intl.NumberFormat(lang).format(value);
|
|
28958
29062
|
};
|
|
28959
29063
|
|
|
28960
|
-
const CSS_VAR_NAME = "--x-color-contrasting";
|
|
28961
|
-
|
|
28962
|
-
const useContrastingColor = (ref, backgroundElementSelector) => {
|
|
28963
|
-
useLayoutEffect(() => {
|
|
28964
|
-
const el = ref.current;
|
|
28965
|
-
if (!el) {
|
|
28966
|
-
return;
|
|
28967
|
-
}
|
|
28968
|
-
let elementToCheck = el;
|
|
28969
|
-
{
|
|
28970
|
-
elementToCheck = el.querySelector(backgroundElementSelector);
|
|
28971
|
-
if (!elementToCheck) {
|
|
28972
|
-
return;
|
|
28973
|
-
}
|
|
28974
|
-
}
|
|
28975
|
-
const lightColor = "var(--navi-color-light)";
|
|
28976
|
-
const darkColor = "var(--navi-color-dark)";
|
|
28977
|
-
const backgroundColor = getComputedStyle(elementToCheck).backgroundColor;
|
|
28978
|
-
if (!backgroundColor) {
|
|
28979
|
-
el.style.removeProperty(CSS_VAR_NAME);
|
|
28980
|
-
return;
|
|
28981
|
-
}
|
|
28982
|
-
const colorPicked = pickLightOrDark(
|
|
28983
|
-
backgroundColor,
|
|
28984
|
-
lightColor,
|
|
28985
|
-
darkColor,
|
|
28986
|
-
el,
|
|
28987
|
-
);
|
|
28988
|
-
el.style.setProperty(CSS_VAR_NAME, colorPicked);
|
|
28989
|
-
}, []);
|
|
28990
|
-
};
|
|
28991
|
-
|
|
28992
29064
|
installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
28993
29065
|
@layer navi {
|
|
28994
29066
|
}
|
|
28995
29067
|
.navi_badge_count {
|
|
28996
29068
|
--font-size: 0.7em;
|
|
28997
29069
|
--x-background: var(--background);
|
|
28998
|
-
--x-background-color: var(--background-color);
|
|
29070
|
+
--x-background-color: var(--background-color, var(--x-background));
|
|
29071
|
+
--x-color-contrasting: var(--navi-color-black);
|
|
29072
|
+
--x-color: var(--color, var(--x-color-contrasting));
|
|
28999
29073
|
--padding-x: 0.5em;
|
|
29000
29074
|
--padding-y: 0.2em;
|
|
29001
29075
|
position: relative;
|
|
29002
29076
|
display: inline-block;
|
|
29003
|
-
color: var(--
|
|
29077
|
+
color: var(--x-color);
|
|
29004
29078
|
font-size: var(--font-size);
|
|
29005
29079
|
|
|
29080
|
+
&[data-dark-background] {
|
|
29081
|
+
--x-color-contrasting: var(--navi-color-white);
|
|
29082
|
+
}
|
|
29083
|
+
|
|
29084
|
+
&[data-loading] {
|
|
29085
|
+
--x-background: transparent;
|
|
29086
|
+
--x-background-color: transparent;
|
|
29087
|
+
}
|
|
29088
|
+
|
|
29006
29089
|
.navi_count_badge_overflow {
|
|
29007
29090
|
position: relative;
|
|
29008
29091
|
}
|
|
@@ -29015,7 +29098,7 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
|
29015
29098
|
padding-left: var(--padding-x);
|
|
29016
29099
|
line-height: normal;
|
|
29017
29100
|
background: var(--x-background);
|
|
29018
|
-
background-color: var(--x-background-color
|
|
29101
|
+
background-color: var(--x-background-color);
|
|
29019
29102
|
border-radius: 1em;
|
|
29020
29103
|
}
|
|
29021
29104
|
|
|
@@ -29031,7 +29114,7 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
|
29031
29114
|
align-items: center;
|
|
29032
29115
|
justify-content: center;
|
|
29033
29116
|
background: var(--x-background);
|
|
29034
|
-
background-color: var(--x-background-color
|
|
29117
|
+
background-color: var(--x-background-color);
|
|
29035
29118
|
border-radius: 50%;
|
|
29036
29119
|
|
|
29037
29120
|
&[data-single-char] {
|
|
@@ -29055,11 +29138,6 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
|
29055
29138
|
font-size: var(--x-number-font-size);
|
|
29056
29139
|
}
|
|
29057
29140
|
}
|
|
29058
|
-
|
|
29059
|
-
&[data-loading] {
|
|
29060
|
-
--x-background: transparent;
|
|
29061
|
-
--x-background-color: transparent;
|
|
29062
|
-
}
|
|
29063
29141
|
}
|
|
29064
29142
|
`;
|
|
29065
29143
|
const BadgeStyleCSSVars = {
|
|
@@ -29092,7 +29170,7 @@ const BadgeCount = ({
|
|
|
29092
29170
|
}) => {
|
|
29093
29171
|
const defaultRef = useRef();
|
|
29094
29172
|
const ref = props.ref || defaultRef;
|
|
29095
|
-
|
|
29173
|
+
useDarkBackgroundAttribute(ref);
|
|
29096
29174
|
let valueRequested = (() => {
|
|
29097
29175
|
if (typeof children !== "string") return children;
|
|
29098
29176
|
const parsed = Number(children);
|
|
@@ -29519,6 +29597,99 @@ const CodeBox = ({
|
|
|
29519
29597
|
});
|
|
29520
29598
|
};
|
|
29521
29599
|
|
|
29600
|
+
const navigator = typeof window === "undefined" ? undefined : window.navigator;
|
|
29601
|
+
const browserLang =
|
|
29602
|
+
typeof navigator !== "undefined"
|
|
29603
|
+
? (navigator.language ?? navigator.languages?.[0])
|
|
29604
|
+
: undefined;
|
|
29605
|
+
|
|
29606
|
+
const createIntl = ({ systemLang = browserLang } = {}) => {
|
|
29607
|
+
const languageMap = new Map();
|
|
29608
|
+
|
|
29609
|
+
let defaultLang = systemLang;
|
|
29610
|
+
|
|
29611
|
+
const add = (lang, translations) => {
|
|
29612
|
+
// Accumulate: merge with any existing translations for this lang
|
|
29613
|
+
const existing = languageMap.get(lang);
|
|
29614
|
+
if (existing) {
|
|
29615
|
+
translations = { ...existing, ...translations };
|
|
29616
|
+
}
|
|
29617
|
+
// Derived language inherits all keys not explicitly overridden
|
|
29618
|
+
// e.g. "fr-provencal" inherits from "fr"
|
|
29619
|
+
const dashIndex = lang.indexOf("-");
|
|
29620
|
+
if (dashIndex !== -1) {
|
|
29621
|
+
const parentLang = lang.slice(0, dashIndex);
|
|
29622
|
+
const parentTranslations = languageMap.get(parentLang);
|
|
29623
|
+
if (parentTranslations) {
|
|
29624
|
+
translations = { ...parentTranslations, ...translations };
|
|
29625
|
+
}
|
|
29626
|
+
}
|
|
29627
|
+
languageMap.set(lang, translations);
|
|
29628
|
+
|
|
29629
|
+
defaultLang = matchBestLang(systemLang, languageMap);
|
|
29630
|
+
};
|
|
29631
|
+
|
|
29632
|
+
const _getTranslationTemplate = (key, lang) => {
|
|
29633
|
+
if (!lang) {
|
|
29634
|
+
// no lang specified
|
|
29635
|
+
return key;
|
|
29636
|
+
}
|
|
29637
|
+
const translations = languageMap.get(lang);
|
|
29638
|
+
if (!translations) {
|
|
29639
|
+
// code don't know this language
|
|
29640
|
+
return key;
|
|
29641
|
+
}
|
|
29642
|
+
const template = translations[key];
|
|
29643
|
+
if (!template) {
|
|
29644
|
+
// code know this language but have no translation for this key
|
|
29645
|
+
return key;
|
|
29646
|
+
}
|
|
29647
|
+
return template;
|
|
29648
|
+
};
|
|
29649
|
+
|
|
29650
|
+
const format = (key, values, { lang = defaultLang } = {}) => {
|
|
29651
|
+
const template = _getTranslationTemplate(key, lang);
|
|
29652
|
+
return interpolate(template, values);
|
|
29653
|
+
};
|
|
29654
|
+
|
|
29655
|
+
return { languageMap, add, format };
|
|
29656
|
+
};
|
|
29657
|
+
|
|
29658
|
+
// Walk "fr-CA-variant" → "fr-CA" → "fr" until a registered lang is found
|
|
29659
|
+
const matchLang = (lang, languageMap) => {
|
|
29660
|
+
if (languageMap.has(lang)) return lang;
|
|
29661
|
+
const parts = lang.split("-");
|
|
29662
|
+
while (parts.length > 1) {
|
|
29663
|
+
parts.pop();
|
|
29664
|
+
const candidate = parts.join("-");
|
|
29665
|
+
if (languageMap.has(candidate)) return candidate;
|
|
29666
|
+
}
|
|
29667
|
+
return null;
|
|
29668
|
+
};
|
|
29669
|
+
|
|
29670
|
+
// lang can be a string or an ordered array of preference strings
|
|
29671
|
+
const matchBestLang = (lang, languageMap) => {
|
|
29672
|
+
if (!lang) {
|
|
29673
|
+
return null;
|
|
29674
|
+
}
|
|
29675
|
+
const candidates = Array.isArray(lang) ? lang : [lang];
|
|
29676
|
+
for (const candidate of candidates) {
|
|
29677
|
+
const match = matchLang(candidate, languageMap);
|
|
29678
|
+
if (match) {
|
|
29679
|
+
return match;
|
|
29680
|
+
}
|
|
29681
|
+
}
|
|
29682
|
+
return null;
|
|
29683
|
+
};
|
|
29684
|
+
|
|
29685
|
+
const interpolate = (template, values) => {
|
|
29686
|
+
if (!values || typeof template !== "string") return template;
|
|
29687
|
+
return template.replace(/\{(\w+)\}/g, (_, key) => {
|
|
29688
|
+
const value = values[key];
|
|
29689
|
+
return value !== undefined ? String(value) : `{${key}}`;
|
|
29690
|
+
});
|
|
29691
|
+
};
|
|
29692
|
+
|
|
29522
29693
|
installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
29523
29694
|
@layer navi {
|
|
29524
29695
|
.navi_quantity {
|
|
@@ -29579,12 +29750,49 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
|
29579
29750
|
}
|
|
29580
29751
|
}
|
|
29581
29752
|
`;
|
|
29753
|
+
const QuantityIntl = createIntl();
|
|
29754
|
+
const wellKnownUnitMap = new Map();
|
|
29755
|
+
/**
|
|
29756
|
+
* Registers a unit with its translations per language, making it a "well-known"
|
|
29757
|
+
* unit that Quantity will automatically translate and pluralize.
|
|
29758
|
+
*
|
|
29759
|
+
* @param {string} unitName - The unit identifier used in the `unit` prop, e.g. `"minute"`.
|
|
29760
|
+
* @param {Record<string, string | [string, string]>} langTranslations
|
|
29761
|
+
* A map of language codes to translations. Each value is either:
|
|
29762
|
+
* - A tuple `[singular, plural]` for languages with distinct plural forms.
|
|
29763
|
+
* - A plain string for languages that use the same form for singular and plural (e.g. Japanese).
|
|
29764
|
+
*
|
|
29765
|
+
* @example
|
|
29766
|
+
* Quantity.Intl.addUnit("minute", {
|
|
29767
|
+
* en: ["minute", "minutes"],
|
|
29768
|
+
* fr: ["minute", "minutes"],
|
|
29769
|
+
* ja: "分",
|
|
29770
|
+
* });
|
|
29771
|
+
*/
|
|
29772
|
+
QuantityIntl.addUnit = (unitName, langTranslations) => {
|
|
29773
|
+
const singularKey = unitName;
|
|
29774
|
+
const pluralKey = `${unitName}__plural`;
|
|
29775
|
+
wellKnownUnitMap.set(unitName, {
|
|
29776
|
+
singularKey,
|
|
29777
|
+
pluralKey
|
|
29778
|
+
});
|
|
29779
|
+
for (const [lang, translation] of Object.entries(langTranslations)) {
|
|
29780
|
+
const [singular, plural] = Array.isArray(translation) ? translation : [translation, translation];
|
|
29781
|
+
QuantityIntl.add(lang, {
|
|
29782
|
+
[singularKey]: singular,
|
|
29783
|
+
[pluralKey]: plural
|
|
29784
|
+
});
|
|
29785
|
+
}
|
|
29786
|
+
};
|
|
29787
|
+
const QuantityPropsCSSVars = {
|
|
29788
|
+
unitColor: "--unit-color",
|
|
29789
|
+
unitSizeRatio: "--unit-size-ratio"
|
|
29790
|
+
};
|
|
29582
29791
|
const QuantityPseudoClasses = [":hover", ":active", ":read-only", ":disabled", ":-navi-loading"];
|
|
29583
29792
|
const Quantity = ({
|
|
29584
29793
|
children,
|
|
29585
29794
|
unit,
|
|
29586
29795
|
unitPosition = "right",
|
|
29587
|
-
unitSizeRatio,
|
|
29588
29796
|
label,
|
|
29589
29797
|
size,
|
|
29590
29798
|
lang,
|
|
@@ -29603,6 +29811,7 @@ const Quantity = ({
|
|
|
29603
29811
|
return jsxs(Text, {
|
|
29604
29812
|
baseClassName: "navi_quantity",
|
|
29605
29813
|
"data-unit-bottom": unitBottom ? "" : undefined,
|
|
29814
|
+
propsCSSVars: QuantityPropsCSSVars,
|
|
29606
29815
|
basePseudoState: {
|
|
29607
29816
|
":read-only": readOnly,
|
|
29608
29817
|
":disabled": disabled,
|
|
@@ -29624,18 +29833,49 @@ const Quantity = ({
|
|
|
29624
29833
|
flowInline: true,
|
|
29625
29834
|
children: jsx(LoadingDots, {})
|
|
29626
29835
|
}) : valueFormatted
|
|
29627
|
-
}), unit && jsx(
|
|
29628
|
-
|
|
29629
|
-
|
|
29630
|
-
|
|
29631
|
-
"--unit-size-ratio": unitSizeRatio
|
|
29632
|
-
})
|
|
29633
|
-
},
|
|
29634
|
-
children: unit
|
|
29836
|
+
}), unit && jsx(Unit, {
|
|
29837
|
+
value: value,
|
|
29838
|
+
unit: unit,
|
|
29839
|
+
lang: lang
|
|
29635
29840
|
})]
|
|
29636
29841
|
})]
|
|
29637
29842
|
});
|
|
29638
29843
|
};
|
|
29844
|
+
Quantity.Intl = QuantityIntl;
|
|
29845
|
+
const Unit = ({
|
|
29846
|
+
value,
|
|
29847
|
+
unit,
|
|
29848
|
+
lang
|
|
29849
|
+
}) => {
|
|
29850
|
+
let unitText = unit;
|
|
29851
|
+
if (Array.isArray(unit)) {
|
|
29852
|
+
const [singular, plural] = unit;
|
|
29853
|
+
unitText = value > 1 ? plural : singular;
|
|
29854
|
+
} else {
|
|
29855
|
+
const wellKnownUnit = wellKnownUnitMap.get(unit);
|
|
29856
|
+
if (wellKnownUnit) {
|
|
29857
|
+
const {
|
|
29858
|
+
singularKey,
|
|
29859
|
+
pluralKey
|
|
29860
|
+
} = wellKnownUnit;
|
|
29861
|
+
if (value > 1) {
|
|
29862
|
+
unitText = QuantityIntl.format(pluralKey, {
|
|
29863
|
+
x: value
|
|
29864
|
+
}, {
|
|
29865
|
+
lang
|
|
29866
|
+
});
|
|
29867
|
+
} else {
|
|
29868
|
+
unitText = QuantityIntl.format(singularKey, undefined, {
|
|
29869
|
+
lang
|
|
29870
|
+
});
|
|
29871
|
+
}
|
|
29872
|
+
}
|
|
29873
|
+
}
|
|
29874
|
+
return jsx("span", {
|
|
29875
|
+
className: "navi_quantity_unit",
|
|
29876
|
+
children: unitText
|
|
29877
|
+
});
|
|
29878
|
+
};
|
|
29639
29879
|
const parseQuantityValue = children => {
|
|
29640
29880
|
if (typeof children !== "string") {
|
|
29641
29881
|
return children;
|
|
@@ -29659,6 +29899,14 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
|
29659
29899
|
--fill-color-optimum: light-dark(#0f7c0f, #4caf50);
|
|
29660
29900
|
--fill-color-suboptimum: light-dark(#fdb900, #ffc107);
|
|
29661
29901
|
--fill-color-even-less-good: light-dark(#d83b01, #f44336);
|
|
29902
|
+
|
|
29903
|
+
--x-color: var(--navi-color-black);
|
|
29904
|
+
--x-shadow-color: white;
|
|
29905
|
+
--shadow-size: 0.5em;
|
|
29906
|
+
&[data-dark-background] {
|
|
29907
|
+
--x-color: white;
|
|
29908
|
+
--x-shadow-color: black;
|
|
29909
|
+
}
|
|
29662
29910
|
}
|
|
29663
29911
|
}
|
|
29664
29912
|
|
|
@@ -29704,11 +29952,11 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
|
29704
29952
|
display: flex;
|
|
29705
29953
|
align-items: center;
|
|
29706
29954
|
justify-content: center;
|
|
29707
|
-
color: var(--x-
|
|
29955
|
+
color: var(--x-color);
|
|
29708
29956
|
font-size: calc(var(--height) * 0.55);
|
|
29709
29957
|
text-shadow:
|
|
29710
|
-
0 0
|
|
29711
|
-
0 0
|
|
29958
|
+
0 0 var(--shadow-size) var(--x-shadow-color),
|
|
29959
|
+
0 0 calc(var(--shadow-size) * 0.5) var(--x-shadow-color);
|
|
29712
29960
|
white-space: nowrap;
|
|
29713
29961
|
pointer-events: none;
|
|
29714
29962
|
user-select: none;
|
|
@@ -29790,8 +30038,10 @@ const Meter = ({
|
|
|
29790
30038
|
borderless,
|
|
29791
30039
|
transition,
|
|
29792
30040
|
style,
|
|
29793
|
-
...
|
|
30041
|
+
...rest
|
|
29794
30042
|
}) => {
|
|
30043
|
+
const defaultRef = useRef();
|
|
30044
|
+
const ref = rest.ref || defaultRef;
|
|
29795
30045
|
const clampedValue = value < min ? min : value > max ? max : value;
|
|
29796
30046
|
const fillRatio = max === min ? 0 : (clampedValue - min) / (max - min);
|
|
29797
30047
|
let children = caption;
|
|
@@ -29799,6 +30049,7 @@ const Meter = ({
|
|
|
29799
30049
|
children = jsx(Quantity, {
|
|
29800
30050
|
unit: "%",
|
|
29801
30051
|
unitSizeRatio: "1",
|
|
30052
|
+
unitColor: "inherit",
|
|
29802
30053
|
children: Math.round(fillRatio * 100)
|
|
29803
30054
|
});
|
|
29804
30055
|
}
|
|
@@ -29806,28 +30057,15 @@ const Meter = ({
|
|
|
29806
30057
|
const fillColorVar = level === "optimum" ? "var(--fill-color-optimum)" : level === "suboptimum" ? "var(--fill-color-suboptimum)" : "var(--fill-color-even-less-good)";
|
|
29807
30058
|
reportDisabledToLabel(disabled);
|
|
29808
30059
|
reportReadOnlyToLabel(readOnly);
|
|
29809
|
-
|
|
29810
|
-
|
|
29811
|
-
|
|
29812
|
-
|
|
29813
|
-
|
|
29814
|
-
|
|
29815
|
-
|
|
29816
|
-
return;
|
|
29817
|
-
}
|
|
29818
|
-
// When fill covers less than half the track, the text center sits on the
|
|
29819
|
-
// empty track — use the track color for contrast. Otherwise use fill color.
|
|
29820
|
-
const bgEl = fillRatio >= 0.5 ? trackContainer.querySelector(".navi_meter_fill") : trackContainer.querySelector(".navi_meter_track");
|
|
29821
|
-
if (!bgEl) {
|
|
29822
|
-
return;
|
|
29823
|
-
}
|
|
29824
|
-
const bgColor = getComputedStyle(bgEl).backgroundColor;
|
|
29825
|
-
const textColor = pickLightOrDark(bgColor, "white", "black", bgEl);
|
|
29826
|
-
const shadowColor = textColor === "white" ? "black" : "white";
|
|
29827
|
-
trackContainer.style.setProperty("--x-caption-color", textColor);
|
|
29828
|
-
trackContainer.style.setProperty("--x-caption-shadow-color", shadowColor);
|
|
29829
|
-
}, [children, level, fillRatio]);
|
|
30060
|
+
|
|
30061
|
+
// When fill covers less than half the track, the text center sits on the
|
|
30062
|
+
// empty track — use the track color for contrast. Otherwise use fill color.
|
|
30063
|
+
const backgroundElementSelector = fillRatio >= 0.5 ? ".navi_meter_fill" : ".navi_meter_track";
|
|
30064
|
+
useDarkBackgroundAttribute(ref, [], {
|
|
30065
|
+
backgroundElementSelector
|
|
30066
|
+
});
|
|
29830
30067
|
return jsx(Box, {
|
|
30068
|
+
ref: ref,
|
|
29831
30069
|
role: "meter",
|
|
29832
30070
|
"aria-valuenow": clampedValue,
|
|
29833
30071
|
"aria-valuemin": min,
|
|
@@ -29854,10 +30092,9 @@ const Meter = ({
|
|
|
29854
30092
|
"--x-fill-color": fillColorVar,
|
|
29855
30093
|
...style
|
|
29856
30094
|
},
|
|
29857
|
-
...
|
|
30095
|
+
...rest,
|
|
29858
30096
|
children: jsxs("span", {
|
|
29859
30097
|
className: "navi_meter_track_container",
|
|
29860
|
-
ref: trackContainerRef,
|
|
29861
30098
|
children: [jsx(LoaderBackground, {
|
|
29862
30099
|
loading: loading,
|
|
29863
30100
|
color: "var(--loader-color)",
|
|
@@ -30318,5 +30555,5 @@ const UserSvg = () => jsx("svg", {
|
|
|
30318
30555
|
})
|
|
30319
30556
|
});
|
|
30320
30557
|
|
|
30321
|
-
export { ActionRenderer, ActiveKeyboardShortcuts, Address, BadgeCount, Box, Button, ButtonCopyToClipboard, Caption, CheckSvg, Checkbox, CheckboxList, Code, Col, Colgroup, ConstructionSvg, Details, DialogLayout, Editable, ErrorBoundaryContext, ExclamationSvg, EyeClosedSvg, EyeSvg, Form, Group, HeartSvg, HomeSvg, Icon, Image, Input, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, MessageBox, Meter, Paragraph, Quantity, Radio, RadioList, Route, RouteLink, Routes, RowNumberCol, RowNumberTableCell, SINGLE_SPACE_CONSTRAINT, SVGMaskOverlay, SearchSvg, Select, SelectionContext, Separator, SettingsSvg, StarSvg, SummaryMarker, Svg, Tab, TabList, Table, TableCell, Tbody, Text, Thead, Title, Tr, UITransition, UserSvg, ViewportLayout, actionIntegratedVia, actionRunEffect, addCustomMessage, arraySignalMembership, compareTwoJsValues, createAction, createAvailableConstraint, createRequestCanceller, createSelectionKeyboardShortcuts, enableDebugActions, enableDebugOnDocumentLoading, forwardActionRequested, installCustomConstraintValidation, isCellSelected, isColumnSelected, isRowSelected, localStorageSignal, navBack, navForward, navTo, openCallout, rawUrlPart, reload, removeCustomMessage, requestAction, rerunActions, resource, route, routeAction, setBaseUrl, setupRoutes, stateSignal, stopLoad, stringifyTableSelectionValue, updateActions, useActionData, useActionStatus, useArraySignalMembership, useCalloutClose, useCancelPrevious, useCellsAndColumns, useConstraintValidityState, useDependenciesDiff, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useKeyboardShortcuts, useMatchingRouteInfo, useNavState$1 as useNavState, useRouteStatus, useRunOnMount, useSelectableElement, useSelectionController, useSignalSync, useStateArray, useTitleLevel, useUrlSearchParam, valueInLocalStorage };
|
|
30558
|
+
export { ActionRenderer, ActiveKeyboardShortcuts, Address, BadgeCount, Box, Button, ButtonCopyToClipboard, Caption, CheckSvg, Checkbox, CheckboxList, Code, Col, Colgroup, ConstructionSvg, Details, DialogLayout, Editable, ErrorBoundaryContext, ExclamationSvg, EyeClosedSvg, EyeSvg, Form, Group, HeartSvg, HomeSvg, Icon, Image, Input, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, MessageBox, Meter, Paragraph, Quantity, QuantityIntl, Radio, RadioList, Route, RouteLink, Routes, RowNumberCol, RowNumberTableCell, SINGLE_SPACE_CONSTRAINT, SVGMaskOverlay, SearchSvg, Select, SelectionContext, Separator, SettingsSvg, StarSvg, SummaryMarker, Svg, Tab, TabList, Table, TableCell, Tbody, Text, Thead, Title, Tr, UITransition, UserSvg, ViewportLayout, actionIntegratedVia, actionRunEffect, addCustomMessage, arraySignalMembership, compareTwoJsValues, createAction, createAvailableConstraint, createIntl, createRequestCanceller, createSelectionKeyboardShortcuts, enableDebugActions, enableDebugOnDocumentLoading, forwardActionRequested, installCustomConstraintValidation, isCellSelected, isColumnSelected, isRowSelected, localStorageSignal, navBack, navForward, navTo, openCallout, rawUrlPart, reload, removeCustomMessage, requestAction, rerunActions, resource, route, routeAction, setBaseUrl, setupRoutes, stateSignal, stopLoad, stringifyTableSelectionValue, updateActions, useActionData, useActionStatus, useArraySignalMembership, useCalloutClose, useCancelPrevious, useCellsAndColumns, useConstraintValidityState, useDarkBackgroundAttribute, useDependenciesDiff, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useKeyboardShortcuts, useMatchingRouteInfo, useNavState$1 as useNavState, useRouteStatus, useRunOnMount, useSelectableElement, useSelectionController, useSignalSync, useStateArray, useTitleLevel, useUrlSearchParam, valueInLocalStorage };
|
|
30322
30559
|
//# sourceMappingURL=jsenv_navi.js.map
|