neba 1.2.0 → 1.4.0
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 +8 -8
- package/dist/components/alert/Alert.d.ts +7 -1
- package/dist/components/alert/Alert.js +1 -1
- package/dist/components/animate-appear/AnimateAppear.d.ts +55 -0
- package/dist/components/animate-appear/AnimateAppear.js +1 -0
- package/dist/components/animate-appear/index.d.ts +2 -0
- package/dist/components/animate-appear/index.js +1 -0
- package/dist/components/animate-blink/AnimateBlink.d.ts +31 -0
- package/dist/components/animate-blink/AnimateBlink.js +1 -0
- package/dist/components/animate-blink/index.d.ts +2 -0
- package/dist/components/animate-blink/index.js +1 -0
- package/dist/components/animate-fade/AnimateFade.d.ts +31 -0
- package/dist/components/animate-fade/AnimateFade.js +1 -0
- package/dist/components/animate-fade/index.d.ts +2 -0
- package/dist/components/animate-fade/index.js +1 -0
- package/dist/components/animate-grow/AnimateGrow.d.ts +41 -0
- package/dist/components/animate-grow/AnimateGrow.js +1 -0
- package/dist/components/animate-grow/index.d.ts +2 -0
- package/dist/components/animate-grow/index.js +1 -0
- package/dist/components/animate-headline/AnimateHeadline.d.ts +55 -0
- package/dist/components/animate-headline/AnimateHeadline.js +1 -0
- package/dist/components/animate-headline/index.d.ts +2 -0
- package/dist/components/animate-headline/index.js +1 -0
- package/dist/components/animate-lighting/AnimateLighting.d.ts +62 -0
- package/dist/components/animate-lighting/AnimateLighting.js +1 -0
- package/dist/components/animate-lighting/index.d.ts +2 -0
- package/dist/components/animate-lighting/index.js +1 -0
- package/dist/components/animate-marquee/AnimateMarquee.d.ts +65 -0
- package/dist/components/animate-marquee/AnimateMarquee.js +1 -0
- package/dist/components/animate-marquee/index.d.ts +2 -0
- package/dist/components/animate-marquee/index.js +1 -0
- package/dist/components/animate-rotate/AnimateRotate.d.ts +48 -0
- package/dist/components/animate-rotate/AnimateRotate.js +1 -0
- package/dist/components/animate-rotate/index.d.ts +2 -0
- package/dist/components/animate-rotate/index.js +1 -0
- package/dist/components/animate-slide/AnimateSlide.d.ts +46 -0
- package/dist/components/animate-slide/AnimateSlide.js +1 -0
- package/dist/components/animate-slide/index.d.ts +2 -0
- package/dist/components/animate-slide/index.js +1 -0
- package/dist/components/animate-typing/AnimateTyping.d.ts +58 -0
- package/dist/components/animate-typing/AnimateTyping.js +1 -0
- package/dist/components/animate-typing/index.d.ts +2 -0
- package/dist/components/animate-typing/index.js +1 -0
- package/dist/components/animate-zoom/AnimateZoom.d.ts +37 -0
- package/dist/components/animate-zoom/AnimateZoom.js +1 -0
- package/dist/components/animate-zoom/index.d.ts +2 -0
- package/dist/components/animate-zoom/index.js +1 -0
- package/dist/components/aspect-ratio/AspectRatio.d.ts +63 -0
- package/dist/components/aspect-ratio/AspectRatio.js +1 -0
- package/dist/components/aspect-ratio/index.d.ts +2 -0
- package/dist/components/aspect-ratio/index.js +1 -0
- package/dist/components/avatar/Avatar.d.ts +7 -1
- package/dist/components/avatar/Avatar.js +1 -1
- package/dist/components/blockquote/Blockquote.d.ts +7 -1
- package/dist/components/blockquote/Blockquote.js +1 -1
- package/dist/components/box/Box.d.ts +7 -1
- package/dist/components/box/Box.js +1 -1
- package/dist/components/chip/Chip.d.ts +7 -1
- package/dist/components/chip/Chip.js +1 -1
- package/dist/components/color-picker/ColorPicker.d.ts +102 -0
- package/dist/components/color-picker/ColorPicker.js +1 -0
- package/dist/components/color-picker/index.d.ts +2 -0
- package/dist/components/color-picker/index.js +1 -0
- package/dist/components/data-table/DataTable.d.ts +323 -0
- package/dist/components/data-table/DataTable.js +1 -0
- package/dist/components/data-table/index.d.ts +2 -0
- package/dist/components/data-table/index.js +1 -0
- package/dist/components/drawer/Drawer.d.ts +145 -0
- package/dist/components/drawer/Drawer.js +1 -0
- package/dist/components/drawer/index.d.ts +2 -0
- package/dist/components/drawer/index.js +1 -0
- package/dist/components/empty/Empty.d.ts +69 -0
- package/dist/components/empty/Empty.js +1 -0
- package/dist/components/empty/index.d.ts +2 -0
- package/dist/components/empty/index.js +1 -0
- package/dist/components/icon/Icon.d.ts +8 -1
- package/dist/components/icon/Icon.js +1 -1
- package/dist/components/mockup/Mockup.d.ts +151 -0
- package/dist/components/mockup/Mockup.js +1 -0
- package/dist/components/mockup/index.d.ts +2 -0
- package/dist/components/mockup/index.js +1 -0
- package/dist/components/popover/Popover.d.ts +109 -0
- package/dist/components/popover/Popover.js +1 -0
- package/dist/components/popover/index.d.ts +2 -0
- package/dist/components/popover/index.js +1 -0
- package/dist/components/skeleton/Skeleton.d.ts +76 -0
- package/dist/components/skeleton/Skeleton.js +1 -0
- package/dist/components/skeleton/index.d.ts +2 -0
- package/dist/components/skeleton/index.js +1 -0
- package/dist/components/typography/Typography.d.ts +7 -1
- package/dist/components/typography/Typography.js +1 -1
- package/dist/index.d.ts +19 -0
- package/dist/index.js +1 -1
- package/dist/internal/animate.d.ts +175 -0
- package/dist/internal/animate.js +1 -0
- package/dist/internal/color.d.ts +108 -0
- package/dist/internal/color.js +1 -0
- package/dist/internal/data-table.d.ts +170 -0
- package/dist/internal/data-table.js +1 -0
- package/dist/internal/i18n.d.ts +76 -0
- package/dist/internal/i18n.js +1 -1
- package/dist/internal/mockup.d.ts +193 -0
- package/dist/internal/mockup.js +1 -0
- package/dist/styles.css +1 -1
- package/dist/tailwind.css +405 -11
- package/dist/types.d.ts +112 -0
- package/package.json +12 -12
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The machinery every `Animate*` component runs on, and the `transition` prop
|
|
3
|
+
* with it.
|
|
4
|
+
*
|
|
5
|
+
* It lives in `internal/` for the reason `button-group.ts` and `menu.ts` do:
|
|
6
|
+
* eleven components need it and none of them should have to import another. The
|
|
7
|
+
* `transition` prop needs the same table from the other side — a Card and an
|
|
8
|
+
* `AnimateFade` must produce the same fade, or the library has two fades.
|
|
9
|
+
*
|
|
10
|
+
* ## The shape of it
|
|
11
|
+
*
|
|
12
|
+
* Every effect is one `@keyframes` in `styles.css` running from a state written
|
|
13
|
+
* entirely in custom properties to the element's natural one. Nothing here
|
|
14
|
+
* generates CSS: it fills `--n-anim-*` slots, and the stylesheet decides what
|
|
15
|
+
* they mean. That is the same split `styleSlots()` makes for colour, for the
|
|
16
|
+
* same reason — Tailwind only ever sees class names that appear literally in
|
|
17
|
+
* the source, so a per-value class would not scale past the first prop.
|
|
18
|
+
*
|
|
19
|
+
* Because the from-state is the *keyframe* rather than a second class, running
|
|
20
|
+
* an effect backwards is `animation-direction: reverse` and nothing else. That
|
|
21
|
+
* is what makes `mode="out"` free on all six.
|
|
22
|
+
*
|
|
23
|
+
* ## What is deliberately not here
|
|
24
|
+
*
|
|
25
|
+
* An animation that has to know what its children *are* — a marquee that
|
|
26
|
+
* duplicates them, a headline that swaps between them, a typewriter that
|
|
27
|
+
* counts characters — cannot be a class name and a few numbers. Those are
|
|
28
|
+
* components, and their logic stays in their own files.
|
|
29
|
+
*/
|
|
30
|
+
import * as React from 'react';
|
|
31
|
+
import type { NebaAnimateRepeat, NebaAnimateTrigger, NebaAnimation, NebaSide, NebaTransition } from '../types';
|
|
32
|
+
/**
|
|
33
|
+
* Which keyframe an effect runs.
|
|
34
|
+
*
|
|
35
|
+
* `grow` and `zoom` share one: they are the same arithmetic at two strengths,
|
|
36
|
+
* and a second identical `@keyframes` would only be a second place to fix a
|
|
37
|
+
* bug. What separates them is their defaults and their origin, which is a
|
|
38
|
+
* property rather than a keyframe.
|
|
39
|
+
*/
|
|
40
|
+
export declare const animationClasses: Record<NebaAnimation, string>;
|
|
41
|
+
/** The class that reads the slots. Always paired with one of the above. */
|
|
42
|
+
export declare const animBaseClass = "neba-anim";
|
|
43
|
+
/**
|
|
44
|
+
* How long each effect takes when nobody said.
|
|
45
|
+
*
|
|
46
|
+
* They are not one number because they do not travel the same distance. A fade
|
|
47
|
+
* crosses one axis of opacity; a rotate crosses half a turn and reads as rushed
|
|
48
|
+
* at the same duration. `blink` is the odd one out — it is a cycle rather than
|
|
49
|
+
* an arrival, so its number is a period.
|
|
50
|
+
*/
|
|
51
|
+
export declare const defaultDurations: Record<NebaAnimation, number>;
|
|
52
|
+
/** A number is pixels; a string is already a CSS length. */
|
|
53
|
+
export declare function lengthValue(value: number | string): string;
|
|
54
|
+
export declare function isInfinite(repeat: NebaAnimateRepeat | undefined): boolean;
|
|
55
|
+
export interface AnimationSlotOptions {
|
|
56
|
+
duration: number;
|
|
57
|
+
delay: number;
|
|
58
|
+
easing?: string;
|
|
59
|
+
repeat: NebaAnimateRepeat;
|
|
60
|
+
alternate?: boolean;
|
|
61
|
+
mode?: 'in' | 'out';
|
|
62
|
+
/** Where the animated properties start. Only the ones an effect reads. */
|
|
63
|
+
opacity?: number;
|
|
64
|
+
scale?: number;
|
|
65
|
+
x?: string;
|
|
66
|
+
y?: string;
|
|
67
|
+
angle?: string;
|
|
68
|
+
angleTo?: string;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The `--n-anim-*` slots, as an inline style object.
|
|
72
|
+
*
|
|
73
|
+
* Inline rather than utilities for the reason the colour slots are: these are
|
|
74
|
+
* per-instance numbers, and Tailwind cannot generate a class for a duration it
|
|
75
|
+
* has never seen written down.
|
|
76
|
+
*/
|
|
77
|
+
export declare function animationSlots(options: AnimationSlotOptions): React.CSSProperties;
|
|
78
|
+
/**
|
|
79
|
+
* Which way a slide starts, given the edge it comes from.
|
|
80
|
+
*
|
|
81
|
+
* `NebaSide` is physical everywhere in the library, and it stays physical here:
|
|
82
|
+
* something sliding in from the top of the window comes from the top in every
|
|
83
|
+
* writing direction.
|
|
84
|
+
*/
|
|
85
|
+
export declare function slideOffsets(from: NebaSide, distance: number | string): {
|
|
86
|
+
x: string;
|
|
87
|
+
y: string;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* What a component's `transition` prop turns into: one class pair and a handful
|
|
91
|
+
* of custom properties, merged into whatever the component was already writing.
|
|
92
|
+
*
|
|
93
|
+
* No hook, no ref, no observer — which is the whole design. A component that
|
|
94
|
+
* offers `transition` gains four lines and no behaviour it has to own, and a
|
|
95
|
+
* caller who needs a trigger, a replay or a hover reaches for the `Animate*`
|
|
96
|
+
* component that does exactly that.
|
|
97
|
+
*/
|
|
98
|
+
export declare function transitionProps(transition: NebaTransition | undefined): {
|
|
99
|
+
className: string;
|
|
100
|
+
style: React.CSSProperties;
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* Whether the reader has asked for less motion.
|
|
104
|
+
*
|
|
105
|
+
* The CSS side of this is handled in the stylesheet, where every keyframe is
|
|
106
|
+
* switched off at once. This is for the three effects whose motion is written
|
|
107
|
+
* in JavaScript — a typewriter, a headline reel, a measured marquee — where
|
|
108
|
+
* there is no rule to switch off and the component has to decide for itself
|
|
109
|
+
* what "still" means.
|
|
110
|
+
*/
|
|
111
|
+
export declare function usePrefersReducedMotion(): boolean;
|
|
112
|
+
export interface AnimationRunOptions {
|
|
113
|
+
trigger: NebaAnimateTrigger;
|
|
114
|
+
play?: boolean;
|
|
115
|
+
once: boolean;
|
|
116
|
+
threshold: number;
|
|
117
|
+
paused?: boolean;
|
|
118
|
+
/** An infinite effect stops when the pointer leaves; a finite one finishes. */
|
|
119
|
+
infinite: boolean;
|
|
120
|
+
}
|
|
121
|
+
export interface AnimationRun {
|
|
122
|
+
/** Goes on the animated element. */
|
|
123
|
+
ref: React.RefCallback<HTMLElement>;
|
|
124
|
+
/** `running` or `paused`, for `--n-anim-state`. */
|
|
125
|
+
state: 'running' | 'paused';
|
|
126
|
+
/** Whether the animation has been let go at all, for `data-state`. */
|
|
127
|
+
started: boolean;
|
|
128
|
+
/** Spread onto the element when `trigger` is `hover`; empty otherwise. */
|
|
129
|
+
handlers: React.HTMLAttributes<HTMLElement>;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Starts, restarts and holds an animation.
|
|
133
|
+
*
|
|
134
|
+
* Two things here are less obvious than they look.
|
|
135
|
+
*
|
|
136
|
+
* **Waiting is `animation-play-state: paused`, not a second class.** An
|
|
137
|
+
* element that has not been triggered yet has to already look like its own
|
|
138
|
+
* first frame, or a `visible` fade would be fully drawn until it scrolled into
|
|
139
|
+
* view and then blink out to start. With `fill-mode: both` a paused animation
|
|
140
|
+
* shows exactly that frame, so waiting and running are one animation in two
|
|
141
|
+
* states rather than two states to keep in step.
|
|
142
|
+
*
|
|
143
|
+
* **Restarting reaches for the DOM.** There is no way to rewind a CSS animation
|
|
144
|
+
* from React: re-rendering with the same class changes nothing, and a `key`
|
|
145
|
+
* would restart the animation by unmounting the children, taking their state
|
|
146
|
+
* with it. Clearing `animation-name`, reading a layout property to force the
|
|
147
|
+
* style to settle, and putting it back is the one move that rewinds the element
|
|
148
|
+
* and leaves everything inside it alone.
|
|
149
|
+
*/
|
|
150
|
+
export declare function useAnimationRun({ trigger, play, once, threshold, paused, infinite }: AnimationRunOptions): AnimationRun;
|
|
151
|
+
export interface AnimateElementParams extends AnimationSlotOptions, AnimationRunOptions {
|
|
152
|
+
/** Which keyframe. `null` for the components that write their own. */
|
|
153
|
+
effect: NebaAnimation | null;
|
|
154
|
+
}
|
|
155
|
+
export interface AnimateElement {
|
|
156
|
+
ref: React.RefCallback<HTMLElement>;
|
|
157
|
+
className: string;
|
|
158
|
+
style: React.CSSProperties;
|
|
159
|
+
/** The hover handlers and the two data attributes, ready to be spread. */
|
|
160
|
+
props: React.HTMLAttributes<HTMLElement> & Record<string, unknown>;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Everything an `Animate*` root needs, in one call.
|
|
164
|
+
*
|
|
165
|
+
* The six effect components differ only in their defaults and in which slots
|
|
166
|
+
* they fill, so this is where the identical two-thirds of each of them lives.
|
|
167
|
+
* The four that have to understand their children — Typing, Marquee, Headline,
|
|
168
|
+
* Appear — call `useAnimationRun` directly and put the classes where their own
|
|
169
|
+
* structure needs them, which is why `effect` is allowed to be `null`.
|
|
170
|
+
*
|
|
171
|
+
* `data-neba-animation` and `data-state` are here rather than in each component
|
|
172
|
+
* because they are the same two facts every time, and because a test that has
|
|
173
|
+
* to assert on a class name is a test that breaks when a class name changes.
|
|
174
|
+
*/
|
|
175
|
+
export declare function useAnimateElement(params: AnimateElementParams): AnimateElement;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import*as e from"react";export const animationClasses={fade:"neba-anim-fade",grow:"neba-anim-scale",slide:"neba-anim-slide",zoom:"neba-anim-scale",rotate:"neba-anim-rotate",blink:"neba-anim-blink"};export const animBaseClass="neba-anim";export const defaultDurations={fade:320,grow:340,slide:380,zoom:340,rotate:460,blink:900};export function lengthValue(e){return"number"==typeof e?`${e}px`:e}export function isInfinite(e){return"infinite"===e}export function animationSlots(e){const n={"--n-anim-duration":`${e.duration}ms`,"--n-anim-delay":`${e.delay}ms`,"--n-anim-repeat":(i=e.repeat,"infinite"===i?"infinite":String(i)),"--n-anim-direction":(t=e.mode??"in",a=e.alternate,"out"===t?a?"alternate-reverse":"reverse":a?"alternate":"normal")};var t,a,i;return e.easing&&(n["--n-anim-ease"]=e.easing),void 0!==e.opacity&&(n["--n-anim-opacity"]=String(e.opacity)),void 0!==e.scale&&(n["--n-anim-scale"]=String(e.scale)),void 0!==e.x&&(n["--n-anim-x"]=e.x),void 0!==e.y&&(n["--n-anim-y"]=e.y),void 0!==e.angle&&(n["--n-anim-angle"]=e.angle),void 0!==e.angleTo&&(n["--n-anim-angle-to"]=e.angleTo),n}export function slideOffsets(e,n){const t=lengthValue(n),a="number"==typeof n?-n+"px":`calc(-1 * ${t})`;switch(e){case"top":return{x:"0px",y:a};case"bottom":return{x:"0px",y:t};case"left":return{x:a,y:"0px"};default:return{x:t,y:"0px"}}}export function transitionProps(e){if(!e)return{className:"",style:{}};const n="string"==typeof e?{type:e}:e,{type:t}=n,a={duration:n.duration??defaultDurations[t],delay:n.delay??0,easing:n.easing,repeat:n.repeat??("blink"===t?"infinite":1),alternate:n.alternate};if("grow"!==t&&"zoom"!==t||(a.scale=n.scale??("zoom"===t?.4:.8)),"slide"===t){const{x:e,y:t}=slideOffsets(n.from??"bottom",n.distance??"100%");a.x=e,a.y=t}return"rotate"===t&&(a.angle=`${n.angle??-180}deg`),{className:`neba-anim ${animationClasses[t]}`,style:animationSlots(a)}}export function usePrefersReducedMotion(){const[n,t]=e.useState(!1);return e.useEffect(()=>{if("undefined"==typeof window||!window.matchMedia)return;const e=window.matchMedia("(prefers-reduced-motion: reduce)");t(e.matches);const n=e=>t(e.matches);return e.addEventListener("change",n),()=>e.removeEventListener("change",n)},[]),n}export function useAnimationRun({trigger:n,play:t,once:a,threshold:i,paused:o,infinite:r}){const s=e.useRef(null),[c,l]=e.useState("mount"===n),[u,m]=e.useState(0),d=e.useCallback(()=>{l(!0),m(e=>e+1)},[]);e.useLayoutEffect(()=>{const e=s.current;if(!e||0===u)return;const n=[e,...e.querySelectorAll(".neba-anim, .neba-marquee-track")];for(const e of n)e.style.animationName="none";e.offsetWidth;for(const e of n)e.style.animationName=""},[u]),e.useEffect(()=>{if("visible"!==n)return;const e=s.current;if(!e||"undefined"==typeof IntersectionObserver)return void l(!0);const t=new IntersectionObserver(([e])=>{e.isIntersecting?(d(),a&&t.disconnect()):a||l(!1)},{threshold:i});return t.observe(e),()=>t.disconnect()},[n,a,i,d]),e.useEffect(()=>{"manual"===n&&(t?d():l(!1))},[n,t,d]);const f="hover"===n?{onPointerEnter:d,onFocus:d,onPointerLeave:()=>{r&&l(!1)},onBlur:()=>{r&&l(!1)}}:{};return{ref:e.useCallback(e=>{s.current=e},[]),state:c&&!o?"running":"paused",started:c,handlers:f}}export function useAnimateElement(e){const{effect:n,trigger:t,play:a,once:i,threshold:o,paused:r,infinite:s,...c}=e,l=useAnimationRun({trigger:t,play:a,once:i,threshold:o,paused:r,infinite:s});return{ref:l.ref,className:n?`neba-anim ${animationClasses[n]}`:"",style:{...animationSlots(c),"--n-anim-state":l.state},props:{...l.handlers,"data-neba-animation":n??void 0,"data-state":l.state}}}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Colour arithmetic, written out rather than installed.
|
|
3
|
+
*
|
|
4
|
+
* ColorPicker is the only component that has to *compute* a colour rather than
|
|
5
|
+
* name one, and the whole of what it needs is here: three representations, the
|
|
6
|
+
* conversions between them, one parser and one formatter. It is a hundred lines
|
|
7
|
+
* of trigonometry-free arithmetic, which is the entire reason the library still
|
|
8
|
+
* has one runtime dependency.
|
|
9
|
+
*
|
|
10
|
+
* Two decisions worth knowing before reading:
|
|
11
|
+
*
|
|
12
|
+
* - **HSV is the model the panel is drawn in, and it never leaves.** A
|
|
13
|
+
* saturation/value square with a hue rail beside it *is* HSV, so the picker
|
|
14
|
+
* keeps its state that way and converts on the way out. Round-tripping
|
|
15
|
+
* through RGB instead would lose the hue of every greyscale colour — black is
|
|
16
|
+
* `#000` at every hue — and the rail would jump to red the moment the pointer
|
|
17
|
+
* reached a corner.
|
|
18
|
+
* - **`alpha` is 0–1, everything else is what CSS says it is.** `h` is degrees,
|
|
19
|
+
* `s`/`v`/`l` are percentages, `r`/`g`/`b` are 0–255. No component invents a
|
|
20
|
+
* normalised unit, because every one of these numbers is eventually written
|
|
21
|
+
* into a string a human reads.
|
|
22
|
+
*
|
|
23
|
+
* Nothing here is exported from `src/index.ts`. It is the library talking to
|
|
24
|
+
* itself, in the sense that `internal/` always means.
|
|
25
|
+
*/
|
|
26
|
+
/** 0–255 per channel, the way a hex triplet and `rgb()` both spell it. */
|
|
27
|
+
export interface Rgb {
|
|
28
|
+
r: number;
|
|
29
|
+
g: number;
|
|
30
|
+
b: number;
|
|
31
|
+
}
|
|
32
|
+
/** `h` in degrees, `s` and `v` as percentages. The panel's own model. */
|
|
33
|
+
export interface Hsv {
|
|
34
|
+
h: number;
|
|
35
|
+
s: number;
|
|
36
|
+
v: number;
|
|
37
|
+
}
|
|
38
|
+
/** `h` in degrees, `s` and `l` as percentages. Only ever an output format. */
|
|
39
|
+
export interface Hsl {
|
|
40
|
+
h: number;
|
|
41
|
+
s: number;
|
|
42
|
+
l: number;
|
|
43
|
+
}
|
|
44
|
+
/** A colour and how much of it there is. `alpha` is 0–1. */
|
|
45
|
+
export interface ColorValue {
|
|
46
|
+
hsv: Hsv;
|
|
47
|
+
alpha: number;
|
|
48
|
+
}
|
|
49
|
+
/** The three ways the picker will write a colour back out. */
|
|
50
|
+
export type ColorFormat = 'hex' | 'rgb' | 'hsl';
|
|
51
|
+
export declare function clamp(value: number, min: number, max: number): number;
|
|
52
|
+
export declare function hsvToRgb({ h, s, v }: Hsv): Rgb;
|
|
53
|
+
export declare function rgbToHsv({ r, g, b }: Rgb): Hsv;
|
|
54
|
+
export declare function rgbToHsl({ r, g, b }: Rgb): Hsl;
|
|
55
|
+
export declare function hslToRgb({ h, s, l }: Hsl): Rgb;
|
|
56
|
+
/**
|
|
57
|
+
* A CSS colour string, or `null` if it is not one this understands.
|
|
58
|
+
*
|
|
59
|
+
* Hex in all four lengths, `rgb()`/`rgba()` and `hsl()`/`hsla()` in both the
|
|
60
|
+
* comma and the space syntax. Named colours and `color()` are deliberately out:
|
|
61
|
+
* a picker has to be able to write every value it can read, and there is no
|
|
62
|
+
* honest way back from `rebeccapurple` to a point on the panel.
|
|
63
|
+
*/
|
|
64
|
+
export declare function parseColor(input: string): ColorValue | null;
|
|
65
|
+
export declare function rgbToHex({ r, g, b }: Rgb, alpha?: number): string;
|
|
66
|
+
/**
|
|
67
|
+
* The value the component hands back, in whichever notation was asked for.
|
|
68
|
+
*
|
|
69
|
+
* `hex` drops the alpha pair when the colour is opaque, and the two functional
|
|
70
|
+
* forms drop the fourth argument for the same reason — a caller who never
|
|
71
|
+
* turned `alpha` on should never see `rgba(…, 1)` come out of a control they
|
|
72
|
+
* only used three channels of.
|
|
73
|
+
*/
|
|
74
|
+
export declare function formatColor(hsv: Hsv, alpha: number, format: ColorFormat): string;
|
|
75
|
+
/** A plain `rgb()` for the swatches and rails, where alpha is drawn separately. */
|
|
76
|
+
export declare function cssColor(hsv: Hsv, alpha?: number): string;
|
|
77
|
+
/**
|
|
78
|
+
* Black or white, whichever can be read on top of this colour.
|
|
79
|
+
*
|
|
80
|
+
* The tick on a chosen swatch is the only thing the picker draws *on* an
|
|
81
|
+
* arbitrary colour, and a fixed white tick disappears on yellow. The threshold
|
|
82
|
+
* is relative luminance rather than plain lightness, because the eye weighs
|
|
83
|
+
* green about six times as heavily as blue and a colour model that pretends
|
|
84
|
+
* otherwise puts the tick the wrong way round on both.
|
|
85
|
+
*/
|
|
86
|
+
export declare function readableInk(hsv: Hsv): string;
|
|
87
|
+
/**
|
|
88
|
+
* The swatches a picker shows when it was given none.
|
|
89
|
+
*
|
|
90
|
+
* Deliberately not the library's own six families: those are semantic roles and
|
|
91
|
+
* a picker is asked for a colour, not for a meaning. This is a plain spectrum
|
|
92
|
+
* plus the greys — the row somebody reaches for when they are labelling a
|
|
93
|
+
* calendar, a tag or a project, which is what a colour picker in an application
|
|
94
|
+
* is nearly always for.
|
|
95
|
+
*/
|
|
96
|
+
export declare const defaultSwatches: readonly string[];
|
|
97
|
+
/**
|
|
98
|
+
* The chequerboard behind a translucent colour, as a background shorthand.
|
|
99
|
+
*
|
|
100
|
+
* Two conic gradients would be one declaration fewer, but a chequer drawn with
|
|
101
|
+
* conic gradients has a seam down the middle of every tile on a fractional
|
|
102
|
+
* device pixel ratio. Four linear stops at 45° do not.
|
|
103
|
+
*/
|
|
104
|
+
export declare const checkerBackground: {
|
|
105
|
+
readonly backgroundImage: "linear-gradient(45deg, var(--neba-border) 25%, transparent 25%), linear-gradient(-45deg, var(--neba-border) 25%, transparent 25%), linear-gradient(45deg, transparent 75%, var(--neba-border) 75%), linear-gradient(-45deg, transparent 75%, var(--neba-border) 75%)";
|
|
106
|
+
readonly backgroundSize: "8px 8px";
|
|
107
|
+
readonly backgroundPosition: "0 0, 0 4px, 4px -4px, -4px 0";
|
|
108
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function clamp(r,n,t){return r<n?n:r>t?t:r}function r(r){const n=r%360;return n<0?n+360:n}function n(r){return Math.round(r)}export function hsvToRgb({h:t,s:e,v:o}){const a=r(t)/60,s=clamp(e,0,100)/100,c=clamp(o,0,100)/100,g=c*s,u=g*(1-Math.abs(a%2-1)),l=c-g,b=Math.floor(a)%6,[i,f,h]=[[g,u,0],[u,g,0],[0,g,u],[0,u,g],[u,0,g],[g,0,u]][b];return{r:n(255*(i+l)),g:n(255*(f+l)),b:n(255*(h+l))}}export function rgbToHsv({r:n,g:t,b:e}){const o=clamp(n,0,255)/255,a=clamp(t,0,255)/255,s=clamp(e,0,255)/255,c=Math.max(o,a,s),g=c-Math.min(o,a,s);let u=0;return 0!==g&&(u=c===o?(a-s)/g%6:c===a?(s-o)/g+2:(o-a)/g+4),{h:r(60*u),s:0===c?0:g/c*100,v:100*c}}export function rgbToHsl({r:r,g:n,b:t}){const{h:e,s:o,v:a}=rgbToHsv({r:r,g:n,b:t}),s=a/100,c=s*(1-o/100/2),g=Math.min(c,1-c);return{h:e,s:0===g?0:(s-c)/g*100,l:100*c}}export function hslToRgb({h:r,s:n,l:t}){const e=clamp(t,0,100)/100,o=e+clamp(n,0,100)/100*Math.min(e,1-e);return hsvToRgb({h:r,s:0===o?0:2*(1-e/o)*100,v:100*o})}const t=/^#?([0-9a-f]{3,8})$/i;export function parseColor(r){const n=r.trim().toLowerCase();if(!n)return null;if(n.startsWith("#")||t.test(n))return function(r){const n=t.exec(r.trim());if(!n)return null;const e=n[1],o=3===e.length||4===e.length?[...e].map(r=>r+r).join(""):e;if(6!==o.length&&8!==o.length)return null;const a=r=>parseInt(o.slice(2*r,2*r+2),16);return{hsv:rgbToHsv({r:a(0),g:a(1),b:a(2)}),alpha:8===o.length?a(3)/255:1}}(n);const e=/^(rgba?|hsla?)\((.*)\)$/.exec(n);if(!e)return null;const[,o,a]=e,s=function(r){return(r.match(/-?[\d.]+%?/g)??[]).map(r=>(r.endsWith("%"),parseFloat(r)))}(a);if(s.length<3||s.some(Number.isNaN))return null;const c=function(r,n,t){if(n.length<=t)return 1;const e=r.match(/-?[\d.]+%?/g)??[];return clamp(e[t]?.endsWith("%")?n[t]/100:n[t],0,1)}(a,s,3);return o.startsWith("rgb")?{hsv:rgbToHsv({r:s[0],g:s[1],b:s[2]}),alpha:c}:{hsv:rgbToHsv(hslToRgb({h:s[0],s:s[1],l:s[2]})),alpha:c}}function e(r){return clamp(n(r),0,255).toString(16).padStart(2,"0")}export function rgbToHex({r:r,g:n,b:t},o=1){const a=`#${e(r)}${e(n)}${e(t)}`;return o>=1?a:`${a}${e(255*o)}`}function o(r){return String(Math.round(100*clamp(r,0,1))/100)}export function formatColor(r,t,e){const a=hsvToRgb(r);if("hex"===e)return rgbToHex(a,t);if("rgb"===e){const r=`${n(a.r)}, ${n(a.g)}, ${n(a.b)}`;return t>=1?`rgb(${r})`:`rgba(${r}, ${o(t)})`}const{h:s,s:c,l:g}=rgbToHsl(a),u=`${n(s)}, ${n(c)}%, ${n(g)}%`;return t>=1?`hsl(${u})`:`hsla(${u}, ${o(t)})`}export function cssColor(r,n=1){const{r:t,g:e,b:a}=hsvToRgb(r);return n>=1?`rgb(${t}, ${e}, ${a})`:`rgba(${t}, ${e}, ${a}, ${o(n)})`}export function readableInk(r){const{r:n,g:t,b:e}=hsvToRgb(r),o=r=>{const n=r/255;return n<=.03928?n/12.92:((n+.055)/1.055)**2.4};return.2126*o(n)+.7152*o(t)+.0722*o(e)>.42?"#000000":"#ffffff"}export const defaultSwatches=["#000000","#4b5563","#9ca3af","#ffffff","#ef4444","#f97316","#f59e0b","#84cc16","#22c55e","#14b8a6","#06b6d4","#3b82f6","#6366f1","#8b5cf6","#d946ef","#ec4899"];export const checkerBackground={backgroundImage:"linear-gradient(45deg, var(--neba-border) 25%, transparent 25%), linear-gradient(-45deg, var(--neba-border) 25%, transparent 25%), linear-gradient(45deg, transparent 75%, var(--neba-border) 75%), linear-gradient(-45deg, transparent 75%, var(--neba-border) 75%)",backgroundSize:"8px 8px",backgroundPosition:"0 0, 0 4px, 4px -4px, -4px 0"};
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The arithmetic a DataTable is made of.
|
|
3
|
+
*
|
|
4
|
+
* Here rather than in the component for the reason `progress.ts` and `color.ts`
|
|
5
|
+
* are here: none of it is layout and none of it is a class name. Sorting rows,
|
|
6
|
+
* matching a query against them, cutting a page out of the result and working
|
|
7
|
+
* out which twenty of forty thousand rows are actually on screen are four
|
|
8
|
+
* questions with numeric answers, and a component file that also has to draw a
|
|
9
|
+
* resize handle is not where they can be read.
|
|
10
|
+
*
|
|
11
|
+
* `color.ts` is the precedent for a single-consumer file: the test is whether
|
|
12
|
+
* the thing is separable, not whether two components happen to want it. Every
|
|
13
|
+
* function below takes plain values and returns plain values — there is no
|
|
14
|
+
* React in this file, and nothing in it knows what a column is.
|
|
15
|
+
*/
|
|
16
|
+
import type { NebaDensity, NebaSize } from '../types';
|
|
17
|
+
/**
|
|
18
|
+
* Row height, in **pixels**, and the one number in the library that has to be a
|
|
19
|
+
* number rather than a class.
|
|
20
|
+
*
|
|
21
|
+
* Virtual scrolling is arithmetic on this value: how far down the list a scroll
|
|
22
|
+
* offset lands, how tall the spacer above the first rendered row is, how many
|
|
23
|
+
* rows fit in the viewport for PageDown. A Tailwind class cannot be added up.
|
|
24
|
+
*
|
|
25
|
+
* The ladder is deliberately below `controlHeightClasses` at every step — 28px
|
|
26
|
+
* at `md` against a Button's 32 — because a DataTable is a hundred rows on one
|
|
27
|
+
* screen and a Table is eight. A row here is the height of the text plus enough
|
|
28
|
+
* air to click, and nothing more; `density` is what takes the last of the air
|
|
29
|
+
* out. A caller who puts a Chip or an Avatar in a cell raises `rowHeight`.
|
|
30
|
+
*/
|
|
31
|
+
export declare const dataRowHeights: Record<NebaDensity, Record<NebaSize, number>>;
|
|
32
|
+
/**
|
|
33
|
+
* The header, one step taller than the rows under it.
|
|
34
|
+
*
|
|
35
|
+
* A fixed number rather than "whatever the content measures", because a sticky
|
|
36
|
+
* header over a group row needs an offset to stick the second row at, and a
|
|
37
|
+
* measured one would be a layout read on every scroll frame.
|
|
38
|
+
*/
|
|
39
|
+
export declare const dataHeaderHeights: Record<NebaDensity, Record<NebaSize, number>>;
|
|
40
|
+
/**
|
|
41
|
+
* The tick column, and the resize handle's grab area.
|
|
42
|
+
*
|
|
43
|
+
* The tick column is `tickSizeClasses` plus the two padding tracks, rounded to
|
|
44
|
+
* a whole pixel: a column narrower than the box in it clips the focus ring.
|
|
45
|
+
*/
|
|
46
|
+
export declare const dataTickWidths: Record<NebaSize, number>;
|
|
47
|
+
/** How wide a column is when neither the caller nor a drag has said. */
|
|
48
|
+
export declare const defaultColumnWidth = 160;
|
|
49
|
+
/** How narrow a drag may make one. Below this the heading is a single letter. */
|
|
50
|
+
export declare const minColumnWidth = 48;
|
|
51
|
+
export type SortDirection = 'asc' | 'desc';
|
|
52
|
+
/** One key of a sort, and which way it runs. */
|
|
53
|
+
export interface SortEntry {
|
|
54
|
+
key: string;
|
|
55
|
+
direction: SortDirection;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The default comparison, for the columns that do not bring one.
|
|
59
|
+
*
|
|
60
|
+
* Empty sorts last in both directions, which is the one asymmetry here and the
|
|
61
|
+
* one every spreadsheet has: a blank is not the smallest value, it is the
|
|
62
|
+
* absence of one, and a descending sort whose first screen is forty blanks has
|
|
63
|
+
* answered the wrong question.
|
|
64
|
+
*
|
|
65
|
+
* Numbers compare as numbers, dates as instants, booleans with `false` first,
|
|
66
|
+
* and everything else through the collator — which is `numeric`, so `item2`
|
|
67
|
+
* comes before `item10` rather than after it.
|
|
68
|
+
*/
|
|
69
|
+
export declare function compareValues(a: unknown, b: unknown, collator: Intl.Collator): number;
|
|
70
|
+
/**
|
|
71
|
+
* Sorts by every key at once, first key outermost.
|
|
72
|
+
*
|
|
73
|
+
* The sort is **stable** — `Array.prototype.sort` has been since ES2019 — which
|
|
74
|
+
* is what makes a multi-key sort composable: sorting by name and then adding
|
|
75
|
+
* date to the end of the list leaves rows with the same date in name order,
|
|
76
|
+
* because that is the order they were already in.
|
|
77
|
+
*
|
|
78
|
+
* `comparatorFor` returns `null` for a key no column claims, so a sort left
|
|
79
|
+
* over from a column that has since been removed is skipped rather than
|
|
80
|
+
* throwing.
|
|
81
|
+
*/
|
|
82
|
+
export declare function sortRows<T>(rows: readonly T[], sort: readonly SortEntry[], comparatorFor: (key: string) => ((a: T, b: T) => number) | null): T[];
|
|
83
|
+
/**
|
|
84
|
+
* What pressing a heading does, given what the sort already says.
|
|
85
|
+
*
|
|
86
|
+
* Three states rather than two — ascending, descending, unsorted — because the
|
|
87
|
+
* order the rows arrived in is a state a caller cannot get back to by pressing
|
|
88
|
+
* anything if the cycle only has two. This is the opposite of the choice
|
|
89
|
+
* TreeView's single select makes, and for the same reason: there, "nothing
|
|
90
|
+
* chosen" is reachable by pressing something else; here it is not.
|
|
91
|
+
*
|
|
92
|
+
* `additive` is what a Shift-click sets. It keeps the column where it already
|
|
93
|
+
* is in the list rather than moving it to the end, so flipping the second key
|
|
94
|
+
* of a two-key sort does not silently make it the first.
|
|
95
|
+
*/
|
|
96
|
+
export declare function nextSort(current: readonly SortEntry[], key: string, additive: boolean): SortEntry[];
|
|
97
|
+
/**
|
|
98
|
+
* A cell value as something a query can be matched against.
|
|
99
|
+
*
|
|
100
|
+
* Case-folded and stripped of combining marks, so `jose` finds `José` and
|
|
101
|
+
* `SEOUL` finds `Seoul`. `NFD` splits an accented letter into the letter and
|
|
102
|
+
* its accent and the range then deletes the accent; a search field is the one
|
|
103
|
+
* place where losing that distinction is the point.
|
|
104
|
+
*
|
|
105
|
+
* A `Date` is deliberately *not* formatted here. What a reader sees in the cell
|
|
106
|
+
* came out of the caller's `render`, and guessing a format the search would
|
|
107
|
+
* agree with is how a table ends up not finding a date that is on the screen —
|
|
108
|
+
* a column that wants its dates searchable gives them a `value`.
|
|
109
|
+
*/
|
|
110
|
+
export declare function searchText(value: unknown): string;
|
|
111
|
+
/** Where one page starts and stops, and how many there are. */
|
|
112
|
+
export interface PageBounds {
|
|
113
|
+
/** How many pages the rows come to. Never below `1`. */
|
|
114
|
+
pages: number;
|
|
115
|
+
/** The page actually shown, clamped into range. */
|
|
116
|
+
page: number;
|
|
117
|
+
/** Index of the first row on it. */
|
|
118
|
+
start: number;
|
|
119
|
+
/** One past the last. */
|
|
120
|
+
end: number;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* The page arithmetic, with the clamp in it.
|
|
124
|
+
*
|
|
125
|
+
* The clamp is the whole reason this is a function: a filter that cuts a
|
|
126
|
+
* twenty-page table to three leaves the caller's `page` at 14, and a table that
|
|
127
|
+
* answers with an empty screen has told the reader their search found nothing.
|
|
128
|
+
* The page moves to the last one that exists instead.
|
|
129
|
+
*/
|
|
130
|
+
export declare function pageBounds(total: number, page: number, pageSize: number): PageBounds;
|
|
131
|
+
/** Which rows are rendered, and how much empty space stands in for the rest. */
|
|
132
|
+
export interface VirtualWindow {
|
|
133
|
+
/** Index of the first row rendered. */
|
|
134
|
+
start: number;
|
|
135
|
+
/** One past the last. */
|
|
136
|
+
end: number;
|
|
137
|
+
/** The height of the spacer row above, in pixels. */
|
|
138
|
+
before: number;
|
|
139
|
+
/** And of the one below. */
|
|
140
|
+
after: number;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* The rows a scroll offset puts on screen, plus a margin either side.
|
|
144
|
+
*
|
|
145
|
+
* Two spacer rows rather than a translated `<tbody>`: a transform on the body
|
|
146
|
+
* of a table breaks `position: sticky` on the header above it, and the library
|
|
147
|
+
* has a rule about transforms anyway. Padding is also what keeps the scrollbar
|
|
148
|
+
* honest — the scroll height is the real one, so dragging the thumb to the
|
|
149
|
+
* middle lands in the middle of the data.
|
|
150
|
+
*
|
|
151
|
+
* `viewport` of `0` is the first render, before anything has been measured. It
|
|
152
|
+
* cannot mean "no rows" — the layout effect that measures runs after a render
|
|
153
|
+
* and there would be nothing to measure — so it renders a screen's worth on
|
|
154
|
+
* spec and corrects before the frame is painted.
|
|
155
|
+
*/
|
|
156
|
+
export declare function virtualWindow(scrollTop: number, viewport: number, rowHeight: number, count: number, overscan: number): VirtualWindow;
|
|
157
|
+
/**
|
|
158
|
+
* Every key between two, inclusive, in the order the rows are currently in.
|
|
159
|
+
*
|
|
160
|
+
* "Currently" is the point: a Shift-click selects what the reader can see
|
|
161
|
+
* between the two rows they clicked, so the range is taken from the sorted,
|
|
162
|
+
* filtered order rather than from the order `items` arrived in. Which of the
|
|
163
|
+
* two keys is the anchor does not matter — dragging up selects the same rows as
|
|
164
|
+
* dragging down.
|
|
165
|
+
*
|
|
166
|
+
* A key that is no longer in the list (a row the current filter hides) yields
|
|
167
|
+
* an empty range rather than a range from one end, which is what an index of
|
|
168
|
+
* `-1` would silently produce.
|
|
169
|
+
*/
|
|
170
|
+
export declare function keysBetween(order: readonly string[], from: string, to: string): string[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const dataRowHeights={default:{xs:24,sm:28,md:32,lg:38,xl:44},compact:{xs:20,sm:24,md:28,lg:32,xl:38}};export const dataHeaderHeights={default:{xs:28,sm:32,md:36,lg:42,xl:48},compact:{xs:24,sm:28,md:32,lg:36,xl:42}};export const dataTickWidths={xs:30,sm:34,md:38,lg:42,xl:50};export const defaultColumnWidth=160;export const minColumnWidth=48;export function compareValues(t,e,n){const o=null==t||""===t,r=null==e||""===e;return o||r?o&&r?0:o?1:-1:"number"==typeof t&&"number"==typeof e?Number.isNaN(t)||Number.isNaN(e)?Number.isNaN(t)&&Number.isNaN(e)?0:Number.isNaN(t)?1:-1:t-e:"boolean"==typeof t&&"boolean"==typeof e?Number(t)-Number(e):t instanceof Date&&e instanceof Date?t.getTime()-e.getTime():n.compare(String(t),String(e))}export function sortRows(t,e,n){if(0===e.length)return t;const o=e.map(t=>{const e=n(t.key);return e?{compare:e,sign:"desc"===t.direction?-1:1}:null}).filter(t=>null!==t);return 0===o.length?t:[...t].sort((t,e)=>{for(const n of o){const o=n.compare(t,e);if(0!==o)return o*n.sign}return 0})}export function nextSort(t,e,n){const o=t.find(t=>t.key===e),r=void 0===o?"asc":"asc"===o.direction?"desc":null;return n?null===r?t.filter(t=>t.key!==e):void 0===o?[...t,{key:e,direction:r}]:t.map(t=>t.key===e?{key:e,direction:r}:t):null===r?[]:[{key:e,direction:r}]}export function searchText(t){return null==t||"object"==typeof t?"":String(t).normalize("NFD").replace(/[\u0300-\u036f]/g,"").toLowerCase()}export function pageBounds(t,e,n){const o=Math.max(1,Math.floor(n)),r=Math.max(1,Math.ceil(t/o)),a=Math.min(Math.max(Math.floor(e),1),r),i=(a-1)*o;return{pages:r,page:a,start:i,end:Math.min(i+o,t)}}export function virtualWindow(t,e,n,o,r){const a=e>0?Math.ceil(e/n):3*r,i=Math.max(0,Math.floor(t/n)-r),s=Math.min(o,i+a+2*r);return{start:i,end:s,before:i*n,after:Math.max(0,(o-s)*n)}}export function keysBetween(t,e,n){const o=t.indexOf(e),r=t.indexOf(n);return-1===o||-1===r?[]:t.slice(Math.min(o,r),Math.max(o,r)+1)}
|
package/dist/internal/i18n.d.ts
CHANGED
|
@@ -62,6 +62,71 @@ export interface NebaMessages {
|
|
|
62
62
|
/** What the three dots mean. */
|
|
63
63
|
typing: string;
|
|
64
64
|
};
|
|
65
|
+
/** Empty. */
|
|
66
|
+
empty: {
|
|
67
|
+
/**
|
|
68
|
+
* The headline over a region with nothing in it.
|
|
69
|
+
*
|
|
70
|
+
* The one string in here that is *drawn* at full size rather than read out
|
|
71
|
+
* or written on a button, and it is here for the same reason the rest are:
|
|
72
|
+
* an empty state has nothing to take its words from. What is missing is the
|
|
73
|
+
* caller's — a search, an inbox, a folder — so the default says only that
|
|
74
|
+
* there is nothing, and anything more specific is the `title` prop.
|
|
75
|
+
*/
|
|
76
|
+
title: string;
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* DataTable.
|
|
80
|
+
*
|
|
81
|
+
* A DataTable is the one component in the library that draws chrome of its
|
|
82
|
+
* own — a search field, a header tick that chooses everything at once, a
|
|
83
|
+
* footer counting what is on screen — so it is also the one that has the most
|
|
84
|
+
* to say without being handed the words.
|
|
85
|
+
*/
|
|
86
|
+
table: {
|
|
87
|
+
/** Placeholder and accessible name of the search field. */
|
|
88
|
+
search: string;
|
|
89
|
+
/** The tick in the header row, which chooses every row at once. */
|
|
90
|
+
selectAll: string;
|
|
91
|
+
/** The tick on a row. */
|
|
92
|
+
selectRow: string;
|
|
93
|
+
/** Beside the footer's page-size Select. */
|
|
94
|
+
rowsPerPage: string;
|
|
95
|
+
/**
|
|
96
|
+
* Which rows are on screen, out of how many there are. `{start}`, `{end}`
|
|
97
|
+
* and `{total}` are replaced with the numbers, already formatted for the
|
|
98
|
+
* locale — a template rather than three fragments, because the order the
|
|
99
|
+
* three appear in is exactly what differs between languages.
|
|
100
|
+
*/
|
|
101
|
+
range: string;
|
|
102
|
+
/** How many rows are chosen. `{count}` is replaced the same way. */
|
|
103
|
+
selected: string;
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* ColorPicker.
|
|
107
|
+
*
|
|
108
|
+
* All but one of these are names for something with no text on it: a square
|
|
109
|
+
* of colour, two rails, a grid of swatches. A control a sighted reader
|
|
110
|
+
* understands from a glance at a gradient is a control that says nothing at
|
|
111
|
+
* all without them, which makes this the one namespace here where the strings
|
|
112
|
+
* are the entire accessible interface rather than a convenience on top of it.
|
|
113
|
+
*/
|
|
114
|
+
color: {
|
|
115
|
+
/** The saturation/brightness square. */
|
|
116
|
+
area: string;
|
|
117
|
+
/** The rail beside it. */
|
|
118
|
+
hue: string;
|
|
119
|
+
/** The rail below that, when `alpha` is on. */
|
|
120
|
+
alpha: string;
|
|
121
|
+
/** The field the colour can be typed into. */
|
|
122
|
+
value: string;
|
|
123
|
+
/** The grid of ready-made colours. */
|
|
124
|
+
swatches: string;
|
|
125
|
+
/** The × that empties the control. */
|
|
126
|
+
clear: string;
|
|
127
|
+
/** What the trigger reads before anything has been chosen. */
|
|
128
|
+
empty: string;
|
|
129
|
+
};
|
|
65
130
|
}
|
|
66
131
|
/**
|
|
67
132
|
* The strings for a locale, merged over English.
|
|
@@ -76,3 +141,14 @@ export interface NebaMessages {
|
|
|
76
141
|
export declare function resolveMessages(locale?: string): NebaMessages;
|
|
77
142
|
/** The same, as a hook, for the components that read it during a render. */
|
|
78
143
|
export declare function useMessages(locale?: string): NebaMessages;
|
|
144
|
+
/**
|
|
145
|
+
* Fills the `{placeholders}` in one of the strings above.
|
|
146
|
+
*
|
|
147
|
+
* Two of them carry numbers, and neither could be assembled out of fragments:
|
|
148
|
+
* "1–25 of 1,204" is "전체 1,204개 중 1–25" in Korean and "1,204 kayıttan 1–25"
|
|
149
|
+
* in Turkish, so what differs between languages is the *order*, which only a
|
|
150
|
+
* whole sentence can state. A `{name}` with no value is left alone rather than
|
|
151
|
+
* blanked, so a mistranslated placeholder shows up as itself instead of as a
|
|
152
|
+
* hole in the middle of a sentence.
|
|
153
|
+
*/
|
|
154
|
+
export declare function fillMessage(template: string, values: Record<string, string>): string;
|
package/dist/internal/i18n.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import*as e from"react";const n={link:{newTab:"(opens in a new tab)"},spoiler:{reveal:"Reveal",hide:"Hide",notice:"This may contain spoilers"},chat:{sending:"Sending",sent:"Sent",delivered:"Delivered",read:"Read",failed:"Not sent",typing:"Typing…"}},i={ko:{link:{newTab:"(새 창에서 열림)"},spoiler:{reveal:"내용 보기",hide:"숨기기",notice:"스포일러가 포함되어 있을 수 있습니다"},chat:{sending:"보내는 중",sent:"보냄",delivered:"전달됨",read:"읽음",failed:"전송 실패",typing:"입력 중…"}},ja:{link:{newTab:"(新しいタブで開きます)"},spoiler:{reveal:"表示する",hide:"隠す",notice:"ネタバレを含む可能性があります"},chat:{sending:"送信中",sent:"送信済み",delivered:"配信済み",read:"既読",failed:"送信できませんでした",typing:"入力中…"}},"zh-hans":{link:{newTab:"(在新标签页中打开)"},spoiler:{reveal:"显示内容",hide:"隐藏",notice:"此内容可能包含剧透"},chat:{sending:"发送中",sent:"已发送",delivered:"已送达",read:"已读",failed:"发送失败",typing:"正在输入…"}},"zh-hant":{link:{newTab:"(在新分頁中開啟)"},spoiler:{reveal:"顯示內容",hide:"隱藏",notice:"此內容可能包含劇透"},chat:{sending:"傳送中",sent:"已傳送",delivered:"已送達",read:"已讀",failed:"傳送失敗",typing:"正在輸入…"}},es:{link:{newTab:"(se abre en una pestaña nueva)"},spoiler:{reveal:"Mostrar",hide:"Ocultar",notice:"Puede contener spoilers"},chat:{sending:"Enviando",sent:"Enviado",delivered:"Entregado",read:"Leído",failed:"No enviado",typing:"Escribiendo…"}},pt:{link:{newTab:"(abre em uma nova aba)"},spoiler:{reveal:"Mostrar",hide:"Ocultar",notice:"Pode conter spoilers"},chat:{sending:"Enviando",sent:"Enviado",delivered:"Entregue",read:"Lido",failed:"Não enviado",typing:"Digitando…"}},fr:{link:{newTab:"(s’ouvre dans un nouvel onglet)"},spoiler:{reveal:"Afficher",hide:"Masquer",notice:"Peut contenir des spoilers"},chat:{sending:"Envoi en cours",sent:"Envoyé",delivered:"Distribué",read:"Lu",failed:"Non envoyé",typing:"En train d’écrire…"}},de:{link:{newTab:"(wird in einem neuen Tab geöffnet)"},spoiler:{reveal:"Anzeigen",hide:"Ausblenden",notice:"Kann Spoiler enthalten"},chat:{sending:"Wird gesendet",sent:"Gesendet",delivered:"Zugestellt",read:"Gelesen",failed:"Nicht gesendet",typing:"Schreibt…"}},it:{link:{newTab:"(si apre in una nuova scheda)"},spoiler:{reveal:"Mostra",hide:"Nascondi",notice:"Può contenere spoiler"},chat:{sending:"Invio in corso",sent:"Inviato",delivered:"Consegnato",read:"Letto",failed:"Non inviato",typing:"Sta scrivendo…"}},nl:{link:{newTab:"(opent in een nieuw tabblad)"},spoiler:{reveal:"Tonen",hide:"Verbergen",notice:"Kan spoilers bevatten"},chat:{sending:"Wordt verzonden",sent:"Verzonden",delivered:"Afgeleverd",read:"Gelezen",failed:"Niet verzonden",typing:"Aan het typen…"}},pl:{link:{newTab:"(otwiera się w nowej karcie)"},spoiler:{reveal:"Pokaż",hide:"Ukryj",notice:"Może zawierać spoilery"},chat:{sending:"Wysyłanie",sent:"Wysłano",delivered:"Dostarczono",read:"Przeczytano",failed:"Nie wysłano",typing:"Pisze…"}},ru:{link:{newTab:"(откроется в новой вкладке)"},spoiler:{reveal:"Показать",hide:"Скрыть",notice:"Может содержать спойлеры"},chat:{sending:"Отправляется",sent:"Отправлено",delivered:"Доставлено",read:"Прочитано",failed:"Не отправлено",typing:"Печатает…"}},tr:{link:{newTab:"(yeni sekmede açılır)"},spoiler:{reveal:"Göster",hide:"Gizle",notice:"Spoiler içerebilir"},chat:{sending:"Gönderiliyor",sent:"Gönderildi",delivered:"İletildi",read:"Okundu",failed:"Gönderilemedi",typing:"Yazıyor…"}},ar:{link:{newTab:"(يفتح في علامة تبويب جديدة)"},spoiler:{reveal:"إظهار",hide:"إخفاء",notice:"قد يحتوي على حرق للأحداث"},chat:{sending:"جارٍ الإرسال",sent:"تم الإرسال",delivered:"تم التسليم",read:"تمت القراءة",failed:"لم يتم الإرسال",typing:"يكتب الآن…"}},hi:{link:{newTab:"(नए टैब में खुलता है)"},spoiler:{reveal:"दिखाएँ",hide:"छिपाएँ",notice:"इसमें स्पॉइलर हो सकते हैं"},chat:{sending:"भेजा जा रहा है",sent:"भेजा गया",delivered:"डिलीवर हुआ",read:"पढ़ा गया",failed:"नहीं भेजा गया",typing:"टाइप कर रहे हैं…"}},id:{link:{newTab:"(terbuka di tab baru)"},spoiler:{reveal:"Tampilkan",hide:"Sembunyikan",notice:"Mungkin mengandung spoiler"},chat:{sending:"Mengirim",sent:"Terkirim",delivered:"Diterima",read:"Dibaca",failed:"Gagal terkirim",typing:"Sedang mengetik…"}},vi:{link:{newTab:"(mở trong tab mới)"},spoiler:{reveal:"Hiện nội dung",hide:"Ẩn",notice:"Có thể chứa nội dung tiết lộ"},chat:{sending:"Đang gửi",sent:"Đã gửi",delivered:"Đã nhận",read:"Đã xem",failed:"Chưa gửi được",typing:"Đang nhập…"}},th:{link:{newTab:"(เปิดในแท็บใหม่)"},spoiler:{reveal:"แสดงเนื้อหา",hide:"ซ่อน",notice:"อาจมีการเปิดเผยเนื้อหา"},chat:{sending:"กำลังส่ง",sent:"ส่งแล้ว",delivered:"ส่งถึงแล้ว",read:"อ่านแล้ว",failed:"ส่งไม่สำเร็จ",typing:"กำลังพิมพ์…"}}},a={zh:"zh-hans","zh-cn":"zh-hans","zh-my":"zh-hans","zh-sg":"zh-hans","zh-hk":"zh-hant","zh-mo":"zh-hant","zh-tw":"zh-hant"};const t=new Map([["",n]]);export function resolveMessages(e){const r=e?.trim()??"",d=t.get(r);if(d)return d;const l=function(e){const n=e.toLowerCase().split(/[-_]/).filter(Boolean),i=n[0];if(!i)return[];const a=n.slice(1),t=a.find(e=>/^[a-z]{4}$/.test(e)),r=a.find(e=>/^([a-z]{2}|\d{3})$/.test(e));return[t?`${i}-${t}`:"",r?`${i}-${r}`:"",i].filter(Boolean)}(r).map(e=>i[e]??i[a[e]??""]).find(Boolean),o=l?{link:{...n.link,...l.link},spoiler:{...n.spoiler,...l.spoiler},chat:{...n.chat,...l.chat}}:n;return t.set(r,o),o}export function useMessages(n){return e.useMemo(()=>resolveMessages(n),[n])}
|
|
1
|
+
import*as e from"react";const a={link:{newTab:"(opens in a new tab)"},spoiler:{reveal:"Reveal",hide:"Hide",notice:"This may contain spoilers"},chat:{sending:"Sending",sent:"Sent",delivered:"Delivered",read:"Read",failed:"Not sent",typing:"Typing…"},empty:{title:"Nothing here"},table:{search:"Search",selectAll:"Select all rows",selectRow:"Select row",rowsPerPage:"Rows per page",range:"{start}–{end} of {total}",selected:"{count} selected"},color:{area:"Saturation and brightness",hue:"Hue",alpha:"Opacity",value:"Colour value",swatches:"Preset colours",clear:"Clear",empty:"Choose a colour"}},t={ko:{link:{newTab:"(새 창에서 열림)"},spoiler:{reveal:"내용 보기",hide:"숨기기",notice:"스포일러가 포함되어 있을 수 있습니다"},chat:{sending:"보내는 중",sent:"보냄",delivered:"전달됨",read:"읽음",failed:"전송 실패",typing:"입력 중…"},empty:{title:"내용이 없습니다"},table:{search:"검색",selectAll:"모든 행 선택",selectRow:"행 선택",rowsPerPage:"페이지당 행 수",range:"전체 {total}개 중 {start}–{end}",selected:"{count}개 선택됨"},color:{area:"채도와 명도",hue:"색상",alpha:"불투명도",value:"색상 값",swatches:"기본 색상",clear:"지우기",empty:"색상 선택"}},ja:{link:{newTab:"(新しいタブで開きます)"},spoiler:{reveal:"表示する",hide:"隠す",notice:"ネタバレを含む可能性があります"},chat:{sending:"送信中",sent:"送信済み",delivered:"配信済み",read:"既読",failed:"送信できませんでした",typing:"入力中…"},empty:{title:"表示するものがありません"},table:{search:"検索",selectAll:"すべての行を選択",selectRow:"行を選択",rowsPerPage:"1 ページの行数",range:"{total} 件中 {start}–{end} 件",selected:"{count} 件を選択中"},color:{area:"彩度と明度",hue:"色相",alpha:"不透明度",value:"カラー値",swatches:"プリセットの色",clear:"クリア",empty:"色を選択"}},"zh-hans":{link:{newTab:"(在新标签页中打开)"},spoiler:{reveal:"显示内容",hide:"隐藏",notice:"此内容可能包含剧透"},chat:{sending:"发送中",sent:"已发送",delivered:"已送达",read:"已读",failed:"发送失败",typing:"正在输入…"},empty:{title:"暂无内容"},table:{search:"搜索",selectAll:"全选所有行",selectRow:"选择此行",rowsPerPage:"每页行数",range:"第 {start}–{end} 行,共 {total} 行",selected:"已选择 {count} 行"},color:{area:"饱和度和明度",hue:"色相",alpha:"不透明度",value:"颜色值",swatches:"预设颜色",clear:"清除",empty:"选择颜色"}},"zh-hant":{link:{newTab:"(在新分頁中開啟)"},spoiler:{reveal:"顯示內容",hide:"隱藏",notice:"此內容可能包含劇透"},chat:{sending:"傳送中",sent:"已傳送",delivered:"已送達",read:"已讀",failed:"傳送失敗",typing:"正在輸入…"},empty:{title:"沒有內容"},table:{search:"搜尋",selectAll:"全選所有列",selectRow:"選擇此列",rowsPerPage:"每頁列數",range:"第 {start}–{end} 列,共 {total} 列",selected:"已選擇 {count} 列"},color:{area:"飽和度與明度",hue:"色相",alpha:"不透明度",value:"顏色值",swatches:"預設顏色",clear:"清除",empty:"選擇顏色"}},es:{link:{newTab:"(se abre en una pestaña nueva)"},spoiler:{reveal:"Mostrar",hide:"Ocultar",notice:"Puede contener spoilers"},chat:{sending:"Enviando",sent:"Enviado",delivered:"Entregado",read:"Leído",failed:"No enviado",typing:"Escribiendo…"},empty:{title:"No hay nada aquí"},table:{search:"Buscar",selectAll:"Seleccionar todas las filas",selectRow:"Seleccionar fila",rowsPerPage:"Filas por página",range:"{start}–{end} de {total}",selected:"{count} seleccionadas"},color:{area:"Saturación y brillo",hue:"Tono",alpha:"Opacidad",value:"Valor del color",swatches:"Colores predefinidos",clear:"Borrar",empty:"Elegir un color"}},pt:{link:{newTab:"(abre em uma nova aba)"},spoiler:{reveal:"Mostrar",hide:"Ocultar",notice:"Pode conter spoilers"},chat:{sending:"Enviando",sent:"Enviado",delivered:"Entregue",read:"Lido",failed:"Não enviado",typing:"Digitando…"},empty:{title:"Nada por aqui"},table:{search:"Pesquisar",selectAll:"Selecionar todas as linhas",selectRow:"Selecionar linha",rowsPerPage:"Linhas por página",range:"{start}–{end} de {total}",selected:"{count} selecionadas"},color:{area:"Saturação e brilho",hue:"Matiz",alpha:"Opacidade",value:"Valor da cor",swatches:"Cores predefinidas",clear:"Limpar",empty:"Escolher uma cor"}},fr:{link:{newTab:"(s’ouvre dans un nouvel onglet)"},spoiler:{reveal:"Afficher",hide:"Masquer",notice:"Peut contenir des spoilers"},chat:{sending:"Envoi en cours",sent:"Envoyé",delivered:"Distribué",read:"Lu",failed:"Non envoyé",typing:"En train d’écrire…"},empty:{title:"Rien ici"},table:{search:"Rechercher",selectAll:"Sélectionner toutes les lignes",selectRow:"Sélectionner la ligne",rowsPerPage:"Lignes par page",range:"{start}–{end} sur {total}",selected:"{count} sélectionnées"},color:{area:"Saturation et luminosité",hue:"Teinte",alpha:"Opacité",value:"Valeur de la couleur",swatches:"Couleurs prédéfinies",clear:"Effacer",empty:"Choisir une couleur"}},de:{link:{newTab:"(wird in einem neuen Tab geöffnet)"},spoiler:{reveal:"Anzeigen",hide:"Ausblenden",notice:"Kann Spoiler enthalten"},chat:{sending:"Wird gesendet",sent:"Gesendet",delivered:"Zugestellt",read:"Gelesen",failed:"Nicht gesendet",typing:"Schreibt…"},empty:{title:"Nichts vorhanden"},table:{search:"Suchen",selectAll:"Alle Zeilen auswählen",selectRow:"Zeile auswählen",rowsPerPage:"Zeilen pro Seite",range:"{start}–{end} von {total}",selected:"{count} ausgewählt"},color:{area:"Sättigung und Helligkeit",hue:"Farbton",alpha:"Deckkraft",value:"Farbwert",swatches:"Vorgegebene Farben",clear:"Löschen",empty:"Farbe auswählen"}},it:{link:{newTab:"(si apre in una nuova scheda)"},spoiler:{reveal:"Mostra",hide:"Nascondi",notice:"Può contenere spoiler"},chat:{sending:"Invio in corso",sent:"Inviato",delivered:"Consegnato",read:"Letto",failed:"Non inviato",typing:"Sta scrivendo…"},empty:{title:"Non c’è nulla"},table:{search:"Cerca",selectAll:"Seleziona tutte le righe",selectRow:"Seleziona riga",rowsPerPage:"Righe per pagina",range:"{start}–{end} di {total}",selected:"{count} selezionate"},color:{area:"Saturazione e luminosità",hue:"Tonalità",alpha:"Opacità",value:"Valore del colore",swatches:"Colori predefiniti",clear:"Cancella",empty:"Scegli un colore"}},nl:{link:{newTab:"(opent in een nieuw tabblad)"},spoiler:{reveal:"Tonen",hide:"Verbergen",notice:"Kan spoilers bevatten"},chat:{sending:"Wordt verzonden",sent:"Verzonden",delivered:"Afgeleverd",read:"Gelezen",failed:"Niet verzonden",typing:"Aan het typen…"},empty:{title:"Hier is niets"},table:{search:"Zoeken",selectAll:"Alle rijen selecteren",selectRow:"Rij selecteren",rowsPerPage:"Rijen per pagina",range:"{start}–{end} van {total}",selected:"{count} geselecteerd"},color:{area:"Verzadiging en helderheid",hue:"Kleurtoon",alpha:"Dekking",value:"Kleurwaarde",swatches:"Vooraf ingestelde kleuren",clear:"Wissen",empty:"Kies een kleur"}},pl:{link:{newTab:"(otwiera się w nowej karcie)"},spoiler:{reveal:"Pokaż",hide:"Ukryj",notice:"Może zawierać spoilery"},chat:{sending:"Wysyłanie",sent:"Wysłano",delivered:"Dostarczono",read:"Przeczytano",failed:"Nie wysłano",typing:"Pisze…"},empty:{title:"Nic tu nie ma"},table:{search:"Szukaj",selectAll:"Zaznacz wszystkie wiersze",selectRow:"Zaznacz wiersz",rowsPerPage:"Wierszy na stronę",range:"{start}–{end} z {total}",selected:"Zaznaczono: {count}"},color:{area:"Nasycenie i jasność",hue:"Barwa",alpha:"Krycie",value:"Wartość koloru",swatches:"Kolory predefiniowane",clear:"Wyczyść",empty:"Wybierz kolor"}},ru:{link:{newTab:"(откроется в новой вкладке)"},spoiler:{reveal:"Показать",hide:"Скрыть",notice:"Может содержать спойлеры"},chat:{sending:"Отправляется",sent:"Отправлено",delivered:"Доставлено",read:"Прочитано",failed:"Не отправлено",typing:"Печатает…"},empty:{title:"Здесь пусто"},table:{search:"Поиск",selectAll:"Выбрать все строки",selectRow:"Выбрать строку",rowsPerPage:"Строк на странице",range:"{start}–{end} из {total}",selected:"Выбрано: {count}"},color:{area:"Насыщенность и яркость",hue:"Оттенок",alpha:"Непрозрачность",value:"Значение цвета",swatches:"Готовые цвета",clear:"Очистить",empty:"Выберите цвет"}},tr:{link:{newTab:"(yeni sekmede açılır)"},spoiler:{reveal:"Göster",hide:"Gizle",notice:"Spoiler içerebilir"},chat:{sending:"Gönderiliyor",sent:"Gönderildi",delivered:"İletildi",read:"Okundu",failed:"Gönderilemedi",typing:"Yazıyor…"},empty:{title:"Burada bir şey yok"},table:{search:"Ara",selectAll:"Tüm satırları seç",selectRow:"Satırı seç",rowsPerPage:"Sayfa başına satır",range:"{total} kayıttan {start}–{end}",selected:"{count} seçildi"},color:{area:"Doygunluk ve parlaklık",hue:"Renk tonu",alpha:"Opaklık",value:"Renk değeri",swatches:"Hazır renkler",clear:"Temizle",empty:"Bir renk seçin"}},ar:{link:{newTab:"(يفتح في علامة تبويب جديدة)"},spoiler:{reveal:"إظهار",hide:"إخفاء",notice:"قد يحتوي على حرق للأحداث"},chat:{sending:"جارٍ الإرسال",sent:"تم الإرسال",delivered:"تم التسليم",read:"تمت القراءة",failed:"لم يتم الإرسال",typing:"يكتب الآن…"},empty:{title:"لا يوجد شيء هنا"},table:{search:"بحث",selectAll:"تحديد كل الصفوف",selectRow:"تحديد الصف",rowsPerPage:"صفوف لكل صفحة",range:"{start}–{end} من {total}",selected:"تم تحديد {count}"},color:{area:"التشبع والسطوع",hue:"درجة اللون",alpha:"العتامة",value:"قيمة اللون",swatches:"ألوان جاهزة",clear:"مسح",empty:"اختر لونًا"}},hi:{link:{newTab:"(नए टैब में खुलता है)"},spoiler:{reveal:"दिखाएँ",hide:"छिपाएँ",notice:"इसमें स्पॉइलर हो सकते हैं"},chat:{sending:"भेजा जा रहा है",sent:"भेजा गया",delivered:"डिलीवर हुआ",read:"पढ़ा गया",failed:"नहीं भेजा गया",typing:"टाइप कर रहे हैं…"},empty:{title:"यहाँ कुछ नहीं है"},table:{search:"खोजें",selectAll:"सभी पंक्तियाँ चुनें",selectRow:"पंक्ति चुनें",rowsPerPage:"प्रति पृष्ठ पंक्तियाँ",range:"{total} में से {start}–{end}",selected:"{count} चुनी गईं"},color:{area:"संतृप्ति और चमक",hue:"रंग",alpha:"अपारदर्शिता",value:"रंग मान",swatches:"पूर्व निर्धारित रंग",clear:"साफ़ करें",empty:"एक रंग चुनें"}},id:{link:{newTab:"(terbuka di tab baru)"},spoiler:{reveal:"Tampilkan",hide:"Sembunyikan",notice:"Mungkin mengandung spoiler"},chat:{sending:"Mengirim",sent:"Terkirim",delivered:"Diterima",read:"Dibaca",failed:"Gagal terkirim",typing:"Sedang mengetik…"},empty:{title:"Tidak ada apa-apa di sini"},table:{search:"Cari",selectAll:"Pilih semua baris",selectRow:"Pilih baris",rowsPerPage:"Baris per halaman",range:"{start}–{end} dari {total}",selected:"{count} dipilih"},color:{area:"Saturasi dan kecerahan",hue:"Rona",alpha:"Opasitas",value:"Nilai warna",swatches:"Warna preset",clear:"Hapus",empty:"Pilih warna"}},vi:{link:{newTab:"(mở trong tab mới)"},spoiler:{reveal:"Hiện nội dung",hide:"Ẩn",notice:"Có thể chứa nội dung tiết lộ"},chat:{sending:"Đang gửi",sent:"Đã gửi",delivered:"Đã nhận",read:"Đã xem",failed:"Chưa gửi được",typing:"Đang nhập…"},empty:{title:"Không có gì ở đây"},table:{search:"Tìm kiếm",selectAll:"Chọn tất cả các hàng",selectRow:"Chọn hàng",rowsPerPage:"Số hàng mỗi trang",range:"{start}–{end} trên {total}",selected:"Đã chọn {count}"},color:{area:"Độ bão hòa và độ sáng",hue:"Sắc độ",alpha:"Độ mờ đục",value:"Giá trị màu",swatches:"Màu dựng sẵn",clear:"Xóa",empty:"Chọn màu"}},th:{link:{newTab:"(เปิดในแท็บใหม่)"},spoiler:{reveal:"แสดงเนื้อหา",hide:"ซ่อน",notice:"อาจมีการเปิดเผยเนื้อหา"},chat:{sending:"กำลังส่ง",sent:"ส่งแล้ว",delivered:"ส่งถึงแล้ว",read:"อ่านแล้ว",failed:"ส่งไม่สำเร็จ",typing:"กำลังพิมพ์…"},empty:{title:"ไม่มีอะไรที่นี่"},table:{search:"ค้นหา",selectAll:"เลือกทุกแถว",selectRow:"เลือกแถวนี้",rowsPerPage:"จำนวนแถวต่อหน้า",range:"{start}–{end} จาก {total}",selected:"เลือกแล้ว {count} รายการ"},color:{area:"ความอิ่มตัวและความสว่าง",hue:"เฉดสี",alpha:"ความทึบ",value:"ค่าสี",swatches:"สีที่กำหนดไว้",clear:"ล้าง",empty:"เลือกสี"}}},l={zh:"zh-hans","zh-cn":"zh-hans","zh-my":"zh-hans","zh-sg":"zh-hans","zh-hk":"zh-hant","zh-mo":"zh-hant","zh-tw":"zh-hant"};const n=new Map([["",a]]);export function resolveMessages(e){const r=e?.trim()??"",i=n.get(r);if(i)return i;const s=function(e){const a=e.toLowerCase().split(/[-_]/).filter(Boolean),t=a[0];if(!t)return[];const l=a.slice(1),n=l.find(e=>/^[a-z]{4}$/.test(e)),r=l.find(e=>/^([a-z]{2}|\d{3})$/.test(e));return[n?`${t}-${n}`:"",r?`${t}-${r}`:"",t].filter(Boolean)}(r).map(e=>t[e]??t[l[e]??""]).find(Boolean),o=s?{link:{...a.link,...s.link},spoiler:{...a.spoiler,...s.spoiler},chat:{...a.chat,...s.chat},empty:{...a.empty,...s.empty},table:{...a.table,...s.table},color:{...a.color,...s.color}}:a;return n.set(r,o),o}export function useMessages(a){return e.useMemo(()=>resolveMessages(a),[a])}export function fillMessage(e,a){return e.replace(/\{(\w+)\}/g,(e,t)=>a[t]??e)}
|