@jsenv/navi 0.29.27 → 0.29.28
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/README.md +4 -0
- package/dist/jsenv_navi.js +162 -86
- package/dist/jsenv_navi.js.map +10 -10
- package/docs/AI_INSTRUCTIONS.md +10 -0
- package/docs/i18n.md +183 -0
- package/docs/resource.md +11 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -29,6 +29,10 @@ between related actions. Parent/child relations are first-class — `.one`,
|
|
|
29
29
|
|
|
30
30
|
**`Text`** and related components (`Title`, `Paragraph`, `Code`, `Caption`) handle typography consistently across the app.
|
|
31
31
|
|
|
32
|
+
## Texts & i18n
|
|
33
|
+
|
|
34
|
+
`interpolateText`/`Interpolate` keep a sentence containing values readable as one string, and `createI18n` gives the app a single place holding its wording — worth it even with one language. `naviI18n` is where navi's own texts (validation messages, button labels, relative time) can be overridden or translated. See [docs/i18n.md](./docs/i18n.md).
|
|
35
|
+
|
|
32
36
|
## Icons
|
|
33
37
|
|
|
34
38
|
Icons are a piece that is often missing or painful in web projects. The `Icon` component makes icons behave like text — they scale with font size, inherit color, and align naturally in any layout. No sizing hacks, no SVG wrangling.
|
package/dist/jsenv_navi.js
CHANGED
|
@@ -167,17 +167,39 @@ const requestConfirmation = async ({ message, content, anchor }) => {
|
|
|
167
167
|
};
|
|
168
168
|
|
|
169
169
|
/**
|
|
170
|
-
* Interpolates a template string, replacing [key] placeholders with values.
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
170
|
+
* Interpolates a template string, replacing `[key]` placeholders with values.
|
|
171
|
+
*
|
|
172
|
+
* Usable on its own — no i18n instance required — whenever a sentence should
|
|
173
|
+
* stay readable as one string instead of being cut into JSX expressions or
|
|
174
|
+
* concatenations. `<Interpolate>` is the JSX form of this function, and
|
|
175
|
+
* `createI18n` runs every translation through it. See `docs/i18n.md`.
|
|
174
176
|
*
|
|
175
177
|
* `[]` was chosen as the placeholder delimiter (rather than `{}` or `{{}}`)
|
|
176
178
|
* because it does not conflict with JSX syntax, JavaScript template literals,
|
|
177
179
|
* or common punctuation in translated strings.
|
|
178
180
|
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
+
* @param {string} template
|
|
182
|
+
* e.g. `"Hello [name], you have [count] messages"`. A non-string is returned
|
|
183
|
+
* untouched, as is any template when `replacements` is missing.
|
|
184
|
+
* @param {object} [replacements]
|
|
185
|
+
* Values keyed by placeholder name. A key can be:
|
|
186
|
+
* - a direct name — `[name]` ← `{ name: "Alice" }`
|
|
187
|
+
* - a dot-path — `[item.label]` ← `{ item: { label: "Book" } }` (a literal
|
|
188
|
+
* `"item.label"` key wins over the path)
|
|
189
|
+
*
|
|
190
|
+
* A value that is a function is called at that point, so an expensive or
|
|
191
|
+
* lazily-known replacement is only computed when the placeholder is actually
|
|
192
|
+
* present in this language's template.
|
|
193
|
+
*
|
|
194
|
+
* A placeholder with no matching value is left in the output as-is
|
|
195
|
+
* (`"[name]"`), making the gap visible rather than silently empty.
|
|
196
|
+
* @param {object} [options]
|
|
197
|
+
* @param {boolean} [options.allowJsx=false]
|
|
198
|
+
* Allow VNode replacements (what `<Interpolate>` passes). Without it, a VNode
|
|
199
|
+
* value warns and is coerced to a string.
|
|
200
|
+
* @returns {string|import("preact").VNode}
|
|
201
|
+
* A plain string when every replacement is a string, a Preact fragment when
|
|
202
|
+
* at least one VNode was interpolated with `allowJsx`.
|
|
181
203
|
*/
|
|
182
204
|
const interpolateText = (
|
|
183
205
|
template,
|
|
@@ -288,8 +310,7 @@ if (typeof window !== "undefined") {
|
|
|
288
310
|
* because that happens to be the browser's or the user's own preference.
|
|
289
311
|
*
|
|
290
312
|
* `null` (the default) means no restriction at all: every language the
|
|
291
|
-
* browser/user prefers is allowed through
|
|
292
|
-
* unrestricted behavior.
|
|
313
|
+
* browser/user prefers is allowed through.
|
|
293
314
|
*/
|
|
294
315
|
const supportedLanguagesSignal = signal(null);
|
|
295
316
|
|
|
@@ -376,30 +397,45 @@ const languagesSignal = computed(() => {
|
|
|
376
397
|
});
|
|
377
398
|
|
|
378
399
|
/**
|
|
379
|
-
* Creates a lightweight i18n instance
|
|
400
|
+
* Creates a lightweight i18n instance: a central place where an app declares
|
|
401
|
+
* its texts once and reads them back translated into the active language.
|
|
402
|
+
*
|
|
403
|
+
* Worth using even in a single-language app — one registry beats strings
|
|
404
|
+
* scattered across components, and adding a second language later becomes a
|
|
405
|
+
* data change instead of a refactor. See `docs/i18n.md` for how to choose
|
|
406
|
+
* between the two key styles below and how this relates to `naviI18n`.
|
|
380
407
|
*
|
|
381
408
|
* @param {object} [options]
|
|
382
409
|
* @param {string} [options.keyLang]
|
|
383
410
|
* When set, each key also serves as its own translation for `keyLang`.
|
|
384
|
-
* This allows writing keys directly in that language (typically
|
|
385
|
-
* only other languages need
|
|
411
|
+
* This allows writing keys directly in that language (typically the language
|
|
412
|
+
* the app is written in) so only *other* languages need registering:
|
|
386
413
|
*
|
|
387
414
|
* ```js
|
|
388
415
|
* const i18n = createI18n({ keyLang: "en" });
|
|
389
416
|
* i18n.add("Hello [name]!", { fr: "Bonjour [name] !" });
|
|
390
|
-
* i18n("Hello [name]!", { name: "Alice" }); // "Hello Alice!"
|
|
391
|
-
* i18n("Hello [name]!", { name: "Alice" }); // "Bonjour Alice !"
|
|
417
|
+
* i18n("Hello [name]!", { name: "Alice" }, { lang: "en" }); // "Hello Alice!"
|
|
418
|
+
* i18n("Hello [name]!", { name: "Alice" }, { lang: "fr" }); // "Bonjour Alice !"
|
|
392
419
|
* ```
|
|
393
420
|
*
|
|
394
|
-
*
|
|
395
|
-
*
|
|
421
|
+
* `keyLang` only applies to keys passed to `add()`/`addAll()`; a key never
|
|
422
|
+
* registered stays opaque and comes back as-is.
|
|
423
|
+
*
|
|
424
|
+
* Without `keyLang`, keys are opaque identifiers and every language
|
|
425
|
+
* (including the one the app was written in) must be registered explicitly:
|
|
396
426
|
*
|
|
397
427
|
* ```js
|
|
398
428
|
* const i18n = createI18n();
|
|
399
429
|
* i18n.add("greeting", { en: "Hello [name]!", fr: "Bonjour [name] !" });
|
|
400
|
-
* i18n("greeting", { name: "Alice" }); // "Hello Alice!"
|
|
430
|
+
* i18n("greeting", { name: "Alice" }, { lang: "en" }); // "Hello Alice!"
|
|
401
431
|
* ```
|
|
402
432
|
*
|
|
433
|
+
* @param {string} [options.fallbackLang]
|
|
434
|
+
* Language consulted when the active language has no translation for a key
|
|
435
|
+
* — per key, not per language: a partially translated language falls through
|
|
436
|
+
* to `fallbackLang` only for the keys it is missing. Without it, a missing
|
|
437
|
+
* translation returns the key itself.
|
|
438
|
+
*
|
|
403
439
|
* @param {string|string[]} [options.runtimeLang]
|
|
404
440
|
* The active language (BCP 47 tag or ordered array of tags) — named
|
|
405
441
|
* "runtime" rather than "system" because there is no actual access to the
|
|
@@ -413,7 +449,7 @@ const languagesSignal = computed(() => {
|
|
|
413
449
|
*
|
|
414
450
|
* ---
|
|
415
451
|
*
|
|
416
|
-
* ##
|
|
452
|
+
* ## Registration
|
|
417
453
|
*
|
|
418
454
|
* **`i18n.add(key, { lang: "translation" })`** — one key, multiple languages.
|
|
419
455
|
*
|
|
@@ -422,18 +458,31 @@ const languagesSignal = computed(() => {
|
|
|
422
458
|
* **`i18n.addLangKeys(lang, { key: "translation", ... })`** — full language pack
|
|
423
459
|
* (useful when loading a JSON translation file).
|
|
424
460
|
*
|
|
461
|
+
* All three accumulate: registering a key that already exists overwrites that
|
|
462
|
+
* one key and leaves the rest of the language untouched. This is what lets an
|
|
463
|
+
* app override a single built-in navi text without redeclaring the others.
|
|
464
|
+
*
|
|
425
465
|
* A regional variant (e.g. `"fr-CA"`) automatically inherits all keys from its
|
|
426
466
|
* parent (`"fr"`) that it does not explicitly override:
|
|
427
467
|
* ```js
|
|
428
468
|
* i18n.addLangKeys("fr", { hello: "Bonjour !" });
|
|
429
469
|
* i18n.addLangKeys("fr-CA", { hello: "Allo !" }); // other "fr" keys inherited
|
|
430
470
|
* ```
|
|
471
|
+
* Inheritance is resolved at registration time, so register the parent first.
|
|
431
472
|
*
|
|
432
473
|
* ---
|
|
433
474
|
*
|
|
434
|
-
*
|
|
435
|
-
*
|
|
436
|
-
*
|
|
475
|
+
* ## Reading
|
|
476
|
+
*
|
|
477
|
+
* **`i18n(key, values?, { lang? })`** — the translation for `key`, with
|
|
478
|
+
* `[placeholder]` occurrences replaced from `values` (see `interpolateText`).
|
|
479
|
+
* Returns `key` itself when nothing matches, so an untranslated string still
|
|
480
|
+
* renders something readable. `i18n.format` is an alias of this call.
|
|
481
|
+
*
|
|
482
|
+
* **`i18n.has(key, { lang? })`** — whether a translation genuinely exists,
|
|
483
|
+
* i.e. how to tell "no translation" apart from "translation equal to the key".
|
|
484
|
+
*
|
|
485
|
+
* @returns {Function & { add, addAll, addLangKeys, has, format, languageMap }}
|
|
437
486
|
*/
|
|
438
487
|
const createI18n = ({ keyLang, fallbackLang, runtimeLang } = {}) => {
|
|
439
488
|
const languageMap = new Map();
|
|
@@ -442,12 +491,10 @@ const createI18n = ({ keyLang, fallbackLang, runtimeLang } = {}) => {
|
|
|
442
491
|
// resolve to, so it's what invalidates their own small caches.
|
|
443
492
|
let languageMapVersion = 0;
|
|
444
493
|
|
|
445
|
-
//
|
|
446
|
-
//
|
|
447
|
-
//
|
|
448
|
-
//
|
|
449
|
-
// setSupportedLanguages() (see lang_signal.js) for the rest of this
|
|
450
|
-
// instance's life.
|
|
494
|
+
// Without an explicit runtimeLang, languagesSignal.value is re-read fresh on
|
|
495
|
+
// every call rather than frozen here via languagesSignal.peek() — freezing it
|
|
496
|
+
// would silently ignore setPreferredLanguage()/setSupportedLanguages() (see
|
|
497
|
+
// lang_signal.js) for the rest of this instance's life.
|
|
451
498
|
const hasExplicitRuntimeLang = runtimeLang !== undefined;
|
|
452
499
|
|
|
453
500
|
// matchBestLang does real work (a Map lookup per candidate, a possible
|
|
@@ -622,39 +669,49 @@ const matchBestLang = (lang, languageMap) => {
|
|
|
622
669
|
};
|
|
623
670
|
|
|
624
671
|
/**
|
|
625
|
-
* The shared i18n instance
|
|
626
|
-
*
|
|
627
|
-
*
|
|
628
|
-
*
|
|
629
|
-
*
|
|
630
|
-
*
|
|
631
|
-
*
|
|
632
|
-
*
|
|
633
|
-
*
|
|
634
|
-
*
|
|
635
|
-
*
|
|
636
|
-
*
|
|
637
|
-
*
|
|
638
|
-
*
|
|
672
|
+
* The shared i18n instance holding every text @jsenv/navi components display
|
|
673
|
+
* on their own — validation messages, button labels, empty-list messages,
|
|
674
|
+
* relative time wording…
|
|
675
|
+
*
|
|
676
|
+
* It is navi's texts, not the application's: an app registers its own texts in
|
|
677
|
+
* its own `createI18n()` instance and reaches for `naviI18n` only to change
|
|
678
|
+
* what navi itself says, or to add a language navi does not ship. Keys here are
|
|
679
|
+
* opaque identifiers (`"list.empty"`), never the English sentence — the
|
|
680
|
+
* opposite of what an app is advised to do. `docs/i18n.md` explains why.
|
|
681
|
+
*
|
|
682
|
+
* The active language is read from `languagesSignal` (see lang_signal.js —
|
|
683
|
+
* combines the browser's own `navigator.languages`, an optional
|
|
684
|
+
* `setPreferredLanguage()` user override, and an optional
|
|
685
|
+
* `setSupportedLanguages()` app-wide allow-list), live on every lookup.
|
|
686
|
+
*
|
|
687
|
+
* Built-in key namespaces, all overridable — the registrations below are the
|
|
688
|
+
* exhaustive list, read them to find the exact key to override:
|
|
689
|
+
* - `"button.*"` — Clear, Reset, Send, Open, Close, Cancel, Confirm…
|
|
690
|
+
* - `"time.*"` — relative time wording, duration unit symbols, date field placeholders
|
|
691
|
+
* - `"spin.*"` — the ends of a steppable range
|
|
692
|
+
* - `"list.*"` — empty/no-match/failed-rows messages
|
|
693
|
+
* - `"badge_list.*"` — the "+[count] more" overflow badge
|
|
694
|
+
* - `"constraint.*"` — every field validation message
|
|
695
|
+
*
|
|
696
|
+
* Unit names get two derived keys, both optional: `<unit>__plural` and
|
|
697
|
+
* `<unit>__short`. `<Unit>`/`<Quantity>` fall back to the singular when the
|
|
698
|
+
* derived key is missing, and to `Intl.NumberFormat` when the unit itself is
|
|
699
|
+
* not registered at all — so only units Intl gets wrong need registering.
|
|
639
700
|
*
|
|
640
701
|
* @example
|
|
641
702
|
* import { naviI18n } from "@jsenv/navi";
|
|
642
703
|
*
|
|
643
|
-
* // Register unit translations for Quantity:
|
|
644
|
-
* naviI18n.add("minute", { en: "minute", fr: "minute" });
|
|
645
|
-
* naviI18n.add("minute__plural", { en: "minutes", fr: "minutes" });
|
|
646
|
-
*
|
|
647
|
-
* // Register multiple keys at once:
|
|
648
|
-
* naviI18n.addAll({
|
|
649
|
-
* minute: { en: "minute", fr: "minute" },
|
|
650
|
-
* minute__plural: { en: "minutes", fr: "minutes" },
|
|
651
|
-
* });
|
|
652
|
-
*
|
|
653
704
|
* // Override a built-in text:
|
|
654
705
|
* naviI18n.add("time.ongoing", { fr: "En cours…" });
|
|
655
706
|
*
|
|
656
|
-
* //
|
|
657
|
-
* naviI18n.addLangKeys("
|
|
707
|
+
* // Teach navi a language it does not ship:
|
|
708
|
+
* naviI18n.addLangKeys("ja", { "list.empty": "項目がありません。" });
|
|
709
|
+
*
|
|
710
|
+
* // Register unit translations used by <Quantity>/<Unit>:
|
|
711
|
+
* naviI18n.addAll({
|
|
712
|
+
* ticket: { en: "ticket", fr: "billet" },
|
|
713
|
+
* ticket__plural: { en: "tickets", fr: "billets" },
|
|
714
|
+
* });
|
|
658
715
|
*/
|
|
659
716
|
const naviI18n = createI18n();
|
|
660
717
|
|
|
@@ -30480,7 +30537,7 @@ const getParamScope = (params) => {
|
|
|
30480
30537
|
};
|
|
30481
30538
|
|
|
30482
30539
|
/*
|
|
30483
|
-
*
|
|
30540
|
+
* GET_RANGE: reading a resource one slice at a time, for a list that draws its
|
|
30484
30541
|
* rows as it goes (`<List.Items itemsAction>`).
|
|
30485
30542
|
*
|
|
30486
30543
|
* It is on purpose not an action. An action keeps the one response it got and
|
|
@@ -30496,16 +30553,16 @@ const getParamScope = (params) => {
|
|
|
30496
30553
|
* list is holding is the one it was given.
|
|
30497
30554
|
*
|
|
30498
30555
|
* The reader is a function, so a list feeds on it the way it feeds on any other
|
|
30499
|
-
* source: `itemsAction={GAME.
|
|
30556
|
+
* source: `itemsAction={GAME.GET_RANGE.bindParams({ radar })}`.
|
|
30500
30557
|
*/
|
|
30501
30558
|
|
|
30502
30559
|
|
|
30503
|
-
const
|
|
30560
|
+
const createRangeReader = (
|
|
30504
30561
|
actionName,
|
|
30505
30562
|
callback,
|
|
30506
30563
|
{ store, params: boundParams },
|
|
30507
30564
|
) => {
|
|
30508
|
-
const
|
|
30565
|
+
const readRange = async (range = {}) => {
|
|
30509
30566
|
const { signal, ...rangeParams } = range;
|
|
30510
30567
|
const paramsResolved = { ...resolveParams(boundParams), ...rangeParams };
|
|
30511
30568
|
const result = await callback(paramsResolved, { signal });
|
|
@@ -30520,7 +30577,7 @@ const createPageReader = (
|
|
|
30520
30577
|
const startAsked = rangeParams.start;
|
|
30521
30578
|
if (startAsked === undefined || startAsked < 0) {
|
|
30522
30579
|
throw new TypeError(
|
|
30523
|
-
`${actionName} must say where the
|
|
30580
|
+
`${actionName} must say where the range lands (start), it was asked for ${describeRangeAsked(rangeParams)}.`,
|
|
30524
30581
|
);
|
|
30525
30582
|
}
|
|
30526
30583
|
start = startAsked;
|
|
@@ -30530,19 +30587,19 @@ const createPageReader = (
|
|
|
30530
30587
|
}
|
|
30531
30588
|
return { items, start, count };
|
|
30532
30589
|
};
|
|
30533
|
-
Object.defineProperty(
|
|
30534
|
-
|
|
30535
|
-
|
|
30536
|
-
return
|
|
30590
|
+
Object.defineProperty(readRange, "name", { value: actionName });
|
|
30591
|
+
readRange.isRangeReader = true;
|
|
30592
|
+
readRange.bindParams = (paramsToBind) => {
|
|
30593
|
+
return createRangeReader(actionName, callback, {
|
|
30537
30594
|
store,
|
|
30538
30595
|
params: boundParams ? { ...boundParams, ...paramsToBind } : paramsToBind,
|
|
30539
30596
|
});
|
|
30540
30597
|
};
|
|
30541
|
-
return
|
|
30598
|
+
return readRange;
|
|
30542
30599
|
};
|
|
30543
30600
|
|
|
30544
30601
|
// Params bound to a reader may be signals (the radar currently on screen); the
|
|
30545
|
-
// value they hold when the
|
|
30602
|
+
// value they hold when the range is asked for is the one the range is about.
|
|
30546
30603
|
const resolveParams = (params) => {
|
|
30547
30604
|
if (!params) {
|
|
30548
30605
|
return {};
|
|
@@ -30600,18 +30657,18 @@ const debug$2 = (args) => {
|
|
|
30600
30657
|
* - GET / POST / PUT / PATCH → the full item object, e.g. `{ id, name }`
|
|
30601
30658
|
* - DELETE → the id or `{ id }` of the removed item
|
|
30602
30659
|
* - GET_MANY / POST_MANY / … → an array of item objects
|
|
30603
|
-
* -
|
|
30660
|
+
* - GET_RANGE → `{ items, start, count }`, one slice of the collection
|
|
30604
30661
|
*
|
|
30605
|
-
* `
|
|
30662
|
+
* `GET_RANGE` is a reader rather than an action: it keeps no value and takes no place in
|
|
30606
30663
|
* the rerun graph, so a `<List.Items>` can feed on it slice by slice
|
|
30607
|
-
* (`itemsAction={USER.
|
|
30664
|
+
* (`itemsAction={USER.GET_RANGE.bindParams({ team })}`).
|
|
30608
30665
|
*
|
|
30609
30666
|
* A sub-resource of the backend (`/games/:id/candidates`) must be modelled with a
|
|
30610
30667
|
* relationship method, never as an `op`/`type` discriminator dispatched inside one
|
|
30611
30668
|
* verb's callback.
|
|
30612
30669
|
*
|
|
30613
30670
|
* @param {string} name - resource name, used in action names and error messages
|
|
30614
|
-
* @param {Object} restCallbacks - `{ idKey, uniqueKeys, rerunOn, dependencies, GET, GET_MANY,
|
|
30671
|
+
* @param {Object} restCallbacks - `{ idKey, uniqueKeys, rerunOn, dependencies, GET, GET_MANY, GET_RANGE, POST, POST_MANY, PUT, PUT_MANY, PATCH, PATCH_MANY, DELETE, DELETE_MANY }`
|
|
30615
30672
|
* @param {string} [restCallbacks.idKey] - primary key property, defaults to `"id"` (or the first `uniqueKeys` entry)
|
|
30616
30673
|
* @param {string[]} [restCallbacks.uniqueKeys] - alternate keys the store can find an item by (e.g. `"username"`); a callback may return a different `id` to rename the item's primary key
|
|
30617
30674
|
* @see docs/resource.md — relationships, callback return contracts, decision table
|
|
@@ -30635,7 +30692,7 @@ const resource = (
|
|
|
30635
30692
|
|
|
30636
30693
|
GET,
|
|
30637
30694
|
GET_MANY,
|
|
30638
|
-
|
|
30695
|
+
GET_RANGE,
|
|
30639
30696
|
POST,
|
|
30640
30697
|
POST_MANY,
|
|
30641
30698
|
PUT,
|
|
@@ -30701,7 +30758,7 @@ const resource = (
|
|
|
30701
30758
|
restCallbacks: {
|
|
30702
30759
|
GET,
|
|
30703
30760
|
GET_MANY,
|
|
30704
|
-
|
|
30761
|
+
GET_RANGE,
|
|
30705
30762
|
POST,
|
|
30706
30763
|
POST_MANY,
|
|
30707
30764
|
PUT,
|
|
@@ -31783,11 +31840,11 @@ ${originalActionName} source location: ${locationInfo}`,
|
|
|
31783
31840
|
if (restCallback === undefined) {
|
|
31784
31841
|
continue;
|
|
31785
31842
|
}
|
|
31786
|
-
if (restCallbackKey === "
|
|
31787
|
-
// A
|
|
31788
|
-
// (see
|
|
31789
|
-
stateFacade.
|
|
31790
|
-
`${name}.
|
|
31843
|
+
if (restCallbackKey === "GET_RANGE") {
|
|
31844
|
+
// A range is read, never kept: no action, no place in the rerun graph
|
|
31845
|
+
// (see resource_range_reader.js).
|
|
31846
|
+
stateFacade.GET_RANGE = createRangeReader(
|
|
31847
|
+
`${name}.GET_RANGE`,
|
|
31791
31848
|
restCallback,
|
|
31792
31849
|
{ store, params },
|
|
31793
31850
|
);
|
|
@@ -54314,15 +54371,15 @@ const VISIBILITY_HIDDEN_STYLE = {
|
|
|
54314
54371
|
* before it knows how many there are), `limit` (how many rows), and
|
|
54315
54372
|
* `before`/`after`/`around` (the id of a row to count from, for a source
|
|
54316
54373
|
* paginating by cursor). Answer with the rows (an array — that is all of
|
|
54317
|
-
* them), or with a
|
|
54374
|
+
* them), or with a range the way a Content-Range does: `{ items, start, count }`
|
|
54318
54375
|
* — these rows, at this place, out of that many. May be async. The range also
|
|
54319
54376
|
* carries a `signal`, aborted when the list stops wanting those rows (the
|
|
54320
54377
|
* window has moved on) — pass it to fetch to call the request off.
|
|
54321
54378
|
*
|
|
54322
|
-
* A resource answers through its
|
|
54323
|
-
* `itemsAction={GAME.
|
|
54379
|
+
* A resource answers through its range reader:
|
|
54380
|
+
* `itemsAction={GAME.GET_RANGE.bindParams({ radar })}` — the rows are upserted
|
|
54324
54381
|
* into the store on their way in, so the list draws store items rather than
|
|
54325
|
-
* copies of the JSON. The list holds the
|
|
54382
|
+
* copies of the JSON. The list holds the slices, the store holds the objects
|
|
54326
54383
|
* (see docs/resource.md).
|
|
54327
54384
|
*
|
|
54328
54385
|
* A collection held in memory answers synchronously: `itemsAction={() => rows}`.
|
|
@@ -54857,7 +54914,7 @@ const useItemStore = ({
|
|
|
54857
54914
|
let result;
|
|
54858
54915
|
try {
|
|
54859
54916
|
if (typeof itemsAction !== "function") {
|
|
54860
|
-
throw new TypeError(`itemsAction must be a function, received ${itemsAction}. A resource feeds a list through its
|
|
54917
|
+
throw new TypeError(`itemsAction must be a function, received ${itemsAction}. A resource feeds a list through its range reader: itemsAction={RESOURCE.GET_RANGE.bindParams(...)} — its other actions keep one response and cannot answer a range.`);
|
|
54861
54918
|
}
|
|
54862
54919
|
result = itemsAction(range);
|
|
54863
54920
|
} catch (e) {
|
|
@@ -65944,16 +66001,35 @@ const CodeBox = ({
|
|
|
65944
66001
|
*/
|
|
65945
66002
|
|
|
65946
66003
|
/**
|
|
65947
|
-
* Renders a template string with [key] placeholders replaced by props.
|
|
65948
|
-
*
|
|
65949
|
-
* Returns a plain string when all replacements are
|
|
66004
|
+
* Renders a template string with `[key]` placeholders replaced by props.
|
|
66005
|
+
* Every prop other than `children` is a replacement value; values can be
|
|
66006
|
+
* strings or JSX elements. Returns a plain string when all replacements are
|
|
66007
|
+
* strings, a fragment otherwise.
|
|
65950
66008
|
*
|
|
65951
66009
|
* Keeps the full sentence readable in one place and makes the string
|
|
65952
|
-
* i18n-ready, since the template contains no JSX expressions.
|
|
66010
|
+
* i18n-ready, since the template contains no JSX expressions. `children` must
|
|
66011
|
+
* be a plain string for interpolation to happen.
|
|
66012
|
+
*
|
|
66013
|
+
* Placeholder resolution (dot-paths, function values, unmatched placeholders
|
|
66014
|
+
* left visible) is `interpolateText`'s — read its JSDoc for the details, and
|
|
66015
|
+
* `docs/i18n.md` for how this fits with `naviI18n`/`createI18n`.
|
|
65953
66016
|
*
|
|
65954
66017
|
* @example
|
|
65955
|
-
* <Interpolate radiusKm={<Text bold>50 km</Text>} zoneName="
|
|
65956
|
-
*
|
|
66018
|
+
* <Interpolate radiusKm={<Text bold>50 km</Text>} zoneName="your area">
|
|
66019
|
+
* Data limited to [radiusKm] around [zoneName].
|
|
66020
|
+
* </Interpolate>
|
|
66021
|
+
*
|
|
66022
|
+
* @example
|
|
66023
|
+
* // translated template: the sentence comes from i18n, the JSX from here.
|
|
66024
|
+
* // With keyLang, the key is the sentence itself — same string as above, so
|
|
66025
|
+
* // moving a hardcoded <Interpolate> to i18n is wrapping it in a call.
|
|
66026
|
+
* const i18n = createI18n({ keyLang: "en" });
|
|
66027
|
+
* i18n.add("Data limited to [radiusKm] around [zoneName].", {
|
|
66028
|
+
* fr: "Données limitées à [radiusKm] autour de [zoneName].",
|
|
66029
|
+
* });
|
|
66030
|
+
*
|
|
66031
|
+
* <Interpolate radiusKm={<Text bold>50 km</Text>} zoneName="your area">
|
|
66032
|
+
* {i18n("Data limited to [radiusKm] around [zoneName].")}
|
|
65957
66033
|
* </Interpolate>
|
|
65958
66034
|
*/
|
|
65959
66035
|
const Interpolate = ({
|
|
@@ -67514,5 +67590,5 @@ const UserSvg = () => jsx("svg", {
|
|
|
67514
67590
|
})
|
|
67515
67591
|
});
|
|
67516
67592
|
|
|
67517
|
-
export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, BadgeList, Binder, Box, Button, ButtonCopyToClipboard, Caption, CardLayout, CheckSvg, CheckboxGroup, CloseSvg, Code, Col, Colgroup, Color, ConstructionSvg, ControlGroup, DaySpin, Details, Dialog, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, EyeClosedSvg, EyeSvg, Field, FixedBar, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, 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, RouteTravel, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, Select, SelectableInput, SelectionContext, Separator, SettingsSvg, SidePanel, Slide, SlideContainer, Spin, StarSvg, SummaryMarker, Svg, Table, TableCell, Tbody, Text, TextBox, Textarea, TextareaCharCount, Thead, Time, Title, Tr, UITransition, Unit, UserSvg, ViewportLayout, Wheel, WheelGroup, WheelItem, actionRunEffect, anyMatchingRouteSignal, applySearch, arraySignalMembership, coarsePointerSignal, compareTwoJsValues, createAction, createAvailableConstraint, createRequestCanceller, createSearch, createSelectionKeyboardShortcuts, createSlot, defineNaviConfirmPopupOptions, detectHorizontalOverflow, enableDebugActions, enableDebugOnDocumentLoading, ensureDocumentStartViewTransition, filterTableSelection, formatDatetime, formatDay, formatDayRelative, formatMonth, formatNumber, formatTime, formatTimeRelative, getNowHours, getNowHoursRoundedToStep, interpolateText, isCellSelected, isColumnSelected, isRowSelected, isToday, languagesSignal, localStorageSignal, moveArrayItemByIndex, navBack, navForward, navIntegratedVia, navTo, naviI18n, openCallout, rawUrlPart, registerGlobalConstraint, reload, rerunActions, resource, route, routeAction, setBaseUrl, setPreferredLanguage, setSupportedLanguages, setupRoutes, stateSignal, stopLoad, stringifyTableSelectionValue, swapArrayItemByIndex, syncOwnedResourceToSignals, syncResourceToSignals, triggerNaviCommand, updateActions, useActionStatus, useArraySignalMembership, useAsyncData, useCalloutRequestClose, 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, valueInLocalStorage, windowWidthSignal };
|
|
67593
|
+
export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, BadgeList, Binder, Box, Button, ButtonCopyToClipboard, Caption, CardLayout, CheckSvg, CheckboxGroup, CloseSvg, Code, Col, Colgroup, Color, ConstructionSvg, ControlGroup, DaySpin, Details, Dialog, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, EyeClosedSvg, EyeSvg, Field, FixedBar, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, 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, RouteTravel, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, Select, SelectableInput, SelectionContext, Separator, SettingsSvg, SidePanel, Slide, SlideContainer, Spin, StarSvg, SummaryMarker, Svg, Table, TableCell, Tbody, Text, TextBox, Textarea, TextareaCharCount, Thead, Time, Title, Tr, UITransition, Unit, UserSvg, ViewportLayout, Wheel, WheelGroup, WheelItem, actionRunEffect, anyMatchingRouteSignal, applySearch, arraySignalMembership, coarsePointerSignal, compareTwoJsValues, createAction, createAvailableConstraint, createI18n, createRequestCanceller, createSearch, createSelectionKeyboardShortcuts, createSlot, defineNaviConfirmPopupOptions, detectHorizontalOverflow, enableDebugActions, enableDebugOnDocumentLoading, ensureDocumentStartViewTransition, filterTableSelection, formatDatetime, formatDay, formatDayRelative, formatMonth, formatNumber, formatTime, formatTimeRelative, getNowHours, getNowHoursRoundedToStep, interpolateText, isCellSelected, isColumnSelected, isRowSelected, isToday, languagesSignal, localStorageSignal, moveArrayItemByIndex, navBack, navForward, navIntegratedVia, navTo, naviI18n, openCallout, rawUrlPart, registerGlobalConstraint, reload, rerunActions, resource, route, routeAction, setBaseUrl, setPreferredLanguage, setSupportedLanguages, setupRoutes, stateSignal, stopLoad, stringifyTableSelectionValue, swapArrayItemByIndex, syncOwnedResourceToSignals, syncResourceToSignals, triggerNaviCommand, updateActions, useActionStatus, useArraySignalMembership, useAsyncData, useCalloutRequestClose, 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, valueInLocalStorage, windowWidthSignal };
|
|
67518
67594
|
//# sourceMappingURL=jsenv_navi.js.map
|