@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.
- package/dist/jsenv_navi.js +364 -173
- package/dist/jsenv_navi.js.map +24 -20
- package/docs/AI_INSTRUCTIONS.md +16 -2
- package/docs/error_handling.md +3 -0
- package/docs/field_validation.md +193 -0
- package/docs/typography.md +14 -0
- package/package.json +2 -2
package/dist/jsenv_navi.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
|
|
7255
|
-
|
|
7256
|
-
|
|
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.
|
|
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
|
|
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
|
-
//
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
*
|
|
10992
|
-
*
|
|
10993
|
-
*
|
|
10994
|
-
*
|
|
10995
|
-
*
|
|
10996
|
-
*
|
|
10997
|
-
*
|
|
10998
|
-
*
|
|
10999
|
-
*
|
|
11000
|
-
*
|
|
11001
|
-
*
|
|
11002
|
-
*
|
|
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
|
-
|
|
11016
|
-
|
|
11017
|
-
|
|
11018
|
-
|
|
11019
|
-
|
|
11020
|
-
|
|
11021
|
-
|
|
11022
|
-
|
|
11023
|
-
|
|
11024
|
-
|
|
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
|
|
11027
|
-
|
|
11028
|
-
|
|
11029
|
-
|
|
11030
|
-
|
|
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
|
-
|
|
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
|
|
11170
|
+
if (!isConstraintAttributeOn(displayable)) {
|
|
11043
11171
|
return null;
|
|
11044
11172
|
}
|
|
11045
11173
|
const valueAsString =
|
|
11046
11174
|
field.uiState === undefined ? "" : String(field.uiState);
|
|
11047
|
-
|
|
11048
|
-
|
|
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
|
-
|
|
11053
|
-
|
|
11054
|
-
|
|
11055
|
-
|
|
11056
|
-
|
|
11057
|
-
|
|
11058
|
-
|
|
11059
|
-
|
|
11060
|
-
|
|
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
|
-
|
|
11064
|
-
|
|
11211
|
+
const maxLineBreaks = parseInt(maxLineBreaksAttribute, 10);
|
|
11212
|
+
if (isNaN(maxLineBreaks)) {
|
|
11213
|
+
return null;
|
|
11065
11214
|
}
|
|
11066
|
-
|
|
11067
|
-
|
|
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
|
|
11221
|
+
return naviI18nFromValidityMessage(result);
|
|
11070
11222
|
},
|
|
11071
11223
|
};
|
|
11072
|
-
CONSTRAINT_ATTRIBUTE_SET.add("data-
|
|
11073
|
-
|
|
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
|
|
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
|
|
11356
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
31200
|
-
const
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
35324
|
-
|
|
35325
|
-
|
|
35326
|
-
|
|
35327
|
-
}
|
|
35328
|
-
|
|
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
|
|
67780
|
-
|
|
67781
|
-
|
|
67782
|
-
|
|
67783
|
-
|
|
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
|