@jsenv/navi 0.29.481 → 0.29.482
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/dev/jsenv_navi.js +135 -9
- package/dist/dev/jsenv_navi.js.map +5 -5
- package/dist/jsenv_navi.js +135 -9
- package/dist/jsenv_navi.js.map +5 -5
- package/docs/testid.md +3 -1
- package/package.json +3 -3
package/dist/jsenv_navi.js
CHANGED
|
@@ -41994,6 +41994,14 @@ const useParallelGuard = (max) => {
|
|
|
41994
41994
|
* `getAssociatedLabels` in control_hooks.jsx. Both channels carry the same
|
|
41995
41995
|
* values and land on the same setters, so a control reachable through both is
|
|
41996
41996
|
* simply told twice.
|
|
41997
|
+
*
|
|
41998
|
+
* The link also runs the other way, for a control whose role is on its root
|
|
41999
|
+
* (`nameOn: "root"`, a wheel): a label[for] can only point at its host, an
|
|
42000
|
+
* aria-hidden carrier, and names nothing. That control says so in the state it
|
|
42001
|
+
* publishes; its label then drops `for` and publishes its own id under the
|
|
42002
|
+
* control's id instead, and the control names its root with the ids it finds
|
|
42003
|
+
* there (aria-labelledby). A label[for] kept as well would name the carrier
|
|
42004
|
+
* too, and a lookup by label would find two elements.
|
|
41997
42005
|
*/
|
|
41998
42006
|
|
|
41999
42007
|
const stateByControlId = new Map();
|
|
@@ -42046,6 +42054,68 @@ const subscribeToControlState = (controlId, callback) => {
|
|
|
42046
42054
|
};
|
|
42047
42055
|
};
|
|
42048
42056
|
|
|
42057
|
+
const labelIdSetByControlId = new Map();
|
|
42058
|
+
const labelCallbackSetByControlId = new Map();
|
|
42059
|
+
|
|
42060
|
+
const notifyLabelIds = (controlId) => {
|
|
42061
|
+
const callbackSet = labelCallbackSetByControlId.get(controlId);
|
|
42062
|
+
if (!callbackSet) {
|
|
42063
|
+
return;
|
|
42064
|
+
}
|
|
42065
|
+
const labelIds = readLabelIds(controlId);
|
|
42066
|
+
for (const callback of callbackSet) {
|
|
42067
|
+
callback(labelIds);
|
|
42068
|
+
}
|
|
42069
|
+
};
|
|
42070
|
+
// One string ("label_a label_b"), the shape aria-labelledby takes, and equal
|
|
42071
|
+
// from one call to the next while nothing changed.
|
|
42072
|
+
const readLabelIds = (controlId) => {
|
|
42073
|
+
const labelIdSet = labelIdSetByControlId.get(controlId);
|
|
42074
|
+
return labelIdSet ? Array.from(labelIdSet).join(" ") : undefined;
|
|
42075
|
+
};
|
|
42076
|
+
|
|
42077
|
+
/**
|
|
42078
|
+
* Publishes `labelId` as a label of the control identified by `controlId`.
|
|
42079
|
+
* Returns the teardown.
|
|
42080
|
+
*/
|
|
42081
|
+
const publishLabelToControl = (controlId, labelId) => {
|
|
42082
|
+
let labelIdSet = labelIdSetByControlId.get(controlId);
|
|
42083
|
+
if (!labelIdSet) {
|
|
42084
|
+
labelIdSet = new Set();
|
|
42085
|
+
labelIdSetByControlId.set(controlId, labelIdSet);
|
|
42086
|
+
}
|
|
42087
|
+
labelIdSet.add(labelId);
|
|
42088
|
+
notifyLabelIds(controlId);
|
|
42089
|
+
return () => {
|
|
42090
|
+
labelIdSet.delete(labelId);
|
|
42091
|
+
if (labelIdSet.size === 0) {
|
|
42092
|
+
labelIdSetByControlId.delete(controlId);
|
|
42093
|
+
}
|
|
42094
|
+
notifyLabelIds(controlId);
|
|
42095
|
+
};
|
|
42096
|
+
};
|
|
42097
|
+
|
|
42098
|
+
/**
|
|
42099
|
+
* Subscribes to the ids of the labels published for `controlId`. The callback
|
|
42100
|
+
* is called right away (with `undefined` when there is none), then on every
|
|
42101
|
+
* change. Returns the teardown.
|
|
42102
|
+
*/
|
|
42103
|
+
const subscribeToControlLabels = (controlId, callback) => {
|
|
42104
|
+
let callbackSet = labelCallbackSetByControlId.get(controlId);
|
|
42105
|
+
if (!callbackSet) {
|
|
42106
|
+
callbackSet = new Set();
|
|
42107
|
+
labelCallbackSetByControlId.set(controlId, callbackSet);
|
|
42108
|
+
}
|
|
42109
|
+
callbackSet.add(callback);
|
|
42110
|
+
callback(readLabelIds(controlId));
|
|
42111
|
+
return () => {
|
|
42112
|
+
callbackSet.delete(callback);
|
|
42113
|
+
if (callbackSet.size === 0) {
|
|
42114
|
+
labelCallbackSetByControlId.delete(controlId);
|
|
42115
|
+
}
|
|
42116
|
+
};
|
|
42117
|
+
};
|
|
42118
|
+
|
|
42049
42119
|
const FormContext = createContext();
|
|
42050
42120
|
|
|
42051
42121
|
/**
|
|
@@ -46342,17 +46412,11 @@ const useControlProps = (props, {
|
|
|
46342
46412
|
};
|
|
46343
46413
|
}
|
|
46344
46414
|
if (defaultAction === "activate") {
|
|
46345
|
-
// activating the control (e.g. space on a button/range)
|
|
46346
46415
|
return {
|
|
46347
46416
|
name: `keydown to activate`,
|
|
46348
46417
|
prevented: () => e.preventDefault(),
|
|
46349
46418
|
allowed: () => {
|
|
46350
46419
|
triggerUIAction(e);
|
|
46351
|
-
if (controlType === "button" && e.key === " ") {
|
|
46352
|
-
// prevent browser dispatching click
|
|
46353
|
-
// it could lead to duplicates as we explicitely handled the space to click here
|
|
46354
|
-
e.preventDefault();
|
|
46355
|
-
}
|
|
46356
46420
|
}
|
|
46357
46421
|
};
|
|
46358
46422
|
}
|
|
@@ -46489,7 +46553,15 @@ const useControlProps = (props, {
|
|
|
46489
46553
|
runWhenActionSucceeded(completion, deferredCommand);
|
|
46490
46554
|
};
|
|
46491
46555
|
return {
|
|
46492
|
-
keyDown:
|
|
46556
|
+
keyDown: e => {
|
|
46557
|
+
if (getKeyboardEventDefaultAction(e) === "activate") {
|
|
46558
|
+
// The browser presses the button itself — Enter clicks on
|
|
46559
|
+
// keydown, Space on keyup — and the click reaction below answers
|
|
46560
|
+
// that press, once: ui action, action and command together.
|
|
46561
|
+
return null;
|
|
46562
|
+
}
|
|
46563
|
+
return keyDownDefault(e);
|
|
46564
|
+
},
|
|
46493
46565
|
mouseDown: e => {
|
|
46494
46566
|
if (actsOnMouseDown) {
|
|
46495
46567
|
return {
|
|
@@ -46508,7 +46580,9 @@ const useControlProps = (props, {
|
|
|
46508
46580
|
return null;
|
|
46509
46581
|
},
|
|
46510
46582
|
click: e => {
|
|
46511
|
-
|
|
46583
|
+
// A click no mousedown came before (the keyboard, a label) is a
|
|
46584
|
+
// press the mousedown reaction never saw.
|
|
46585
|
+
if (actsOnMouseDown && e.detail !== 0) {
|
|
46512
46586
|
return null;
|
|
46513
46587
|
}
|
|
46514
46588
|
return {
|
|
@@ -47729,7 +47803,8 @@ const useInteractiveProps = (props, {
|
|
|
47729
47803
|
const controlState = {
|
|
47730
47804
|
disabled: disabledResolved,
|
|
47731
47805
|
readOnly,
|
|
47732
|
-
required: requiredResolved
|
|
47806
|
+
required: requiredResolved,
|
|
47807
|
+
nameOn
|
|
47733
47808
|
};
|
|
47734
47809
|
for (const label of getAssociatedLabels(element)) {
|
|
47735
47810
|
label.dispatchEvent(new CustomEvent("navi_control_state", {
|
|
@@ -47750,6 +47825,22 @@ const useInteractiveProps = (props, {
|
|
|
47750
47825
|
unpublishControlStateToLabels(publishedIdRef.current);
|
|
47751
47826
|
};
|
|
47752
47827
|
}, []);
|
|
47828
|
+
// Named on its root, the control is named by its labels' ids: they drop
|
|
47829
|
+
// their `for` for it (see control_label_state.js). A name the caller gives
|
|
47830
|
+
// wins.
|
|
47831
|
+
const [labelledBy, setLabelledBy] = useState();
|
|
47832
|
+
const {
|
|
47833
|
+
id
|
|
47834
|
+
} = props;
|
|
47835
|
+
useLayoutEffect(() => {
|
|
47836
|
+
if (nameOn !== "root" || !id) {
|
|
47837
|
+
return undefined;
|
|
47838
|
+
}
|
|
47839
|
+
return subscribeToControlLabels(id, setLabelledBy);
|
|
47840
|
+
}, [id]);
|
|
47841
|
+
if (labelledBy && controlRootProps["aria-label"] === undefined && controlRootProps["aria-labelledby"] === undefined) {
|
|
47842
|
+
controlRootProps["aria-labelledby"] = labelledBy;
|
|
47843
|
+
}
|
|
47753
47844
|
}
|
|
47754
47845
|
{
|
|
47755
47846
|
const isCheckable = isCheckableInput(uiStateController.controlType, props.type);
|
|
@@ -58882,6 +58973,7 @@ const Label = props => {
|
|
|
58882
58973
|
const [readOnly, setReadOnly] = useState(false);
|
|
58883
58974
|
const [required, setRequired] = useState(false);
|
|
58884
58975
|
const [connected, setConnected] = useState(false);
|
|
58976
|
+
const [namesControlRoot, setNamesControlRoot] = useState(false);
|
|
58885
58977
|
// Set htmlFor only when we know the correct target id:
|
|
58886
58978
|
// - caller explicitly provided one (even undefined to opt out)
|
|
58887
58979
|
// - a parent Field provided one via ControlIdContext
|
|
@@ -58908,8 +59000,37 @@ const Label = props => {
|
|
|
58908
59000
|
setDisabled(Boolean(controlState?.disabled));
|
|
58909
59001
|
setReadOnly(Boolean(controlState?.readOnly));
|
|
58910
59002
|
setRequired(Boolean(controlState?.required));
|
|
59003
|
+
setNamesControlRoot(controlState?.nameOn === "root");
|
|
58911
59004
|
});
|
|
58912
59005
|
}, [htmlFor]);
|
|
59006
|
+
// A control whose role is on its root (a wheel) is named by this label's id
|
|
59007
|
+
// rather than by `for`, which can only point at its aria-hidden host (see
|
|
59008
|
+
// control_label_state.js).
|
|
59009
|
+
const labelId = props.id;
|
|
59010
|
+
useLayoutEffect(() => {
|
|
59011
|
+
if (!htmlFor || !namesControlRoot) {
|
|
59012
|
+
return undefined;
|
|
59013
|
+
}
|
|
59014
|
+
return publishLabelToControl(htmlFor, labelId);
|
|
59015
|
+
}, [htmlFor, namesControlRoot, labelId]);
|
|
59016
|
+
// …and without `for`, the press that focuses the control is this label's
|
|
59017
|
+
// to answer. A press on something interactive inside the label stays that
|
|
59018
|
+
// thing's, as the browser does for a label[for].
|
|
59019
|
+
const onClickFocusingControlRoot = e => {
|
|
59020
|
+
props.onClick?.(e);
|
|
59021
|
+
if (e.defaultPrevented) {
|
|
59022
|
+
return;
|
|
59023
|
+
}
|
|
59024
|
+
const interactive = e.target.closest("a[href], button, input, select, textarea, [tabindex]");
|
|
59025
|
+
if (interactive && e.currentTarget.contains(interactive)) {
|
|
59026
|
+
return;
|
|
59027
|
+
}
|
|
59028
|
+
const host = document.getElementById(htmlFor);
|
|
59029
|
+
const controlRoot = host && findControlRoot(host);
|
|
59030
|
+
if (controlRoot) {
|
|
59031
|
+
moveFocusTo(controlRoot);
|
|
59032
|
+
}
|
|
59033
|
+
};
|
|
58913
59034
|
const [messageProps, remainingProps] = extractMessageAndRemainingProps({
|
|
58914
59035
|
...props,
|
|
58915
59036
|
requiredIndicator: undefined
|
|
@@ -58926,6 +59047,8 @@ const Label = props => {
|
|
|
58926
59047
|
":read-only": readOnly
|
|
58927
59048
|
},
|
|
58928
59049
|
...remainingProps,
|
|
59050
|
+
htmlFor: namesControlRoot ? undefined : htmlFor,
|
|
59051
|
+
onClick: namesControlRoot ? onClickFocusingControlRoot : props.onClick,
|
|
58929
59052
|
onnavi_control_state: e => {
|
|
58930
59053
|
setConnected(true);
|
|
58931
59054
|
setDisabled(e.detail.disabled);
|
|
@@ -66217,10 +66340,12 @@ const TimeHour = ({
|
|
|
66217
66340
|
children: String(children)
|
|
66218
66341
|
});
|
|
66219
66342
|
}
|
|
66343
|
+
const dateTime = formatClock(hours * 3600);
|
|
66220
66344
|
if (format === "timestring") {
|
|
66221
66345
|
const totalMinutes = Math.round(hours * 60);
|
|
66222
66346
|
const date = new Date(1970, 0, 1, Math.floor(totalMinutes / 60), totalMinutes % 60, 0);
|
|
66223
66347
|
return jsx(TimeText, {
|
|
66348
|
+
dateTime: dateTime,
|
|
66224
66349
|
...props,
|
|
66225
66350
|
children: formatTime(date, {
|
|
66226
66351
|
lang
|
|
@@ -66233,6 +66358,7 @@ const TimeHour = ({
|
|
|
66233
66358
|
forceUnit
|
|
66234
66359
|
});
|
|
66235
66360
|
return jsx(TimeText, {
|
|
66361
|
+
dateTime: dateTime,
|
|
66236
66362
|
...props,
|
|
66237
66363
|
children: text
|
|
66238
66364
|
});
|