@nibapp/ui 0.0.0-stage → 0.1.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/CHANGELOG.md +35 -0
- package/LICENSE +21 -0
- package/README.md +124 -2
- package/TRADEMARKS.md +22 -0
- package/dist/_bundled/money/index.d.ts +98 -0
- package/dist/_bundled/money/index.js +152 -0
- package/dist/_bundled/tokens/index.d.ts +2 -0
- package/dist/_bundled/tokens/index.js +2 -0
- package/dist/_bundled/tokens/palette.gen.d.ts +51 -0
- package/dist/_bundled/tokens/palette.gen.js +206 -0
- package/dist/_bundled/tokens/spec.d.ts +291 -0
- package/dist/_bundled/tokens/spec.js +221 -0
- package/dist/components/accordion.d.ts +27 -0
- package/dist/components/accordion.js +66 -0
- package/dist/components/alert-dialog.d.ts +59 -0
- package/dist/components/alert-dialog.js +82 -0
- package/dist/components/alert.d.ts +141 -0
- package/dist/components/alert.js +55 -0
- package/dist/components/appear.d.ts +21 -0
- package/dist/components/appear.js +42 -0
- package/dist/components/avatar.d.ts +196 -0
- package/dist/components/avatar.js +135 -0
- package/dist/components/badge.d.ts +183 -0
- package/dist/components/badge.js +68 -0
- package/dist/components/block-stack.d.ts +33 -0
- package/dist/components/block-stack.js +197 -0
- package/dist/components/button.d.ts +327 -0
- package/dist/components/button.js +101 -0
- package/dist/components/card.d.ts +131 -0
- package/dist/components/card.js +100 -0
- package/dist/components/checkbox.d.ts +34 -0
- package/dist/components/checkbox.js +37 -0
- package/dist/components/choice-row.d.ts +104 -0
- package/dist/components/choice-row.js +56 -0
- package/dist/components/clamped-text.d.ts +17 -0
- package/dist/components/clamped-text.js +47 -0
- package/dist/components/code-block.d.ts +48 -0
- package/dist/components/code-block.js +124 -0
- package/dist/components/code-input.d.ts +49 -0
- package/dist/components/code-input.js +59 -0
- package/dist/components/combobox.d.ts +194 -0
- package/dist/components/combobox.js +176 -0
- package/dist/components/command-palette.d.ts +57 -0
- package/dist/components/command-palette.js +83 -0
- package/dist/components/context-menu.d.ts +24 -0
- package/dist/components/context-menu.js +30 -0
- package/dist/components/copy-button.d.ts +9 -0
- package/dist/components/copy-button.js +20 -0
- package/dist/components/copy-command.d.ts +18 -0
- package/dist/components/copy-command.js +19 -0
- package/dist/components/data-table.d.ts +240 -0
- package/dist/components/data-table.js +63 -0
- package/dist/components/dialog.d.ts +80 -0
- package/dist/components/dialog.js +85 -0
- package/dist/components/diff-lines.d.ts +20 -0
- package/dist/components/diff-lines.js +10 -0
- package/dist/components/diff-view.d.ts +128 -0
- package/dist/components/diff-view.js +675 -0
- package/dist/components/disclosure.d.ts +33 -0
- package/dist/components/disclosure.js +51 -0
- package/dist/components/drawn-check.d.ts +8 -0
- package/dist/components/drawn-check.js +16 -0
- package/dist/components/edge-resizer.d.ts +26 -0
- package/dist/components/edge-resizer.js +53 -0
- package/dist/components/empty-state.d.ts +112 -0
- package/dist/components/empty-state.js +56 -0
- package/dist/components/expand-toggle.d.ts +19 -0
- package/dist/components/expand-toggle.js +10 -0
- package/dist/components/field.d.ts +48 -0
- package/dist/components/field.js +60 -0
- package/dist/components/filter-chip.d.ts +47 -0
- package/dist/components/filter-chip.js +33 -0
- package/dist/components/group.d.ts +10 -0
- package/dist/components/group.js +9 -0
- package/dist/components/icon-text.d.ts +11 -0
- package/dist/components/icon-text.js +10 -0
- package/dist/components/image-sources.d.ts +57 -0
- package/dist/components/image-sources.js +58 -0
- package/dist/components/image-thumbnail.d.ts +34 -0
- package/dist/components/image-thumbnail.js +58 -0
- package/dist/components/image-viewer.d.ts +60 -0
- package/dist/components/image-viewer.js +411 -0
- package/dist/components/inline-edit.d.ts +22 -0
- package/dist/components/inline-edit.js +51 -0
- package/dist/components/input.d.ts +155 -0
- package/dist/components/input.js +109 -0
- package/dist/components/item-label.d.ts +11 -0
- package/dist/components/item-label.js +5 -0
- package/dist/components/item-row.d.ts +23 -0
- package/dist/components/item-row.js +19 -0
- package/dist/components/kbd.d.ts +55 -0
- package/dist/components/kbd.js +86 -0
- package/dist/components/link-policy.d.ts +24 -0
- package/dist/components/link-policy.js +32 -0
- package/dist/components/linked-text.d.ts +15 -0
- package/dist/components/linked-text.js +48 -0
- package/dist/components/loading-region.d.ts +18 -0
- package/dist/components/loading-region.js +17 -0
- package/dist/components/markdown.d.ts +34 -0
- package/dist/components/markdown.js +307 -0
- package/dist/components/menu.d.ts +132 -0
- package/dist/components/menu.js +102 -0
- package/dist/components/message-bubble.d.ts +57 -0
- package/dist/components/message-bubble.js +28 -0
- package/dist/components/meta-line.d.ts +28 -0
- package/dist/components/meta-line.js +36 -0
- package/dist/components/money-input.d.ts +23 -0
- package/dist/components/money-input.js +34 -0
- package/dist/components/native-select.d.ts +120 -0
- package/dist/components/native-select.js +85 -0
- package/dist/components/page-trail.d.ts +30 -0
- package/dist/components/page-trail.js +43 -0
- package/dist/components/page.d.ts +80 -0
- package/dist/components/page.js +60 -0
- package/dist/components/path-text.d.ts +8 -0
- package/dist/components/path-text.js +11 -0
- package/dist/components/percent-input.d.ts +20 -0
- package/dist/components/percent-input.js +32 -0
- package/dist/components/popover.d.ts +52 -0
- package/dist/components/popover.js +36 -0
- package/dist/components/progress.d.ts +389 -0
- package/dist/components/progress.js +142 -0
- package/dist/components/radio.d.ts +65 -0
- package/dist/components/radio.js +54 -0
- package/dist/components/remote-image-gate.d.ts +34 -0
- package/dist/components/remote-image-gate.js +60 -0
- package/dist/components/resizable.d.ts +23 -0
- package/dist/components/resizable.js +30 -0
- package/dist/components/rolling-number.d.ts +13 -0
- package/dist/components/rolling-number.js +69 -0
- package/dist/components/scroll-area.d.ts +17 -0
- package/dist/components/scroll-area.js +25 -0
- package/dist/components/section-label.d.ts +10 -0
- package/dist/components/section-label.js +21 -0
- package/dist/components/section-nav.d.ts +22 -0
- package/dist/components/section-nav.js +80 -0
- package/dist/components/segmented-control.d.ts +125 -0
- package/dist/components/segmented-control.js +101 -0
- package/dist/components/select.d.ts +120 -0
- package/dist/components/select.js +89 -0
- package/dist/components/separator.d.ts +10 -0
- package/dist/components/separator.js +12 -0
- package/dist/components/sheet.d.ts +101 -0
- package/dist/components/sheet.js +129 -0
- package/dist/components/shortcut-recorder.d.ts +31 -0
- package/dist/components/shortcut-recorder.js +67 -0
- package/dist/components/skeleton.d.ts +31 -0
- package/dist/components/skeleton.js +33 -0
- package/dist/components/slider.d.ts +138 -0
- package/dist/components/slider.js +98 -0
- package/dist/components/spinner.d.ts +37 -0
- package/dist/components/spinner.js +16 -0
- package/dist/components/switch.d.ts +59 -0
- package/dist/components/switch.js +56 -0
- package/dist/components/tab-strip.d.ts +29 -0
- package/dist/components/tab-strip.js +42 -0
- package/dist/components/tabs.d.ts +13 -0
- package/dist/components/tabs.js +68 -0
- package/dist/components/text-link.d.ts +54 -0
- package/dist/components/text-link.js +38 -0
- package/dist/components/textarea.d.ts +75 -0
- package/dist/components/textarea.js +90 -0
- package/dist/components/toast.d.ts +74 -0
- package/dist/components/toast.js +128 -0
- package/dist/components/tooltip.d.ts +50 -0
- package/dist/components/tooltip.js +46 -0
- package/dist/components/truncated-text.d.ts +15 -0
- package/dist/components/truncated-text.js +19 -0
- package/dist/i18n/defaults.d.ts +7 -0
- package/dist/i18n/defaults.js +7 -0
- package/dist/i18n/format.d.ts +84 -0
- package/dist/i18n/format.js +169 -0
- package/dist/i18n/index.d.ts +6 -0
- package/dist/i18n/index.js +8 -0
- package/dist/i18n/locales/ru-formatters.d.ts +3 -0
- package/dist/i18n/locales/ru-formatters.js +22 -0
- package/dist/i18n/locales/ru.d.ts +5 -0
- package/dist/i18n/locales/ru.js +185 -0
- package/dist/i18n/plural.d.ts +20 -0
- package/dist/i18n/plural.js +26 -0
- package/dist/i18n/provider.d.ts +36 -0
- package/dist/i18n/provider.js +39 -0
- package/dist/i18n/rich.d.ts +14 -0
- package/dist/i18n/rich.js +44 -0
- package/dist/i18n/ru-plural.d.ts +3 -0
- package/dist/i18n/ru-plural.js +4 -0
- package/dist/i18n/strings/alert-dialog.d.ts +5 -0
- package/dist/i18n/strings/alert-dialog.js +5 -0
- package/dist/i18n/strings/block-stack.d.ts +17 -0
- package/dist/i18n/strings/block-stack.js +11 -0
- package/dist/i18n/strings/code-block.d.ts +18 -0
- package/dist/i18n/strings/code-block.js +13 -0
- package/dist/i18n/strings/combobox.d.ts +12 -0
- package/dist/i18n/strings/combobox.js +10 -0
- package/dist/i18n/strings/command-palette.d.ts +17 -0
- package/dist/i18n/strings/command-palette.js +11 -0
- package/dist/i18n/strings/diff-view.d.ts +92 -0
- package/dist/i18n/strings/diff-view.js +68 -0
- package/dist/i18n/strings/image-viewer.d.ts +43 -0
- package/dist/i18n/strings/image-viewer.js +32 -0
- package/dist/i18n/strings/index.d.ts +314 -0
- package/dist/i18n/strings/index.js +43 -0
- package/dist/i18n/strings/kbd.d.ts +42 -0
- package/dist/i18n/strings/kbd.js +39 -0
- package/dist/i18n/strings/markdown.d.ts +23 -0
- package/dist/i18n/strings/markdown.js +18 -0
- package/dist/i18n/strings/money.d.ts +7 -0
- package/dist/i18n/strings/money.js +5 -0
- package/dist/i18n/strings/primitives.d.ts +25 -0
- package/dist/i18n/strings/primitives.js +18 -0
- package/dist/i18n/strings/progress.d.ts +4 -0
- package/dist/i18n/strings/progress.js +4 -0
- package/dist/i18n/strings/shortcut-recorder.d.ts +15 -0
- package/dist/i18n/strings/shortcut-recorder.js +11 -0
- package/dist/i18n/strings/time.d.ts +41 -0
- package/dist/i18n/strings/time.js +20 -0
- package/dist/i18n/strings/toast.d.ts +6 -0
- package/dist/i18n/strings/toast.js +6 -0
- package/dist/i18n/time-zones.d.ts +20 -0
- package/dist/i18n/time-zones.js +64 -0
- package/dist/index.d.ts +84 -0
- package/dist/index.js +88 -0
- package/dist/lib/ansi.d.ts +55 -0
- package/dist/lib/ansi.js +438 -0
- package/dist/lib/clock.d.ts +32 -0
- package/dist/lib/clock.js +106 -0
- package/dist/lib/cn.d.ts +12 -0
- package/dist/lib/cn.js +28 -0
- package/dist/lib/control-props.d.ts +21 -0
- package/dist/lib/control-props.js +14 -0
- package/dist/lib/direction.d.ts +13 -0
- package/dist/lib/direction.js +25 -0
- package/dist/lib/image-size.d.ts +12 -0
- package/dist/lib/image-size.js +66 -0
- package/dist/lib/keycaps.d.ts +21 -0
- package/dist/lib/keycaps.js +62 -0
- package/dist/lib/keyed.d.ts +9 -0
- package/dist/lib/keyed.js +14 -0
- package/dist/lib/keys.d.ts +49 -0
- package/dist/lib/keys.js +159 -0
- package/dist/lib/kit-motion.d.ts +9 -0
- package/dist/lib/kit-motion.js +12 -0
- package/dist/lib/link-check-dialog.d.ts +11 -0
- package/dist/lib/link-check-dialog.js +18 -0
- package/dist/lib/motion-features.d.ts +1 -0
- package/dist/lib/motion-features.js +4 -0
- package/dist/lib/motion-provider.d.ts +5 -0
- package/dist/lib/motion-provider.js +8 -0
- package/dist/lib/motion.d.ts +39 -0
- package/dist/lib/motion.js +49 -0
- package/dist/lib/number-format.d.ts +114 -0
- package/dist/lib/number-format.js +313 -0
- package/dist/lib/patch.d.ts +72 -0
- package/dist/lib/patch.js +344 -0
- package/dist/lib/path.d.ts +34 -0
- package/dist/lib/path.js +85 -0
- package/dist/lib/search.d.ts +10 -0
- package/dist/lib/search.js +20 -0
- package/dist/lib/shiki-themes.d.ts +7 -0
- package/dist/lib/shiki-themes.js +115 -0
- package/dist/lib/shiki.d.ts +101 -0
- package/dist/lib/shiki.js +353 -0
- package/dist/lib/shortcut-context.d.ts +23 -0
- package/dist/lib/shortcut-context.js +29 -0
- package/dist/lib/text.d.ts +56 -0
- package/dist/lib/text.js +149 -0
- package/dist/lib/theme-scope.d.ts +18 -0
- package/dist/lib/theme-scope.js +24 -0
- package/dist/lib/theme.d.ts +40 -0
- package/dist/lib/theme.js +96 -0
- package/dist/lib/tree-keys.d.ts +72 -0
- package/dist/lib/tree-keys.js +126 -0
- package/dist/lib/url.d.ts +17 -0
- package/dist/lib/url.js +38 -0
- package/dist/lib/use-copy-to-clipboard.d.ts +13 -0
- package/dist/lib/use-copy-to-clipboard.js +40 -0
- package/dist/lib/use-delayed-loading.d.ts +11 -0
- package/dist/lib/use-delayed-loading.js +31 -0
- package/dist/lib/use-file-picker.d.ts +26 -0
- package/dist/lib/use-file-picker.js +43 -0
- package/dist/lib/use-hydrated.d.ts +6 -0
- package/dist/lib/use-hydrated.js +11 -0
- package/dist/lib/use-latest.d.ts +12 -0
- package/dist/lib/use-latest.js +22 -0
- package/dist/lib/use-resolved-theme.d.ts +12 -0
- package/dist/lib/use-resolved-theme.js +25 -0
- package/dist/styles/base.css +393 -0
- package/dist/styles/code.css +35 -0
- package/dist/styles/fonts.css +12 -0
- package/dist/styles/index.css +13 -0
- package/dist/styles/tokens.gen.css +494 -0
- package/package.json +208 -4
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lazily created, shared Shiki highlighter. `@nibapp/ui/shiki`, a preview API like CodeBlock: it may
|
|
3
|
+
* change in a minor version before 1.0.
|
|
4
|
+
*
|
|
5
|
+
* - Fine-grained bundle (`shiki/core`) + the JavaScript RegExp engine: no WASM, so it runs the same in
|
|
6
|
+
* Tauri's webview, browsers and SSR.
|
|
7
|
+
* - Grammars are dynamic imports, loaded on demand; common ones are warmed in the background once the
|
|
8
|
+
* highlighter exists.
|
|
9
|
+
* - Tokens carry `--shiki-light` / `--shiki-dark` CSS variables (`defaultColor: false`); `code.css`
|
|
10
|
+
* resolves them with `light-dark()`, so highlighted code follows `data-theme` / `color-scheme`
|
|
11
|
+
* without re-tokenizing on theme change.
|
|
12
|
+
*/
|
|
13
|
+
import { type HighlighterCore } from "shiki/core";
|
|
14
|
+
import { type BundledLanguage } from "shiki/langs";
|
|
15
|
+
export type ResolvedLanguage = BundledLanguage | "text";
|
|
16
|
+
/** Normalizes a fence/info-string language to a Shiki language id, or `"text"` when unknown. */
|
|
17
|
+
export declare function resolveLanguage(lang: string | null | undefined): ResolvedLanguage;
|
|
18
|
+
/** Human label for a language id (`tsx` → `TSX`, `python` → `Python`). */
|
|
19
|
+
export declare function languageLabel(lang: string | null | undefined): string;
|
|
20
|
+
/** The highlighter if it already exists — lets components tokenize synchronously on remount. */
|
|
21
|
+
export declare function getLoadedHighlighter(): HighlighterCore | null;
|
|
22
|
+
/** True when `lang` can be tokenized synchronously right now. */
|
|
23
|
+
export declare function isLanguageReady(lang: ResolvedLanguage): boolean;
|
|
24
|
+
/** Resolves once the highlighter and `lang`'s grammar are loaded. */
|
|
25
|
+
export declare function loadLanguage(lang: ResolvedLanguage): Promise<HighlighterCore>;
|
|
26
|
+
/** One highlighted token. `style` holds `--shiki-light*` / `--shiki-dark*` custom properties. */
|
|
27
|
+
interface CodeToken {
|
|
28
|
+
content: string;
|
|
29
|
+
style?: Record<string, string>;
|
|
30
|
+
}
|
|
31
|
+
export type CodeLine = readonly CodeToken[];
|
|
32
|
+
/**
|
|
33
|
+
* Plain (uncolored) lines — the loading / not-yet-tokenized fallback. Same line split and one text
|
|
34
|
+
* run per line, so it lays out exactly like highlighted output (only the token spans differ).
|
|
35
|
+
*/
|
|
36
|
+
export declare function plainLines(code: string): CodeLine[];
|
|
37
|
+
/** Synchronous tokenization; requires `isLanguageReady(lang)`. Uncached — prefer {@link tokenizeCached}. */
|
|
38
|
+
export declare function tokenize(h: HighlighterCore, code: string, lang: ResolvedLanguage): CodeLine[];
|
|
39
|
+
/**
|
|
40
|
+
* cyrb53: a fast, well-distributed 53-bit string hash (two 32-bit FNV-style lanes). Not
|
|
41
|
+
* cryptographic; cache hits also compare the source text, so a collision can never serve wrong
|
|
42
|
+
* tokens.
|
|
43
|
+
*/
|
|
44
|
+
export declare function hashString(text: string, seed?: number): number;
|
|
45
|
+
export interface TokenCacheLimits {
|
|
46
|
+
/** Most entries kept. */
|
|
47
|
+
maxEntries: number;
|
|
48
|
+
/** Approximate heap budget in bytes (UTF-16 text + per-token overhead). */
|
|
49
|
+
maxBytes: number;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Least-recently-used token store keyed by (language, theme pair, source hash). A `Map` keeps
|
|
53
|
+
* insertion order, so re-inserting on read makes its first key the least recently used. Bounded by
|
|
54
|
+
* entry count and approximate size; a single entry larger than a quarter of the budget isn't kept.
|
|
55
|
+
*/
|
|
56
|
+
export declare class TokenCache {
|
|
57
|
+
private readonly limits;
|
|
58
|
+
private readonly entries;
|
|
59
|
+
private bytes;
|
|
60
|
+
constructor(limits: TokenCacheLimits);
|
|
61
|
+
get size(): number;
|
|
62
|
+
get approximateBytes(): number;
|
|
63
|
+
get(key: string, code: string): readonly CodeLine[] | undefined;
|
|
64
|
+
set(key: string, code: string, lines: readonly CodeLine[]): void;
|
|
65
|
+
clear(): void;
|
|
66
|
+
private delete;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Process-wide token cache, so a code block that remounts (a virtualized list's row scrolling
|
|
70
|
+
* back into view) renders highlighted on its first pass with no Shiki work. It holds only tokens
|
|
71
|
+
* derived from source text: locale- and request-independent, so sharing it across SSR requests is
|
|
72
|
+
* safe. 512 entries / ~16 MB.
|
|
73
|
+
*/
|
|
74
|
+
export declare const tokenCache: TokenCache;
|
|
75
|
+
/** Cached tokens for `code`, if any (no tokenization). `"text"` is always available. */
|
|
76
|
+
export declare function cachedTokens(code: string, lang: ResolvedLanguage): readonly CodeLine[] | undefined;
|
|
77
|
+
/** Stores tokens produced elsewhere (e.g. a finished {@link StreamingTokenizer}). */
|
|
78
|
+
export declare function storeTokens(code: string, lang: ResolvedLanguage, lines: readonly CodeLine[]): void;
|
|
79
|
+
/** {@link tokenize} through the cache; requires `isLanguageReady(lang)`. */
|
|
80
|
+
export declare function tokenizeCached(h: HighlighterCore, code: string, lang: ResolvedLanguage): readonly CodeLine[];
|
|
81
|
+
/**
|
|
82
|
+
* Incremental tokenizer for code that only grows at the end (streaming). Completed lines are
|
|
83
|
+
* tokenized once and cached together with the grammar state; each update re-tokenizes just the
|
|
84
|
+
* newly completed lines plus the trailing partial line.
|
|
85
|
+
*/
|
|
86
|
+
export declare class StreamingTokenizer {
|
|
87
|
+
private readonly h;
|
|
88
|
+
readonly lang: ResolvedLanguage;
|
|
89
|
+
private stableText;
|
|
90
|
+
private stableLines;
|
|
91
|
+
private state;
|
|
92
|
+
constructor(h: HighlighterCore, lang: ResolvedLanguage);
|
|
93
|
+
/** True when `update(code)` only tokenizes what was appended since the previous update. */
|
|
94
|
+
isWarmFor(code: string): boolean;
|
|
95
|
+
update(code: string): CodeLine[];
|
|
96
|
+
private options;
|
|
97
|
+
private reset;
|
|
98
|
+
}
|
|
99
|
+
/** `src/app.tsx` → `tsx`, `Dockerfile` → `docker`, `README.md` → `markdown`; unknown → `"text"`. */
|
|
100
|
+
export declare function languageForPath(path: string): ResolvedLanguage;
|
|
101
|
+
export {};
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lazily created, shared Shiki highlighter. `@nibapp/ui/shiki`, a preview API like CodeBlock: it may
|
|
3
|
+
* change in a minor version before 1.0.
|
|
4
|
+
*
|
|
5
|
+
* - Fine-grained bundle (`shiki/core`) + the JavaScript RegExp engine: no WASM, so it runs the same in
|
|
6
|
+
* Tauri's webview, browsers and SSR.
|
|
7
|
+
* - Grammars are dynamic imports, loaded on demand; common ones are warmed in the background once the
|
|
8
|
+
* highlighter exists.
|
|
9
|
+
* - Tokens carry `--shiki-light` / `--shiki-dark` CSS variables (`defaultColor: false`); `code.css`
|
|
10
|
+
* resolves them with `light-dark()`, so highlighted code follows `data-theme` / `color-scheme`
|
|
11
|
+
* without re-tokenizing on theme change.
|
|
12
|
+
*/
|
|
13
|
+
import { createHighlighterCore } from "shiki/core";
|
|
14
|
+
import { createJavaScriptRegexEngine } from "shiki/engine/javascript";
|
|
15
|
+
import { bundledLanguages } from "shiki/langs";
|
|
16
|
+
import { NIB_SHIKI_THEMES, nibDarkTheme, nibLightTheme } from "./shiki-themes.js";
|
|
17
|
+
import { extensionOf } from "./text.js";
|
|
18
|
+
/** Grammars warmed right after the highlighter is created. */
|
|
19
|
+
const PRELOADED_LANGUAGES = [
|
|
20
|
+
"ts",
|
|
21
|
+
"tsx",
|
|
22
|
+
"js",
|
|
23
|
+
"json",
|
|
24
|
+
"bash",
|
|
25
|
+
"python",
|
|
26
|
+
"rust",
|
|
27
|
+
"go",
|
|
28
|
+
"css",
|
|
29
|
+
"html",
|
|
30
|
+
"md",
|
|
31
|
+
"yaml",
|
|
32
|
+
"diff",
|
|
33
|
+
"sql",
|
|
34
|
+
];
|
|
35
|
+
/** Extra spellings agents and humans use in fences that Shiki doesn't alias itself. */
|
|
36
|
+
const EXTRA_ALIASES = {
|
|
37
|
+
typescriptreact: "tsx",
|
|
38
|
+
javascriptreact: "jsx",
|
|
39
|
+
golang: "go",
|
|
40
|
+
"objective-c++": "objective-cpp",
|
|
41
|
+
dockerfile: "docker",
|
|
42
|
+
ps: "powershell",
|
|
43
|
+
patch: "diff",
|
|
44
|
+
env: "dotenv",
|
|
45
|
+
rb: "ruby",
|
|
46
|
+
};
|
|
47
|
+
const PLAIN = new Set(["", "text", "txt", "plain", "plaintext", "none", "output", "log"]);
|
|
48
|
+
const isBundled = (lang) => Object.hasOwn(bundledLanguages, lang);
|
|
49
|
+
/** Normalizes a fence/info-string language to a Shiki language id, or `"text"` when unknown. */
|
|
50
|
+
export function resolveLanguage(lang) {
|
|
51
|
+
const key = (lang ?? "").trim().toLowerCase().split(/[\s{]/, 1)[0] ?? "";
|
|
52
|
+
if (PLAIN.has(key))
|
|
53
|
+
return "text";
|
|
54
|
+
if (isBundled(key))
|
|
55
|
+
return key;
|
|
56
|
+
const alias = EXTRA_ALIASES[key];
|
|
57
|
+
return alias ?? "text";
|
|
58
|
+
}
|
|
59
|
+
/** Human label for a language id (`tsx` → `TSX`, `python` → `Python`). */
|
|
60
|
+
export function languageLabel(lang) {
|
|
61
|
+
const raw = (lang ?? "").trim().split(/[\s{]/, 1)[0] ?? "";
|
|
62
|
+
if (raw === "" || PLAIN.has(raw.toLowerCase()))
|
|
63
|
+
return "text";
|
|
64
|
+
return raw.toLowerCase();
|
|
65
|
+
}
|
|
66
|
+
let instance = null;
|
|
67
|
+
let creating = null;
|
|
68
|
+
const languageLoads = new Map();
|
|
69
|
+
function warmCommonLanguages(h) {
|
|
70
|
+
const run = () => {
|
|
71
|
+
for (const lang of PRELOADED_LANGUAGES)
|
|
72
|
+
void ensureLoaded(h, lang).catch(() => undefined);
|
|
73
|
+
};
|
|
74
|
+
if (typeof requestIdleCallback === "function")
|
|
75
|
+
requestIdleCallback(run, { timeout: 2000 });
|
|
76
|
+
else
|
|
77
|
+
setTimeout(run, 200);
|
|
78
|
+
}
|
|
79
|
+
/** The shared highlighter (created on first call). */
|
|
80
|
+
function getHighlighter() {
|
|
81
|
+
if (instance)
|
|
82
|
+
return Promise.resolve(instance);
|
|
83
|
+
creating ??= createHighlighterCore({
|
|
84
|
+
themes: [nibLightTheme, nibDarkTheme],
|
|
85
|
+
langs: [],
|
|
86
|
+
engine: createJavaScriptRegexEngine({ forgiving: true }),
|
|
87
|
+
}).then((h) => {
|
|
88
|
+
instance = h;
|
|
89
|
+
warmCommonLanguages(h);
|
|
90
|
+
return h;
|
|
91
|
+
}, (error) => {
|
|
92
|
+
creating = null;
|
|
93
|
+
throw error;
|
|
94
|
+
});
|
|
95
|
+
return creating;
|
|
96
|
+
}
|
|
97
|
+
/** The highlighter if it already exists — lets components tokenize synchronously on remount. */
|
|
98
|
+
export function getLoadedHighlighter() {
|
|
99
|
+
return instance;
|
|
100
|
+
}
|
|
101
|
+
function ensureLoaded(h, lang) {
|
|
102
|
+
if (lang === "text" || h.getLoadedLanguages().includes(lang))
|
|
103
|
+
return Promise.resolve();
|
|
104
|
+
let load = languageLoads.get(lang);
|
|
105
|
+
if (!load) {
|
|
106
|
+
load = h.loadLanguage(bundledLanguages[lang]).catch((error) => {
|
|
107
|
+
languageLoads.delete(lang);
|
|
108
|
+
throw error;
|
|
109
|
+
});
|
|
110
|
+
languageLoads.set(lang, load);
|
|
111
|
+
}
|
|
112
|
+
return load;
|
|
113
|
+
}
|
|
114
|
+
/** True when `lang` can be tokenized synchronously right now. */
|
|
115
|
+
export function isLanguageReady(lang) {
|
|
116
|
+
if (!instance)
|
|
117
|
+
return false;
|
|
118
|
+
if (lang === "text")
|
|
119
|
+
return true;
|
|
120
|
+
return instance.getLoadedLanguages().includes(lang);
|
|
121
|
+
}
|
|
122
|
+
/** Resolves once the highlighter and `lang`'s grammar are loaded. */
|
|
123
|
+
export async function loadLanguage(lang) {
|
|
124
|
+
const h = await getHighlighter();
|
|
125
|
+
await ensureLoaded(h, lang);
|
|
126
|
+
return h;
|
|
127
|
+
}
|
|
128
|
+
const toLine = (tokens) => tokens.map((t) => (t.htmlStyle ? { content: t.content, style: t.htmlStyle } : { content: t.content }));
|
|
129
|
+
const TOKEN_OPTIONS = {
|
|
130
|
+
themes: NIB_SHIKI_THEMES,
|
|
131
|
+
defaultColor: false,
|
|
132
|
+
tokenizeMaxLineLength: 4000,
|
|
133
|
+
// The JS engine compiles a grammar's regexes lazily; the first line of a big grammar (TS) can take
|
|
134
|
+
// ~1s cold. Shiki's 500ms default would then give up mid-line and leave it half-colored.
|
|
135
|
+
tokenizeTimeLimit: 2500,
|
|
136
|
+
};
|
|
137
|
+
/** Shiki's own line split (`\r\n` or `\n`), so plain and highlighted output have the same lines. */
|
|
138
|
+
const LINE_BREAK = /\r?\n/;
|
|
139
|
+
const LINE_BREAK_END = /\r?\n$/;
|
|
140
|
+
/**
|
|
141
|
+
* Plain (uncolored) lines — the loading / not-yet-tokenized fallback. Same line split and one text
|
|
142
|
+
* run per line, so it lays out exactly like highlighted output (only the token spans differ).
|
|
143
|
+
*/
|
|
144
|
+
export function plainLines(code) {
|
|
145
|
+
return code.split(LINE_BREAK).map((line) => (line === "" ? [] : [{ content: line }]));
|
|
146
|
+
}
|
|
147
|
+
/** Synchronous tokenization; requires `isLanguageReady(lang)`. Uncached — prefer {@link tokenizeCached}. */
|
|
148
|
+
export function tokenize(h, code, lang) {
|
|
149
|
+
if (lang === "text")
|
|
150
|
+
return plainLines(code);
|
|
151
|
+
return h.codeToTokens(code, { lang, ...TOKEN_OPTIONS }).tokens.map(toLine);
|
|
152
|
+
}
|
|
153
|
+
/* ------------------------------------------------------------------------------------------ */
|
|
154
|
+
/* Token cache */
|
|
155
|
+
/* ------------------------------------------------------------------------------------------ */
|
|
156
|
+
/**
|
|
157
|
+
* cyrb53: a fast, well-distributed 53-bit string hash (two 32-bit FNV-style lanes). Not
|
|
158
|
+
* cryptographic; cache hits also compare the source text, so a collision can never serve wrong
|
|
159
|
+
* tokens.
|
|
160
|
+
*/
|
|
161
|
+
export function hashString(text, seed = 0) {
|
|
162
|
+
let h1 = 0xdeadbeef ^ seed;
|
|
163
|
+
let h2 = 0x41c6ce57 ^ seed;
|
|
164
|
+
for (let i = 0; i < text.length; i++) {
|
|
165
|
+
const ch = text.charCodeAt(i);
|
|
166
|
+
h1 = Math.imul(h1 ^ ch, 2654435761);
|
|
167
|
+
h2 = Math.imul(h2 ^ ch, 1597334677);
|
|
168
|
+
}
|
|
169
|
+
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
|
|
170
|
+
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
|
|
171
|
+
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
|
|
172
|
+
}
|
|
173
|
+
/** Rough heap cost of one token beyond its text (object, style record, two CSS-variable strings). */
|
|
174
|
+
const TOKEN_OVERHEAD_BYTES = 96;
|
|
175
|
+
/**
|
|
176
|
+
* Least-recently-used token store keyed by (language, theme pair, source hash). A `Map` keeps
|
|
177
|
+
* insertion order, so re-inserting on read makes its first key the least recently used. Bounded by
|
|
178
|
+
* entry count and approximate size; a single entry larger than a quarter of the budget isn't kept.
|
|
179
|
+
*/
|
|
180
|
+
export class TokenCache {
|
|
181
|
+
limits;
|
|
182
|
+
entries = new Map();
|
|
183
|
+
bytes = 0;
|
|
184
|
+
constructor(limits) {
|
|
185
|
+
this.limits = limits;
|
|
186
|
+
}
|
|
187
|
+
get size() {
|
|
188
|
+
return this.entries.size;
|
|
189
|
+
}
|
|
190
|
+
get approximateBytes() {
|
|
191
|
+
return this.bytes;
|
|
192
|
+
}
|
|
193
|
+
get(key, code) {
|
|
194
|
+
const entry = this.entries.get(key);
|
|
195
|
+
if (entry === undefined || entry.code !== code)
|
|
196
|
+
return undefined;
|
|
197
|
+
this.entries.delete(key);
|
|
198
|
+
this.entries.set(key, entry);
|
|
199
|
+
return entry.lines;
|
|
200
|
+
}
|
|
201
|
+
set(key, code, lines) {
|
|
202
|
+
let tokens = 0;
|
|
203
|
+
for (const line of lines)
|
|
204
|
+
tokens += line.length;
|
|
205
|
+
const bytes = code.length * 2 + tokens * TOKEN_OVERHEAD_BYTES;
|
|
206
|
+
this.delete(key);
|
|
207
|
+
if (bytes > this.limits.maxBytes / 4)
|
|
208
|
+
return;
|
|
209
|
+
this.entries.set(key, { code, lines, bytes });
|
|
210
|
+
this.bytes += bytes;
|
|
211
|
+
for (const [oldest, entry] of this.entries) {
|
|
212
|
+
if (this.entries.size <= this.limits.maxEntries && this.bytes <= this.limits.maxBytes)
|
|
213
|
+
break;
|
|
214
|
+
this.entries.delete(oldest);
|
|
215
|
+
this.bytes -= entry.bytes;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
clear() {
|
|
219
|
+
this.entries.clear();
|
|
220
|
+
this.bytes = 0;
|
|
221
|
+
}
|
|
222
|
+
delete(key) {
|
|
223
|
+
const entry = this.entries.get(key);
|
|
224
|
+
if (entry === undefined)
|
|
225
|
+
return;
|
|
226
|
+
this.entries.delete(key);
|
|
227
|
+
this.bytes -= entry.bytes;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const THEME_KEY = `${NIB_SHIKI_THEMES.light}/${NIB_SHIKI_THEMES.dark}`;
|
|
231
|
+
/**
|
|
232
|
+
* Process-wide token cache, so a code block that remounts (a virtualized list's row scrolling
|
|
233
|
+
* back into view) renders highlighted on its first pass with no Shiki work. It holds only tokens
|
|
234
|
+
* derived from source text: locale- and request-independent, so sharing it across SSR requests is
|
|
235
|
+
* safe. 512 entries / ~16 MB.
|
|
236
|
+
*/
|
|
237
|
+
export const tokenCache = new TokenCache({ maxEntries: 512, maxBytes: 16 * 1024 * 1024 });
|
|
238
|
+
const cacheKey = (code, lang) => `${lang}\u0000${THEME_KEY}\u0000${hashString(code)}`;
|
|
239
|
+
/** Cached tokens for `code`, if any (no tokenization). `"text"` is always available. */
|
|
240
|
+
export function cachedTokens(code, lang) {
|
|
241
|
+
if (lang === "text")
|
|
242
|
+
return plainLines(code);
|
|
243
|
+
return tokenCache.get(cacheKey(code, lang), code);
|
|
244
|
+
}
|
|
245
|
+
/** Stores tokens produced elsewhere (e.g. a finished {@link StreamingTokenizer}). */
|
|
246
|
+
export function storeTokens(code, lang, lines) {
|
|
247
|
+
if (lang !== "text")
|
|
248
|
+
tokenCache.set(cacheKey(code, lang), code, lines);
|
|
249
|
+
}
|
|
250
|
+
/** {@link tokenize} through the cache; requires `isLanguageReady(lang)`. */
|
|
251
|
+
export function tokenizeCached(h, code, lang) {
|
|
252
|
+
const hit = cachedTokens(code, lang);
|
|
253
|
+
if (hit !== undefined)
|
|
254
|
+
return hit;
|
|
255
|
+
const lines = tokenize(h, code, lang);
|
|
256
|
+
storeTokens(code, lang, lines);
|
|
257
|
+
return lines;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Incremental tokenizer for code that only grows at the end (streaming). Completed lines are
|
|
261
|
+
* tokenized once and cached together with the grammar state; each update re-tokenizes just the
|
|
262
|
+
* newly completed lines plus the trailing partial line.
|
|
263
|
+
*/
|
|
264
|
+
export class StreamingTokenizer {
|
|
265
|
+
h;
|
|
266
|
+
lang;
|
|
267
|
+
stableText = "";
|
|
268
|
+
stableLines = [];
|
|
269
|
+
state;
|
|
270
|
+
constructor(h, lang) {
|
|
271
|
+
this.h = h;
|
|
272
|
+
this.lang = lang;
|
|
273
|
+
}
|
|
274
|
+
/** True when `update(code)` only tokenizes what was appended since the previous update. */
|
|
275
|
+
isWarmFor(code) {
|
|
276
|
+
return this.stableText !== "" && code.startsWith(this.stableText);
|
|
277
|
+
}
|
|
278
|
+
update(code) {
|
|
279
|
+
if (this.lang === "text")
|
|
280
|
+
return plainLines(code);
|
|
281
|
+
const cut = code.lastIndexOf("\n") + 1;
|
|
282
|
+
const stable = code.slice(0, cut);
|
|
283
|
+
if (!stable.startsWith(this.stableText))
|
|
284
|
+
this.reset();
|
|
285
|
+
const delta = stable.slice(this.stableText.length);
|
|
286
|
+
if (delta !== "") {
|
|
287
|
+
const result = this.h.codeToTokens(delta.replace(LINE_BREAK_END, ""), this.options());
|
|
288
|
+
this.stableLines = [...this.stableLines, ...result.tokens.map(toLine)];
|
|
289
|
+
this.state = result.grammarState;
|
|
290
|
+
this.stableText = stable;
|
|
291
|
+
}
|
|
292
|
+
const tail = code.slice(cut);
|
|
293
|
+
const tailLine = tail === "" ? [] : (this.h.codeToTokens(tail, this.options()).tokens[0] ?? []);
|
|
294
|
+
return [...this.stableLines, toLine(tailLine)];
|
|
295
|
+
}
|
|
296
|
+
options() {
|
|
297
|
+
const base = { lang: this.lang, ...TOKEN_OPTIONS };
|
|
298
|
+
return this.state ? { ...base, grammarState: this.state } : base;
|
|
299
|
+
}
|
|
300
|
+
reset() {
|
|
301
|
+
this.stableText = "";
|
|
302
|
+
this.stableLines = [];
|
|
303
|
+
this.state = undefined;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
/** File-extension and well-known file-name aliases Shiki doesn't resolve by itself. */
|
|
307
|
+
const EXTENSION_ALIASES = {
|
|
308
|
+
mjs: "javascript",
|
|
309
|
+
cjs: "javascript",
|
|
310
|
+
mts: "typescript",
|
|
311
|
+
cts: "typescript",
|
|
312
|
+
py: "python",
|
|
313
|
+
pyi: "python",
|
|
314
|
+
rs: "rust",
|
|
315
|
+
rb: "ruby",
|
|
316
|
+
kt: "kotlin",
|
|
317
|
+
kts: "kotlin",
|
|
318
|
+
cs: "csharp",
|
|
319
|
+
hpp: "cpp",
|
|
320
|
+
cc: "cpp",
|
|
321
|
+
hh: "cpp",
|
|
322
|
+
h: "c",
|
|
323
|
+
md: "markdown",
|
|
324
|
+
mdx: "mdx",
|
|
325
|
+
yml: "yaml",
|
|
326
|
+
zsh: "bash",
|
|
327
|
+
sh: "bash",
|
|
328
|
+
ps1: "powershell",
|
|
329
|
+
tf: "hcl",
|
|
330
|
+
gql: "graphql",
|
|
331
|
+
lock: "text",
|
|
332
|
+
txt: "text",
|
|
333
|
+
};
|
|
334
|
+
const FILENAME_LANGUAGES = {
|
|
335
|
+
dockerfile: "docker",
|
|
336
|
+
makefile: "make",
|
|
337
|
+
gemfile: "ruby",
|
|
338
|
+
rakefile: "ruby",
|
|
339
|
+
".env": "dotenv",
|
|
340
|
+
".gitignore": "text",
|
|
341
|
+
".bashrc": "bash",
|
|
342
|
+
".zshrc": "bash",
|
|
343
|
+
"bun.lock": "json",
|
|
344
|
+
};
|
|
345
|
+
/** `src/app.tsx` → `tsx`, `Dockerfile` → `docker`, `README.md` → `markdown`; unknown → `"text"`. */
|
|
346
|
+
export function languageForPath(path) {
|
|
347
|
+
const name = (path.split(/[/\\]/).pop() ?? path).toLowerCase();
|
|
348
|
+
const byName = FILENAME_LANGUAGES[name];
|
|
349
|
+
if (byName !== undefined)
|
|
350
|
+
return resolveLanguage(byName);
|
|
351
|
+
const ext = extensionOf(name);
|
|
352
|
+
return ext === undefined ? "text" : resolveLanguage(EXTENSION_ALIASES[ext] ?? ext);
|
|
353
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
/** An action's keys (logical, as in `<Shortcut>`), or undefined when it has no binding. */
|
|
3
|
+
export type ShortcutResolver = (action: string) => readonly string[] | undefined;
|
|
4
|
+
/**
|
|
5
|
+
* The actions the kit's own components take from the keyboard, and their keys when the host binds
|
|
6
|
+
* them to nothing else: a diff's comment is sent with ⌘↵ (Ctrl+Enter elsewhere).
|
|
7
|
+
*/
|
|
8
|
+
export declare const KIT_SHORTCUTS: {
|
|
9
|
+
readonly sendComments: readonly ["mod", "enter"];
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* The host's keymap for every tooltip and menu item below it: `<Tooltip action="…">` shows the keys
|
|
13
|
+
* the action is actually bound to, so a hint can't drift from the binding. Without a provider only the
|
|
14
|
+
* kit's own actions answer (`KIT_SHORTCUTS`).
|
|
15
|
+
*/
|
|
16
|
+
export declare function ShortcutProvider({ resolve, children, }: {
|
|
17
|
+
readonly resolve: ShortcutResolver;
|
|
18
|
+
readonly children: ReactNode;
|
|
19
|
+
}): import("react").JSX.Element;
|
|
20
|
+
/** The keys bound to `action`, or undefined (no action, or no binding). */
|
|
21
|
+
export declare function useShortcut(action: string | undefined): readonly string[] | undefined;
|
|
22
|
+
/** The resolver itself, for lists that map many actions at once (a command palette). */
|
|
23
|
+
export declare function useShortcutResolver(): ShortcutResolver;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, use } from "react";
|
|
4
|
+
/**
|
|
5
|
+
* The actions the kit's own components take from the keyboard, and their keys when the host binds
|
|
6
|
+
* them to nothing else: a diff's comment is sent with ⌘↵ (Ctrl+Enter elsewhere).
|
|
7
|
+
*/
|
|
8
|
+
export const KIT_SHORTCUTS = {
|
|
9
|
+
sendComments: ["mod", "enter"],
|
|
10
|
+
};
|
|
11
|
+
const KIT_KEYS = new Map(Object.entries(KIT_SHORTCUTS));
|
|
12
|
+
const ShortcutContext = createContext((action) => KIT_KEYS.get(action));
|
|
13
|
+
/**
|
|
14
|
+
* The host's keymap for every tooltip and menu item below it: `<Tooltip action="…">` shows the keys
|
|
15
|
+
* the action is actually bound to, so a hint can't drift from the binding. Without a provider only the
|
|
16
|
+
* kit's own actions answer (`KIT_SHORTCUTS`).
|
|
17
|
+
*/
|
|
18
|
+
export function ShortcutProvider({ resolve, children, }) {
|
|
19
|
+
return _jsx(ShortcutContext, { value: resolve, children: children });
|
|
20
|
+
}
|
|
21
|
+
/** The keys bound to `action`, or undefined (no action, or no binding). */
|
|
22
|
+
export function useShortcut(action) {
|
|
23
|
+
const resolve = use(ShortcutContext);
|
|
24
|
+
return action === undefined ? undefined : resolve(action);
|
|
25
|
+
}
|
|
26
|
+
/** The resolver itself, for lists that map many actions at once (a command palette). */
|
|
27
|
+
export function useShortcutResolver() {
|
|
28
|
+
return use(ShortcutContext);
|
|
29
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text measured and shaped the way people read it: in graphemes (`Intl.Segmenter`), so a cut never
|
|
3
|
+
* splits an emoji, a flag or a letter with its accents, in any script; cased, sorted and listed in the
|
|
4
|
+
* reader's locale. Each `Intl` object is built once per locale and options and kept, keyed by them, so
|
|
5
|
+
* one process can serve several locales.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* How many words the text has, as its language divides them (`Intl.Segmenter`, granularity
|
|
9
|
+
* `word`): Chinese or Japanese, written without spaces, count word by word too.
|
|
10
|
+
*/
|
|
11
|
+
export declare function wordCount(text: string, locale?: string): number;
|
|
12
|
+
/** The text's graphemes, the characters a reader sees ("é" and "👍🏽" are one each). */
|
|
13
|
+
export declare function graphemesOf(text: string, locale?: string): string[];
|
|
14
|
+
/**
|
|
15
|
+
* The text cut to at most `max` graphemes, an ellipsis standing in for the rest. It reads at most
|
|
16
|
+
* `max + 1` graphemes, however long the text.
|
|
17
|
+
*/
|
|
18
|
+
export declare function truncateText(text: string, max: number, locale?: string): string;
|
|
19
|
+
/** The text's first `count` graphemes ("Éla" of "Élan" for 3); it reads no further than that. */
|
|
20
|
+
export declare function graphemePrefix(text: string, count: number, locale?: string): string;
|
|
21
|
+
/**
|
|
22
|
+
* The longest start of the text within `max` UTF-16 units that ends between graphemes: typed text
|
|
23
|
+
* kept to a cap a schema counts in `.length`, never splitting an emoji or a letter from its accents.
|
|
24
|
+
* No ellipsis: it is what the person typed, shortened.
|
|
25
|
+
*/
|
|
26
|
+
export declare function clipToLength(text: string, max: number, locale?: string): string;
|
|
27
|
+
/** A collator for `locale`: lists people read sort in their own language's order. */
|
|
28
|
+
export declare function collatorFor(locale: string, options?: Intl.CollatorOptions): Intl.Collator;
|
|
29
|
+
/** Items as one phrase in a locale's list pattern ("a, b and c", "a, b or c"), as the catalogues join them. */
|
|
30
|
+
export declare function formatList(items: readonly string[], locale: string, type?: Intl.ListFormatType): string;
|
|
31
|
+
/**
|
|
32
|
+
* The parts of one status as a screen reader says them ("Running, rate limited, 1 minute"): the
|
|
33
|
+
* language's own list separator and no "and" ("a、b、c" in Japanese). It's the narrow conjunction
|
|
34
|
+
* pattern, which every ICU build separates; the `unit` pattern lost its Russian commas in newer CLDR.
|
|
35
|
+
*/
|
|
36
|
+
export declare function spokenParts(items: readonly string[], locale: string): string;
|
|
37
|
+
/** Upper-cases the first grapheme for `locale` ("élan" → "Élan"); the rest stays as it is. */
|
|
38
|
+
export declare function capitalize(text: string, locale: string): string;
|
|
39
|
+
/**
|
|
40
|
+
* An identifier's words, lower-cased for `locale`: camelCase, kebab-case, snake_case and dotted ids
|
|
41
|
+
* split ("repoName", "repo-name", "repo_name" → "repo name"; "parseHTMLFile" → "parse html file").
|
|
42
|
+
*/
|
|
43
|
+
export declare function idWords(id: string, locale: string): string;
|
|
44
|
+
/**
|
|
45
|
+
* An identifier as words in sentence case, for a label it doesn't have: "spendByDay" → "Spend by
|
|
46
|
+
* day", "team_invite" → "Team invite". The id itself when it has no words.
|
|
47
|
+
*/
|
|
48
|
+
export declare function humanizeId(id: string, locale: string): string;
|
|
49
|
+
/**
|
|
50
|
+
* Up to `max` initials of a name, upper-cased for `locale`: the first grapheme of its first word and
|
|
51
|
+
* of its last ones ("Ava Chen" and "Ava Maria Chen" → "AC", "kai-rivera" → "KR", "Milo" → "M",
|
|
52
|
+
* "émile zola" → "ÉZ"). Spaces and punctuation separate words. Empty when the name has none.
|
|
53
|
+
*/
|
|
54
|
+
export declare function initialsOf(name: string, locale: string, max?: number): string;
|
|
55
|
+
/** A file name's extension, lower case ("tsx"), or undefined for none (`Makefile`, `.env`, `notes.`). */
|
|
56
|
+
export declare function extensionOf(name: string): string | undefined;
|