@jsenv/navi 0.29.110 → 0.29.111

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.
@@ -11,7 +11,7 @@ import { isValidElement, createContext, render, h, Fragment, toChildArray, optio
11
11
  import { useErrorBoundary, useLayoutEffect, useContext, useCallback, useRef, useState, useEffect, useMemo, useId } from "preact/hooks";
12
12
  import { jsxs, jsx, Fragment as Fragment$1 } from "preact/jsx-runtime";
13
13
  import { prefixFirstAndIndentRemainingLines } from "@jsenv/humanize";
14
- import { parseDuration, durationContainsNaN, compareTwoDurations, durationToSeconds, createValidity, durationToISOString } from "@jsenv/validity";
14
+ import { parseDuration, durationContainsNaN, compareTwoDurations, durationToSeconds, DISPLAYABLE_RULE, MAX_LINE_BREAKS_RULE, NO_EMOJI_RULE, SINGLE_SPACE_RULE, createValidity, resolveCharClass, getCharClassMessageKey, compileCharClassAnchored, compileCharClass, CHAR_CLASS_PRESETS, durationToISOString } from "@jsenv/validity";
15
15
  export { compareTwoDurations, durationContainsNaN, durationToHours, durationToISOString, durationToMinutes, durationToNumber, durationToSeconds, durationToString, parseDuration } from "@jsenv/validity";
16
16
  import { Suspense, createPortal, forwardRef } from "preact/compat";
17
17
 
@@ -3676,8 +3676,55 @@ const findProxyControllers = (realInputId) => {
3676
3676
  return proxyControllersByRealInputId.get(realInputId) ?? null;
3677
3677
  };
3678
3678
 
3679
+ /**
3680
+ * The attributes constraints read, filled by each constraint module as it
3681
+ * evaluates. A constraint declares the attribute it wants (`"data-no-emoji"`)
3682
+ * and gets the prop for free: a control accepts the camelCase form
3683
+ * (`noEmoji`) and writes it on the control host under the attribute name — the
3684
+ * same conversion `element.dataset` does, so what a component is passed and
3685
+ * what ends up in the DOM read as one thing.
3686
+ */
3687
+
3679
3688
  const CONSTRAINT_ATTRIBUTE_SET = new Set();
3680
3689
 
3690
+ const dataAttributeCache = new Map();
3691
+ // A constraint imported lazily registers its attribute after controls have
3692
+ // already rendered, so an answer computed before it arrived must not survive it.
3693
+ let attributeCountWhenCached = 0;
3694
+ /**
3695
+ * The constraint attribute a prop stands for, `null` when it stands for none:
3696
+ * `"noEmoji"` → `"data-no-emoji"`.
3697
+ */
3698
+ const constraintAttributeFromProp = (key) => {
3699
+ if (attributeCountWhenCached !== CONSTRAINT_ATTRIBUTE_SET.size) {
3700
+ dataAttributeCache.clear();
3701
+ attributeCountWhenCached = CONSTRAINT_ATTRIBUTE_SET.size;
3702
+ }
3703
+ const fromCache = dataAttributeCache.get(key);
3704
+ if (fromCache !== undefined) {
3705
+ return fromCache;
3706
+ }
3707
+ let attribute = null;
3708
+ // An attribute is already written as one (`data-no-emoji`, `aria-label`) —
3709
+ // there is nothing to convert, and the literal lookup has already happened.
3710
+ if (!key.includes("-")) {
3711
+ const candidate = `data-${key.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`)}`;
3712
+ if (CONSTRAINT_ATTRIBUTE_SET.has(candidate)) {
3713
+ attribute = candidate;
3714
+ }
3715
+ }
3716
+ dataAttributeCache.set(key, attribute);
3717
+ return attribute;
3718
+ };
3719
+
3720
+ /**
3721
+ * Whether a constraint attribute is on. Present means on — `""` is how HTML
3722
+ * writes a bare attribute — and only the values that say "passed, and off"
3723
+ * turn it off.
3724
+ */
3725
+ const isConstraintAttributeOn = (value) =>
3726
+ value !== undefined && value !== null && value !== false;
3727
+
3681
3728
  const CONSTRAINT_NAME_TO_PROP = {
3682
3729
  disabled: "disabledMessage",
3683
3730
  required: "requiredMessage",
@@ -3690,6 +3737,8 @@ const CONSTRAINT_NAME_TO_PROP = {
3690
3737
  max: "maxMessage",
3691
3738
  single_space: "singleSpaceMessage",
3692
3739
  displayable: "displayableMessage",
3740
+ max_line_breaks: "maxLineBreaksMessage",
3741
+ no_emoji: "noEmojiMessage",
3693
3742
  same_as: "sameAsMessage",
3694
3743
  min_lower_letter: "minLowerLetterMessage",
3695
3744
  min_upper_letter: "minUpperLetterMessage",
@@ -3756,10 +3805,13 @@ const getConstraintMessage = (
3756
3805
  };
3757
3806
  };
3758
3807
 
3759
- // prop that we'll set on the control
3808
+ // prop that we'll set on the control.
3809
+ // CONSTRAINT_ATTRIBUTE_SET is consulted through controlAttributeFromProp()
3810
+ // rather than spread in here: a constraint registers into it when its own
3811
+ // module evaluates, so anything read at module-eval time reads a set that is
3812
+ // still filling up — and in a bundle, whichever constraint happens to evaluate
3813
+ // last would silently lose its attribute.
3760
3814
  const CONTROL_ATTRIBUTE_SET = new Set([
3761
- ...CONSTRAINT_ATTRIBUTE_SET,
3762
-
3763
3815
  "ref",
3764
3816
  "children",
3765
3817
  "id",
@@ -3801,7 +3853,6 @@ const CONTROL_ATTRIBUTE_SET = new Set([
3801
3853
  ]);
3802
3854
  // prop concerning control but that won't end up in the DOM if not inside CONTROL_ATTRIBUTE_SET
3803
3855
  const CONTROL_PROP_SET = new Set([
3804
- ...CONTROL_ATTRIBUTE_SET,
3805
3856
  ...CONSTRAINT_MESSAGE_PROP_NAME_SET,
3806
3857
 
3807
3858
  "action",
@@ -3863,6 +3914,21 @@ const CONTROL_PROP_SET = new Set([
3863
3914
  "maxLengthGuard",
3864
3915
  ]);
3865
3916
 
3917
+ /**
3918
+ * The attribute a prop must be written as on the control host, `null` when the
3919
+ * prop is not one. A constraint attribute may be passed either way — as the
3920
+ * attribute itself (`data-no-emoji`) or as the prop it stands for (`noEmoji`).
3921
+ */
3922
+ const controlAttributeFromProp = (key) => {
3923
+ if (CONTROL_ATTRIBUTE_SET.has(key) || CONSTRAINT_ATTRIBUTE_SET.has(key)) {
3924
+ return key;
3925
+ }
3926
+ return constraintAttributeFromProp(key);
3927
+ };
3928
+
3929
+ const isControlProp = (key) =>
3930
+ CONTROL_PROP_SET.has(key) || controlAttributeFromProp(key) !== null;
3931
+
3866
3932
  const MessagePropsRefContext = createContext();
3867
3933
 
3868
3934
  const ControlIdContext = createContext();
@@ -7239,30 +7305,48 @@ naviI18n.addAll({
7239
7305
  fr: "L'heure doit être <strong>[max]</strong> ou moins.",
7240
7306
  en: "The time must be <strong>[max]</strong> or earlier.",
7241
7307
  },
7242
- "constraint.single_space.start.default": {
7308
+ "constraint.single_space.start": {
7243
7309
  fr: "Ce champ ne doit pas commencer par un espace.",
7244
7310
  en: "This field must not start with a space.",
7245
7311
  },
7246
- "constraint.single_space.end.default": {
7312
+ "constraint.single_space.end": {
7247
7313
  fr: "Ce champ ne doit pas finir par un espace.",
7248
7314
  en: "This field must not end with a space.",
7249
7315
  },
7250
- "constraint.single_space.consecutive.default": {
7316
+ "constraint.single_space.consecutive": {
7251
7317
  fr: "Ce champ ne doit pas contenir plusieurs espaces consécutifs.",
7252
7318
  en: "This field must not contain consecutive spaces.",
7253
7319
  },
7254
- "constraint.displayable.stacked_marks.default": {
7255
- fr: "Ce champ ne doit pas empiler plus de <strong>[max]</strong> signes sur un même caractère.",
7256
- en: "This field must not stack more than <strong>[max]</strong> marks on a single character.",
7320
+ // [sample] is the offending character with its marks — a stack is invisible
7321
+ // as a description and obvious as a sample.
7322
+ "constraint.displayable.stacked_marks.singular": {
7323
+ fr: "Ce champ contient un caractère qui empile plus de <strong>[max]</strong> signes : « [sample] ».",
7324
+ en: "This field contains a character stacking more than <strong>[max]</strong> marks: “[sample]”.",
7257
7325
  },
7258
- "constraint.displayable.invisible.default": {
7326
+ "constraint.displayable.stacked_marks.plural": {
7327
+ fr: "Ce champ contient [count] caractères qui empilent plus de <strong>[max]</strong> signes (tel que « [sample] »).",
7328
+ en: "This field contains [count] characters stacking more than <strong>[max]</strong> marks (such as “[sample]”).",
7329
+ },
7330
+ "constraint.displayable.invisible": {
7259
7331
  fr: "Ce champ doit contenir au moins un caractère visible.",
7260
7332
  en: "This field must contain at least one visible character.",
7261
7333
  },
7262
- "constraint.displayable.blank_lines.default": {
7334
+ "constraint.displayable.blank_lines": {
7263
7335
  fr: "Ce champ ne doit pas contenir plusieurs lignes vides consécutives.",
7264
7336
  en: "This field must not contain consecutive blank lines.",
7265
7337
  },
7338
+ "constraint.displayable.dangling_joiner": {
7339
+ fr: "Ce champ contient un caractère de liaison invisible qui ne relie rien.",
7340
+ en: "This field contains an invisible joiner that joins nothing.",
7341
+ },
7342
+ "constraint.no_emoji.default": {
7343
+ fr: "Ce champ ne doit pas contenir d'emoji.",
7344
+ en: "This field must not contain emoji.",
7345
+ },
7346
+ "constraint.max_line_breaks.default": {
7347
+ fr: "Ce champ ne doit pas contenir plus de [max] retour[s] à la ligne.",
7348
+ en: "This field must not contain more than [max] line break[s].",
7349
+ },
7266
7350
  "constraint.min_lower_letter.password.singular": {
7267
7351
  fr: "Ce mot de passe doit contenir au moins une lettre minuscule.",
7268
7352
  en: "This password must contain at least one lowercase letter.",
@@ -7329,35 +7413,42 @@ naviI18n.addAll({
7329
7413
  },
7330
7414
  });
7331
7415
 
7332
- // charGuard / maxLengthGuard callout messages
7416
+ // Character class and maxLengthGuard messages. The char class keys are
7417
+ // @jsenv/validity's own ("char_class.slug"), prefixed with "constraint." —
7418
+ // the same sentence refuses a keystroke in a callout and a whole value in a
7419
+ // constraint, so there is one key for both.
7333
7420
  naviI18n.addAll({
7334
7421
  // Preset-specific char messages — more informative than the generic fallback
7335
- "constraint.guard.number": {
7422
+ "constraint.char_class.numeric": {
7336
7423
  fr: "Ce champ ne peut contenir que des chiffres.",
7337
7424
  en: "This field can only contain digits.",
7338
7425
  },
7339
- "constraint.guard.alpha": {
7426
+ "constraint.char_class.alpha": {
7340
7427
  fr: "Ce champ ne peut contenir que des lettres.",
7341
7428
  en: "This field can only contain letters.",
7342
7429
  },
7343
- "constraint.guard.alphanumeric": {
7430
+ "constraint.char_class.alphanumeric": {
7344
7431
  fr: "Ce champ ne peut contenir que des lettres et des chiffres.",
7345
7432
  en: "This field can only contain letters and digits.",
7346
7433
  },
7347
- "constraint.guard.uppercase": {
7434
+ "constraint.char_class.uppercase": {
7348
7435
  fr: "Ce champ ne peut contenir que des lettres majuscules.",
7349
7436
  en: "This field can only contain uppercase letters.",
7350
7437
  },
7351
- "constraint.guard.hex": {
7438
+ "constraint.char_class.hex": {
7352
7439
  fr: "Ce champ ne peut contenir que des chiffres hexadécimaux (0-9, A-F).",
7353
7440
  en: "This field can only contain hexadecimal digits (0-9, A-F).",
7354
7441
  },
7355
- "constraint.guard.slug": {
7442
+ "constraint.char_class.slug": {
7356
7443
  fr: "Ce champ ne peut contenir que des lettres minuscules, des chiffres et des tirets.",
7357
7444
  en: "This field can only contain lowercase letters, digits, and hyphens.",
7358
7445
  },
7359
7446
  // Generic fallback for custom char classes and other presets (tel, card, postal, iban…)
7360
- "constraint.guard.chars": {
7447
+ "constraint.char_class.no_emoji": {
7448
+ fr: "Ce champ ne peut pas contenir d'emoji.",
7449
+ en: "This field cannot contain emoji.",
7450
+ },
7451
+ "constraint.char_class.default": {
7361
7452
  fr: "Ce champ ne peut contenir que les caractères autorisés.",
7362
7453
  en: "This field can only contain allowed characters.",
7363
7454
  },
@@ -8505,7 +8596,11 @@ const PATTERN_CONSTRAINT = {
8505
8596
  if (!valueAsString) {
8506
8597
  return null;
8507
8598
  }
8508
- const regex = new RegExp(`^(?:${pattern})$`);
8599
+ // The `u` flag is how the platform compiles this same attribute, and what
8600
+ // lets a pattern speak about characters: `\p{...}` is only recognized under
8601
+ // it, and a range covers whole code points rather than the two halves an
8602
+ // astral character is made of.
8603
+ const regex = new RegExp(`^(?:${pattern})$`, "u");
8509
8604
  if (regex.test(valueAsString)) {
8510
8605
  return null;
8511
8606
  }
@@ -10988,89 +11083,175 @@ const isFunctionButNotAnActionFunction = (action) => {
10988
11083
  };
10989
11084
 
10990
11085
  /**
10991
- * `data-displayable` the value must be something the layout can actually
10992
- * draw. Three shapes break a row, a card or a list even though every character
10993
- * taken alone is legitimate, so no character class can express them:
10994
- *
10995
- * - marks stacked on one base character ("zalgo"): a diacritic is a normal
10996
- * character a decomposed Vietnamese letter carries two, a vocalized Hebrew
10997
- * one three what is not normal is the count in a row. Thirty of them draw
10998
- * far above the line, over the row above.
10999
- * - a value that is not empty and yet shows nothing: only spaces, only marks,
11000
- * only format characters. An empty-looking line in the middle of a list
11001
- * reads as a bug.
11002
- * - blank lines in series: forty newlines make a card as tall as the screen.
11003
- *
11004
- * These are display rules, not app rules: they hold for every field, whatever
11005
- * it holds — which is why they ship here rather than being rewritten per app.
11006
- *
11007
- * Note what is deliberately NOT refused: U+200D (ZWJ) and U+200C (ZWNJ) are
11008
- * invisible characters, but the first assembles 👨‍👩‍👧 and 🏳️‍🌈 and the second
11009
- * separates two letters in Persian. Banning invisible characters outright
11010
- * would mean banning composed emoji. They only make a value fail here when
11011
- * nothing visible is left once they are removed.
11086
+ * Where navi meets @jsenv/validity.
11087
+ *
11088
+ * validity names a refusal with a key and its parameters rather than a
11089
+ * sentence, so that a field and a server can refuse in the same words in the
11090
+ * language of the person reading. navi is one of those two callers: it says the
11091
+ * sentence, in the browser, through `naviI18n`. The keys line up on purpose —
11092
+ * validity's `"single_space.start"` is navi's `"constraint.single_space.start"`
11093
+ * so overriding a message is looking up one key, not going through a
11094
+ * translation table.
11095
+ *
11096
+ * The other direction is `constraintFromValidityRule`: a rule an app wrote for
11097
+ * its server, worn by a control as a constraint.
11012
11098
  */
11013
11099
 
11014
11100
 
11015
- // Above what any writing system needs on one base character, far below what
11016
- // zalgo uses. Raise it with data-max-stacked-marks when a language needs more.
11017
- const DEFAULT_MAX_STACKED_MARKS = 5;
11018
-
11019
- // Everything that occupies no ink of its own: spaces, control and format
11020
- // characters, and combining marks (which draw on a base character, so a value
11021
- // made only of them has nothing to draw on).
11022
- const INK_LESS_REGEX = /[\p{White_Space}\p{Cc}\p{Cf}\p{M}]/gu;
11023
- // Two newlines are one blank line — a paragraph break; three are two.
11024
- const BLANK_LINES_REGEX = /\n[^\S\n]*\n[^\S\n]*\n/;
11101
+ /**
11102
+ * Turns a @jsenv/validity rule into a constraint a control can wear, so an app
11103
+ * rule written once — in the package its server reads too — is checked on both
11104
+ * sides instead of being written twice.
11105
+ *
11106
+ * @param {object} rule
11107
+ * `{ name, applyOn(ruleValue, value, ruleConfig) }`, the same object passed
11108
+ * to `createValidity({ rules })`.
11109
+ * @param {object} [ruleConfig]
11110
+ * What parameterizes the rule, under its own name — `{ maxWords: 40 }` for a
11111
+ * rule named `maxWords`. Pass `formatMessage` here to say the refusal through
11112
+ * the app's own i18n; without it the key is looked up in `naviI18n` under
11113
+ * `constraint.<key>`, and a rule answering with a finished sentence is shown
11114
+ * as-is.
11115
+ *
11116
+ * Call it once, at module level: a constraint rebuilt on every render is a new
11117
+ * object on every check.
11118
+ */
11119
+ const constraintFromValidityRule = (rule, ruleConfig = {}) => {
11120
+ const { formatMessage, ...ruleParams } = ruleConfig;
11121
+ return {
11122
+ name: rule.name,
11123
+ check: (field) => {
11124
+ const result = rule.applyOn(
11125
+ ruleParams[rule.name],
11126
+ field.uiState,
11127
+ ruleParams,
11128
+ );
11129
+ if (!result) {
11130
+ return null;
11131
+ }
11132
+ if (typeof result === "string") {
11133
+ return result;
11134
+ }
11135
+ if (formatMessage) {
11136
+ return formatMessage(result.key, result.params);
11137
+ }
11138
+ return naviI18nFromValidityMessage(result);
11139
+ },
11140
+ };
11141
+ };
11025
11142
 
11026
- const stackedMarksRegexCache = new Map();
11027
- const getStackedMarksRegex = (maxStackedMarks) => {
11028
- const fromCache = stackedMarksRegexCache.get(maxStackedMarks);
11029
- if (fromCache) {
11030
- return fromCache;
11143
+ const naviI18nFromValidityMessage = ({ key, params }) => {
11144
+ if (params && typeof params.max === "number") {
11145
+ // Lets a template pluralize on the bound it names: "[max] retour[s]".
11146
+ return naviI18n(`constraint.${key}`, {
11147
+ ...params,
11148
+ s: params.max > 1 ? "s" : "",
11149
+ });
11031
11150
  }
11032
- const regex = new RegExp(`\\p{M}{${maxStackedMarks + 1},}`, "u");
11033
- stackedMarksRegexCache.set(maxStackedMarks, regex);
11034
- return regex;
11151
+ return naviI18n(`constraint.${key}`, params);
11035
11152
  };
11036
11153
 
11154
+ /**
11155
+ * `data-displayable` — the value must be something the layout can actually
11156
+ * draw. What it refuses and why lives in @jsenv/validity's DISPLAYABLE_RULE:
11157
+ * these are display rules, not app rules, so a server re-checking the value
11158
+ * asks the exact same question and gets the exact same refusal.
11159
+ *
11160
+ * `data-max-stacked-marks` raises how many marks may stack on one base
11161
+ * character, for a language that needs more than the default.
11162
+ */
11163
+
11164
+
11037
11165
  const DISPLAYABLE_CONSTRAINT = {
11038
11166
  name: "displayable",
11039
11167
  messageAttribute: "data-displayable-message",
11040
11168
  check: (field) => {
11041
11169
  const displayable = field.controlHostProps["data-displayable"];
11042
- if (displayable === undefined) {
11170
+ if (!isConstraintAttributeOn(displayable)) {
11043
11171
  return null;
11044
11172
  }
11045
11173
  const valueAsString =
11046
11174
  field.uiState === undefined ? "" : String(field.uiState);
11047
- if (valueAsString === "") {
11048
- // An empty field is `required`'s business, not this one's.
11175
+ const maxStackedMarksAttribute =
11176
+ field.controlHostProps["data-max-stacked-marks"];
11177
+ const result = DISPLAYABLE_RULE.applyOn(true, valueAsString, {
11178
+ maxStackedMarks:
11179
+ maxStackedMarksAttribute === undefined
11180
+ ? undefined
11181
+ : parseInt(maxStackedMarksAttribute, 10),
11182
+ });
11183
+ if (!result) {
11049
11184
  return null;
11050
11185
  }
11186
+ return naviI18nFromValidityMessage(result);
11187
+ },
11188
+ };
11189
+ CONSTRAINT_ATTRIBUTE_SET.add("data-displayable");
11190
+ CONSTRAINT_ATTRIBUTE_SET.add("data-max-stacked-marks");
11051
11191
 
11052
- const maxStackedMarksAttribute =
11053
- field.controlHostProps["data-max-stacked-marks"];
11054
- const maxStackedMarks =
11055
- maxStackedMarksAttribute === undefined
11056
- ? DEFAULT_MAX_STACKED_MARKS
11057
- : parseInt(maxStackedMarksAttribute, 10);
11058
- if (getStackedMarksRegex(maxStackedMarks).test(valueAsString)) {
11059
- return naviI18n("constraint.displayable.stacked_marks.default", {
11060
- max: maxStackedMarks,
11061
- });
11192
+ /**
11193
+ * `data-max-line-breaks` — how many line breaks the value may hold. Counted in
11194
+ * breaks rather than in lines because how many lines a value renders as depends
11195
+ * on wrapping, which is the layout's answer, not the value's.
11196
+ *
11197
+ * The rule is @jsenv/validity's MAX_LINE_BREAKS_RULE — a textarea in the
11198
+ * browser and a server receiving the value both ask it.
11199
+ */
11200
+
11201
+
11202
+ const MAX_LINE_BREAKS_CONSTRAINT = {
11203
+ name: "max_line_breaks",
11204
+ messageAttribute: "data-max-line-breaks-message",
11205
+ check: (field) => {
11206
+ const maxLineBreaksAttribute =
11207
+ field.controlHostProps["data-max-line-breaks"];
11208
+ if (!isConstraintAttributeOn(maxLineBreaksAttribute)) {
11209
+ return null;
11062
11210
  }
11063
- if (valueAsString.replace(INK_LESS_REGEX, "") === "") {
11064
- return naviI18n("constraint.displayable.invisible.default");
11211
+ const maxLineBreaks = parseInt(maxLineBreaksAttribute, 10);
11212
+ if (isNaN(maxLineBreaks)) {
11213
+ return null;
11065
11214
  }
11066
- if (BLANK_LINES_REGEX.test(valueAsString)) {
11067
- return naviI18n("constraint.displayable.blank_lines.default");
11215
+ const valueAsString =
11216
+ field.uiState === undefined ? "" : String(field.uiState);
11217
+ const result = MAX_LINE_BREAKS_RULE.applyOn(maxLineBreaks, valueAsString);
11218
+ if (!result) {
11219
+ return null;
11068
11220
  }
11069
- return null;
11221
+ return naviI18nFromValidityMessage(result);
11070
11222
  },
11071
11223
  };
11072
- CONSTRAINT_ATTRIBUTE_SET.add("data-displayable");
11073
- CONSTRAINT_ATTRIBUTE_SET.add("data-max-stacked-marks");
11224
+ CONSTRAINT_ATTRIBUTE_SET.add("data-max-line-breaks");
11225
+
11226
+ /**
11227
+ * `data-no-emoji` — an app is free with emoji or it is not, and that is not the
11228
+ * layout's call: a row survives an emoji, a legal name, an identifier or a
11229
+ * title may still not want one. So it is its own switch rather than a part of
11230
+ * `data-displayable`.
11231
+ *
11232
+ * The rule is @jsenv/validity's NO_EMOJI_RULE. To refuse the keystroke instead
11233
+ * of the value, `charGuard="noEmoji"` is the same knowledge on the other side.
11234
+ */
11235
+
11236
+
11237
+ const NO_EMOJI_CONSTRAINT = {
11238
+ name: "no_emoji",
11239
+ messageAttribute: "data-no-emoji-message",
11240
+ check: (field) => {
11241
+ const noEmoji = field.controlHostProps["data-no-emoji"];
11242
+ if (!isConstraintAttributeOn(noEmoji)) {
11243
+ return null;
11244
+ }
11245
+ const valueAsString =
11246
+ field.uiState === undefined ? "" : String(field.uiState);
11247
+ const result = NO_EMOJI_RULE.applyOn(true, valueAsString);
11248
+ if (!result) {
11249
+ return null;
11250
+ }
11251
+ return naviI18nFromValidityMessage(result);
11252
+ },
11253
+ };
11254
+ CONSTRAINT_ATTRIBUTE_SET.add("data-no-emoji");
11074
11255
 
11075
11256
  const MIN_LOWER_LETTER_CONSTRAINT = {
11076
11257
  name: "min_lower_letter",
@@ -11341,30 +11522,28 @@ const SAME_AS_CONSTRAINT = {
11341
11522
  };
11342
11523
  CONSTRAINT_ATTRIBUTE_SET.add("data-same-as");
11343
11524
 
11525
+ /**
11526
+ * `data-single-space` — no leading or trailing space, never two in a row.
11527
+ * The rule itself is @jsenv/validity's SINGLE_SPACE_RULE, so a server checking
11528
+ * the value again refuses it for the same reason and in the same words.
11529
+ */
11530
+
11531
+
11344
11532
  const SINGLE_SPACE_CONSTRAINT = {
11345
11533
  name: "single_space",
11346
11534
  messageAttribute: "data-single-space-message",
11347
11535
  check: (field) => {
11348
11536
  const singleSpace = field.controlHostProps["data-single-space"];
11349
- if (singleSpace === undefined) {
11537
+ if (!isConstraintAttributeOn(singleSpace)) {
11350
11538
  return null;
11351
11539
  }
11352
-
11353
11540
  const valueAsString =
11354
11541
  field.uiState === undefined ? "" : String(field.uiState);
11355
- const hasLeadingSpace = valueAsString.startsWith(" ");
11356
- const hasTrailingSpace = valueAsString.endsWith(" ");
11357
- const hasDoubleSpace = valueAsString.includes(" ");
11358
- if (!hasLeadingSpace && !hasTrailingSpace && !hasDoubleSpace) {
11542
+ const result = SINGLE_SPACE_RULE.applyOn(true, valueAsString);
11543
+ if (!result) {
11359
11544
  return null;
11360
11545
  }
11361
- if (hasLeadingSpace) {
11362
- return naviI18n("constraint.single_space.start.default");
11363
- }
11364
- if (hasTrailingSpace) {
11365
- return naviI18n("constraint.single_space.end.default");
11366
- }
11367
- return naviI18n("constraint.single_space.consecutive.default");
11546
+ return naviI18nFromValidityMessage(result);
11368
11547
  },
11369
11548
  };
11370
11549
  CONSTRAINT_ATTRIBUTE_SET.add("data-single-space");
@@ -11490,6 +11669,8 @@ const NAVI_CONSTRAINT_SET = new Set([
11490
11669
  MIN_SPECIAL_CHAR_CONSTRAINT,
11491
11670
  SINGLE_SPACE_CONSTRAINT,
11492
11671
  DISPLAYABLE_CONSTRAINT,
11672
+ MAX_LINE_BREAKS_CONSTRAINT,
11673
+ NO_EMOJI_CONSTRAINT,
11493
11674
  MIN_DIGIT_CONSTRAINT,
11494
11675
  MIN_UPPER_LETTER_CONSTRAINT,
11495
11676
  MIN_LOWER_LETTER_CONSTRAINT,
@@ -11661,6 +11842,13 @@ const createControlValidation = (
11661
11842
  }
11662
11843
  }
11663
11844
 
11845
+ // Several constraints can fail at once and only one sentence is shown —
11846
+ // naming it here lets whoever draws its own summary of the failures say the
11847
+ // same thing as the callout instead of picking a second one.
11848
+ newConstraintValidityState.reported = failedConstraintInfo
11849
+ ? failedConstraintInfo.name
11850
+ : null;
11851
+
11664
11852
  const activeFailedConstraintInfo = failedConstraintInfo;
11665
11853
  if (activeFailedConstraintInfo) {
11666
11854
  const titleLess = controller.controlHostProps.title === undefined;
@@ -31094,59 +31282,20 @@ const subscribeToControlState = (controlId, callback) => {
31094
31282
 
31095
31283
  const FormContext = createContext();
31096
31284
 
31097
- /**
31098
- * Named presets for the `charGuard` prop.
31099
- * Each value is a regex character class (including the [ ] delimiters).
31100
- */
31101
- const CHAR_CLASS_PRESETS = {
31102
- numeric: "[0-9]", // digits only
31103
- alpha: "[A-Za-z]", // letters only
31104
- alphanumeric: "[0-9A-Za-z]", // letters and digits
31105
- decimal: "[-0-9.,]", // digits, minus, dot, comma
31106
- uppercase: "[A-Z]", // uppercase letters only
31107
- tel: "[-0-9+() ]", // phone: digits, +, -, parens, space
31108
- email: "[a-zA-Z0-9._%+@-]", // email characters
31109
- card: "[0-9 ]", // credit card: digits and spaces
31110
- hex: "[0-9A-Fa-f]", // hexadecimal digits
31111
- pin: "[0-9]", // numeric PIN
31112
- postal: "[0-9A-Za-z -]", // postal code (FR, UK, US)
31113
- iban: "[0-9A-Z]", // IBAN: uppercase and digits
31114
- slug: "[a-z0-9-]", // URL slug
31115
- };
31116
-
31117
- // Specific i18n keys per preset — more informative than the generic fallback
31118
- const MESSAGE_KEY_FROM_PRESET = {
31119
- numeric: "constraint.guard.number",
31120
- pin: "constraint.guard.number",
31121
- alpha: "constraint.guard.alpha",
31122
- alphanumeric: "constraint.guard.alphanumeric",
31123
- uppercase: "constraint.guard.uppercase",
31124
- hex: "constraint.guard.hex",
31125
- slug: "constraint.guard.slug",
31126
- // tel, card, postal, iban, custom → generic fallback
31127
- };
31128
-
31129
- /**
31130
- * Returns the regex character class for a preset name, or the raw value as-is
31131
- * if it's not a known preset (allows custom classes like "[A-Z0-9_]").
31132
- */
31133
- const resolveCharClass = (value) => {
31134
- if (!value) return null;
31135
- return CHAR_CLASS_PRESETS[value] ?? value;
31136
- };
31137
-
31138
- /**
31139
- * Returns the i18n key for the char guard rejection message.
31140
- * Falls back to the generic "constraint.guard.chars" for custom classes and
31141
- * presets without a specific message.
31142
- */
31143
- const getCharGuardMessageKey = (value) => {
31144
- return MESSAGE_KEY_FROM_PRESET[value] ?? "constraint.guard.chars";
31145
- };
31146
-
31147
31285
  /**
31148
31286
  * Input guard — enforces character and length constraints during typing, paste,
31149
- * and external value sets.
31287
+ * and external value sets. What a character class holds and which sentence
31288
+ * refuses it comes from @jsenv/validity; the guard is what only a field can do,
31289
+ * blocking the keystroke before the value exists.
31290
+ *
31291
+ * The guard answers for the GESTURE, never for what the control already holds.
31292
+ * A value can arrive already outside the class or already too long — a
31293
+ * `defaultValue`, a signal, a value written from elsewhere — and refusing the
31294
+ * next keystroke over it would blame the person for a character they did not
31295
+ * type. So a change is refused only when it makes the value worse: one more
31296
+ * character outside the class, or one more character over the limit. What is
31297
+ * already there is the `charClass`/`maxLength` constraint's business, and it
31298
+ * says so at submit.
31150
31299
  *
31151
31300
  * The guard owns a single callout token (shared across all rejection reasons) so
31152
31301
  * successive rejections update the same callout rather than stacking.
@@ -31164,13 +31313,6 @@ const isTypingIntent = (e) =>
31164
31313
 
31165
31314
  const s = (n) => (n > 1 ? "s" : "");
31166
31315
 
31167
- // The `u` flag is what lets a char class speak about characters: `\p{...}` is
31168
- // only recognized under it, and a range covers whole code points instead of
31169
- // the two halves an astral character (an emoji) is made of.
31170
- const compileCharClass = (charClass) => new RegExp(charClass, "u");
31171
- const compileCharClassAnchored = (charClass) =>
31172
- new RegExp(`^(?:${charClass})*$`, "u");
31173
-
31174
31316
  // Keydown: block only single printable characters that don't match the class.
31175
31317
  // Multi-character key names (Delete, ArrowLeft…) are always allowed.
31176
31318
  const getInvalidCharMessage = (char, { charClass, messageKey }) => {
@@ -31180,7 +31322,7 @@ const getInvalidCharMessage = (char, { charClass, messageKey }) => {
31180
31322
  return null;
31181
31323
  }
31182
31324
  if (compileCharClass(charClass).test(char)) return null;
31183
- return naviI18n(messageKey);
31325
+ return naviI18nFromValidityMessage({ key: messageKey });
31184
31326
  };
31185
31327
 
31186
31328
  // Keydown: block when inserting one char would exceed maxLength.
@@ -31196,18 +31338,41 @@ const getMaxLengthInsertionMessage = (el, { maxLength }) => {
31196
31338
  });
31197
31339
  };
31198
31340
 
31199
- // Paste / set: block when value contains disallowed chars.
31200
- const getInvalidCharsMessage = (uiState, { charClass, messageKey }) => {
31341
+ const countCharsOutsideClass = (str, charClass) => {
31342
+ const regex = compileCharClass(charClass);
31343
+ let count = 0;
31344
+ for (const char of str) {
31345
+ if (!regex.test(char)) {
31346
+ count++;
31347
+ }
31348
+ }
31349
+ return count;
31350
+ };
31351
+
31352
+ // Paste / set: block when the gesture brings in a character the class refuses.
31353
+ const getInvalidCharsMessage = (
31354
+ uiState,
31355
+ { charClass, messageKey, uiStateNow },
31356
+ ) => {
31201
31357
  const str = uiState === undefined ? "" : String(uiState);
31202
31358
  if (compileCharClassAnchored(charClass).test(str)) return null;
31203
- return naviI18n(messageKey);
31359
+ const strNow = uiStateNow === undefined ? "" : String(uiStateNow);
31360
+ if (
31361
+ countCharsOutsideClass(str, charClass) <=
31362
+ countCharsOutsideClass(strNow, charClass)
31363
+ ) {
31364
+ return null;
31365
+ }
31366
+ return naviI18nFromValidityMessage({ key: messageKey });
31204
31367
  };
31205
31368
 
31206
- // Paste / set: truncate when value exceeds maxLength.
31207
- const getLengthOverflowResult = (uiState, { maxLength }) => {
31369
+ // Paste / set: truncate what the gesture adds beyond the limit.
31370
+ const getLengthOverflowResult = (uiState, { maxLength, uiStateNow }) => {
31208
31371
  if (maxLength === undefined) return null;
31209
31372
  const str = uiState === undefined ? "" : String(uiState);
31210
31373
  if (str.length <= maxLength) return null;
31374
+ const strNow = uiStateNow === undefined ? "" : String(uiStateNow);
31375
+ if (str.length <= strNow.length) return null;
31211
31376
  return {
31212
31377
  fixedValue: str.slice(0, maxLength),
31213
31378
  message: naviI18n("constraint.guard.max_length.value", {
@@ -31246,7 +31411,7 @@ const createControlGuard = (controller) => {
31246
31411
 
31247
31412
  if (charGuard) {
31248
31413
  const charClass = resolveCharClass(charGuard);
31249
- const messageKey = getCharGuardMessageKey(charGuard);
31414
+ const messageKey = getCharClassMessageKey(charGuard);
31250
31415
  const charMsg = getInvalidCharMessage(e.key, { charClass, messageKey });
31251
31416
  if (charMsg) {
31252
31417
  show(charMsg, e);
@@ -31276,13 +31441,17 @@ const createControlGuard = (controller) => {
31276
31441
  */
31277
31442
  const checkUIState = (uiState, e) => {
31278
31443
  const { charGuard, maxLengthGuard } = controller.props;
31444
+ // What the control holds right now — setUIState has not written the new
31445
+ // value yet, and the paste path computes it without applying it.
31446
+ const uiStateNow = controller.uiState;
31279
31447
 
31280
31448
  if (charGuard) {
31281
31449
  const charClass = resolveCharClass(charGuard);
31282
- const messageKey = getCharGuardMessageKey(charGuard);
31450
+ const messageKey = getCharClassMessageKey(charGuard);
31283
31451
  const charsMsg = getInvalidCharsMessage(uiState, {
31284
31452
  charClass,
31285
31453
  messageKey,
31454
+ uiStateNow,
31286
31455
  });
31287
31456
  if (charsMsg) {
31288
31457
  show(charsMsg, e);
@@ -31293,6 +31462,7 @@ const createControlGuard = (controller) => {
31293
31462
  if (maxLengthGuard !== undefined) {
31294
31463
  const lengthResult = getLengthOverflowResult(uiState, {
31295
31464
  maxLength: maxLengthGuard,
31465
+ uiStateNow,
31296
31466
  });
31297
31467
  if (lengthResult) {
31298
31468
  show(lengthResult.message, e);
@@ -35320,13 +35490,15 @@ const splitControlProps = props => {
35320
35490
  };
35321
35491
  const controlRootProps = {};
35322
35492
  for (const key of Object.keys(props)) {
35323
- if (CONTROL_PROP_SET.has(key)) {
35324
- if (CONTROL_ATTRIBUTE_SET.has(key)) {
35325
- controlHostProps[key] = props[key];
35326
- }
35327
- } else {
35328
- controlRootProps[key] = props[key];
35493
+ const attributeName = controlAttributeFromProp(key);
35494
+ if (attributeName) {
35495
+ controlHostProps[attributeName] = props[key];
35496
+ continue;
35497
+ }
35498
+ if (isControlProp(key)) {
35499
+ continue;
35329
35500
  }
35501
+ controlRootProps[key] = props[key];
35330
35502
  }
35331
35503
  return [controlRootProps, controlHostProps];
35332
35504
  };
@@ -42914,7 +43086,16 @@ const createToggleEvent = open => {
42914
43086
  return toggleEvent;
42915
43087
  };
42916
43088
 
42917
- const DEFAULT_VALIDITY_STATE = { valid: true };
43089
+ const DEFAULT_VALIDITY_STATE = { valid: true, reported: null };
43090
+
43091
+ /**
43092
+ * The control's constraint validity, re-read whenever it changes:
43093
+ * `{ valid, reported, [constraintName]: null | failureInfo }`.
43094
+ *
43095
+ * `reported` names the constraint whose message the callout shows — the one
43096
+ * navi picks when several fail at once. A failure info carries `messageString`,
43097
+ * the sentence itself.
43098
+ */
42918
43099
  const useConstraintValidityState = (ref) => {
42919
43100
  const checkValue = () => {
42920
43101
  const element = ref.current;
@@ -46592,10 +46773,13 @@ installImportMetaCssBuild(import.meta);/**
46592
46773
  * "postal" → postal code (digits, letters, space, hyphen)
46593
46774
  * "iban" → IBAN (uppercase and digits)
46594
46775
  * "slug" → URL slug (lowercase, digits, hyphens)
46776
+ * "noEmoji" → anything but an emoji
46595
46777
  * "[A-Z0-9]" → any custom regex character class, compiled with the `u`
46596
46778
  * flag: `\p{...}` is available, and an emoji counts as one
46597
46779
  * character rather than two halves.
46598
46780
  * inputMode and pattern are auto-derived from the preset when not explicitly set.
46781
+ * The presets come from @jsenv/validity, so the same name names the class a
46782
+ * server checks the value against (see docs/field_validation.md).
46599
46783
  *
46600
46784
  * - maxLengthGuard — combines maxLength + overflow guard in one prop.
46601
46785
  * Blocks keydown when the limit is reached; truncates on paste/set with an info callout.
@@ -67776,11 +67960,18 @@ const css$r = /* css */`
67776
67960
  99999 fallback means "no cap" without needing a conditional rule. */
67777
67961
  max-height: calc(var(--textarea-max-rows, 99999) * 1lh);
67778
67962
  field-sizing: content;
67779
- /* Explicit, never normal: minRows/maxRows are lengths in lh, and with
67780
- line-height normal the lh unit resolves to a theoretical value that
67781
- does not match the real rendered line the box then jumps by a few
67782
- pixels the moment the first character replaces the theory with a real
67783
- line. One number for both keeps every row count exact. */
67963
+ /* Explicit, never normal, for two independent reasons.
67964
+ minRows/maxRows are lengths in lh, and with line-height normal the lh
67965
+ unit resolves to a theoretical value that does not match the real
67966
+ rendered line the box then jumps by a few pixels the moment the first
67967
+ character replaces the theory with a real line.
67968
+ And a line box under "normal" takes the height of the tallest font it
67969
+ holds, so the one line carrying an emoji stands taller than the ones
67970
+ around it — here, where the text is typed and no glyph can be wrapped
67971
+ the way emojiAsIcon wraps one, the line height is the only lever.
67972
+ 1.5 is also tall enough to contain an emoji's own box, so it is not
67973
+ clipped either; a tighter value would keep the rows even and cut the
67974
+ glyph. See docs/typography.md. */
67784
67975
  line-height: 1.5;
67785
67976
  /* The control grows itself; resizable below hands the handle back. */
67786
67977
  resize: none;
@@ -79046,5 +79237,5 @@ const UserSvg = () => jsx("svg", {
79046
79237
  })
79047
79238
  });
79048
79239
 
79049
- export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, BadgeList, Binder, Box, Button, ButtonCopyToClipboard, CalloutStatusIcon, Caption, CardLayout, CheckSvg, CheckboxGroup, CloseSvg, Code, Col, Colgroup, Color, ConstructionSvg, ControlGroup, DaySpin, Details, Dialog, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, Expandable, EyeClosedSvg, EyeSvg, Field, FixedBar, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, InfoSvg, Input, InputDuration, Interpolate, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, LinkCurrentSvg, List, ListItem, ListItemGroup, ListItems, Loading, LoadingDotsSvg, LoadingIndicator, LoadingIndicatorFluid, LoadingOutline, MessageBox, Meter, Nav, NaviDebug, NumberSpin, Paragraph, Picker, Popover, Popup, Quantity, RadioGroup, Route, RouteTransitionArea, RouteTravel, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, Select, SelectableInput, SelectionContext, Separator, SettingsSvg, SidePanel, Slide, SlideContainer, Spin, SpinGroup, SplitButton, StarSvg, Step, StepList, SummaryMarker, Svg, Table, TableCell, Tbody, Text, TextBox, Textarea, TextareaCharCount, Thead, Time, TimeRangeSpin, TimeRangeWheel, TimeSpin, TimeWheel, Title, Tr, UITransition, Unit, UserSvg, ViewportLayout, Wheel, WheelGroup, WheelItem, actionRunEffect, anyMatchingRouteSignal, applySearch, arraySignalMembership, canNavBackSignal, canNavForwardSignal, coarsePointerSignal, compareTwoJsValues, createAction, createAvailableConstraint, createI18n, createRequestCanceller, createSearch, createSelectionKeyboardShortcuts, createSlot, defineInteractionDetector, defineRouteDefaultTransition, defineRouteTransition, detectHorizontalOverflow, enableDebugActions, enableDebugOnDocumentLoading, ensureDocumentStartViewTransition, errorIsDisplayed, filterTableSelection, formatDatetime, formatDay, formatDayRelative, formatMonth, formatNumber, formatTime, formatTimeRelative, getNowHours, getNowHoursRoundedToStep, interpolateText, isCellSelected, isColumnSelected, isRowSelected, isScrolling, isToday, languagesSignal, localStorageSignal, markErrorAsDisplayedBy, moveArrayItemByIndex, navBack, navForward, navIntegratedVia, navTo, naviI18n, openCallout, rawUrlPart, registerGlobalConstraint, reload, renderEmojiAsIcon, rerunActions, resource, route, routeAction, scrollActivitySignal, setBaseUrl, setPreferredLanguage, setSupportedLanguages, setUrlTargetOptions, setupRoutes, smallTouchScreenSignal, stateSignal, stopLoad, stringifyTableSelectionValue, swapArrayItemByIndex, syncOwnedResourceToSignals, syncResourceToSignals, triggerNaviCommand, updateActions, useActionStatus, useArraySignalMembership, useAsyncData, useCalloutElement, useCalloutRequestClose, useCanNavBack, useCanNavForward, useCancelPrevious, useCellGridFromRows, useConstraintValidityState, useDependenciesDiff, useDisplayedLayoutEffect, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useInputGroup, useKeyboardShortcuts, useNavState, useOrderedColumns, usePopupMode, useRouteStatus, useRunOnMount, useSearchText, useSelectableElement, useSelectionController, useSignalSync, useSlideValue, useStateArray, useTitleLevel, useUrlSearchParam, useUrlTargetId, valueInLocalStorage, windowWidthSignal };
79240
+ export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, BadgeList, Binder, Box, Button, ButtonCopyToClipboard, CalloutStatusIcon, Caption, CardLayout, CheckSvg, CheckboxGroup, CloseSvg, Code, Col, Colgroup, Color, ConstructionSvg, ControlGroup, DaySpin, Details, Dialog, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, Expandable, EyeClosedSvg, EyeSvg, Field, FixedBar, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, InfoSvg, Input, InputDuration, Interpolate, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, LinkCurrentSvg, List, ListItem, ListItemGroup, ListItems, Loading, LoadingDotsSvg, LoadingIndicator, LoadingIndicatorFluid, LoadingOutline, MessageBox, Meter, Nav, NaviDebug, NumberSpin, Paragraph, Picker, Popover, Popup, Quantity, RadioGroup, Route, RouteTransitionArea, RouteTravel, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, Select, SelectableInput, SelectionContext, Separator, SettingsSvg, SidePanel, Slide, SlideContainer, Spin, SpinGroup, SplitButton, StarSvg, Step, StepList, SummaryMarker, Svg, Table, TableCell, Tbody, Text, TextBox, Textarea, TextareaCharCount, Thead, Time, TimeRangeSpin, TimeRangeWheel, TimeSpin, TimeWheel, Title, Tr, UITransition, Unit, UserSvg, ViewportLayout, Wheel, WheelGroup, WheelItem, actionRunEffect, anyMatchingRouteSignal, applySearch, arraySignalMembership, canNavBackSignal, canNavForwardSignal, coarsePointerSignal, compareTwoJsValues, constraintFromValidityRule, createAction, createAvailableConstraint, createI18n, createRequestCanceller, createSearch, createSelectionKeyboardShortcuts, createSlot, defineInteractionDetector, defineRouteDefaultTransition, defineRouteTransition, detectHorizontalOverflow, enableDebugActions, enableDebugOnDocumentLoading, ensureDocumentStartViewTransition, errorIsDisplayed, filterTableSelection, formatDatetime, formatDay, formatDayRelative, formatMonth, formatNumber, formatTime, formatTimeRelative, getNowHours, getNowHoursRoundedToStep, interpolateText, isCellSelected, isColumnSelected, isRowSelected, isScrolling, isToday, languagesSignal, localStorageSignal, markErrorAsDisplayedBy, moveArrayItemByIndex, navBack, navForward, navIntegratedVia, navTo, naviI18n, openCallout, rawUrlPart, registerGlobalConstraint, reload, renderEmojiAsIcon, rerunActions, resource, route, routeAction, scrollActivitySignal, setBaseUrl, setPreferredLanguage, setSupportedLanguages, setUrlTargetOptions, setupRoutes, smallTouchScreenSignal, stateSignal, stopLoad, stringifyTableSelectionValue, swapArrayItemByIndex, syncOwnedResourceToSignals, syncResourceToSignals, triggerNaviCommand, updateActions, useActionStatus, useArraySignalMembership, useAsyncData, useCalloutElement, useCalloutRequestClose, useCanNavBack, useCanNavForward, useCancelPrevious, useCellGridFromRows, useConstraintValidityState, useDependenciesDiff, useDisplayedLayoutEffect, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useInputGroup, useKeyboardShortcuts, useNavState, useOrderedColumns, usePopupMode, useRouteStatus, useRunOnMount, useSearchText, useSelectableElement, useSelectionController, useSignalSync, useSlideValue, useStateArray, useTitleLevel, useUrlSearchParam, useUrlTargetId, valueInLocalStorage, windowWidthSignal };
79050
79241
  //# sourceMappingURL=jsenv_navi.js.map