@emoji-components/core 0.0.0-stage → 1.0.0-alpha.2
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/LICENSE +21 -0
- package/README.md +45 -2
- package/THIRD_PARTY_NOTICES.md +17 -0
- package/dist/build.d.ts +27 -0
- package/dist/build.js +12 -0
- package/dist/change.js +40 -0
- package/dist/change.js.map +1 -0
- package/dist/chunk-UG4NPOXS.js +131 -0
- package/dist/cli.js +66 -0
- package/dist/custom.js +47 -0
- package/dist/custom.js.map +1 -0
- package/dist/dataset-BQY5KQKL.d.ts +58 -0
- package/dist/dataset.js +56 -0
- package/dist/dataset.js.map +1 -0
- package/dist/frequents.js +124 -0
- package/dist/frequents.js.map +1 -0
- package/dist/grid.d.ts +54 -0
- package/dist/grid.js +11 -0
- package/dist/grid.js.map +1 -0
- package/dist/index.d.ts +173 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/internal.d.ts +64 -0
- package/dist/internal.js +33 -0
- package/dist/internal.js.map +1 -0
- package/dist/keys.js +8 -0
- package/dist/keys.js.map +1 -0
- package/dist/labels.js +38 -0
- package/dist/labels.js.map +1 -0
- package/dist/native-B0Oa7IDL.d.ts +23 -0
- package/dist/native-probe.js +157 -0
- package/dist/native-probe.js.map +1 -0
- package/dist/native.d.ts +2 -0
- package/dist/native.js +10 -0
- package/dist/native.js.map +1 -0
- package/dist/picker/grid.js +90 -0
- package/dist/picker/grid.js.map +1 -0
- package/dist/picker/navigation.js +34 -0
- package/dist/picker/navigation.js.map +1 -0
- package/dist/picker/picker.js +225 -0
- package/dist/picker/picker.js.map +1 -0
- package/dist/picker/sections.js +78 -0
- package/dist/picker/sections.js.map +1 -0
- package/dist/picker/strip.js +89 -0
- package/dist/picker/strip.js.map +1 -0
- package/dist/platform.js +23 -0
- package/dist/platform.js.map +1 -0
- package/dist/reactive.js +73 -0
- package/dist/reactive.js.map +1 -0
- package/dist/resolve.js +63 -0
- package/dist/resolve.js.map +1 -0
- package/dist/search/query.js +69 -0
- package/dist/search/query.js.map +1 -0
- package/dist/search/search-index.js +122 -0
- package/dist/search/search-index.js.map +1 -0
- package/dist/search/search.js +82 -0
- package/dist/search/search.js.map +1 -0
- package/dist/search/tokenize.js +48 -0
- package/dist/search/tokenize.js.map +1 -0
- package/dist/sections-Bwe4sMgE.d.ts +252 -0
- package/dist/shortcodes.js +46 -0
- package/dist/shortcodes.js.map +1 -0
- package/dist/store.js +207 -0
- package/dist/store.js.map +1 -0
- package/dist/suggestions.js +118 -0
- package/dist/suggestions.js.map +1 -0
- package/dist/sync-attributes.js +32 -0
- package/dist/sync-attributes.js.map +1 -0
- package/package.json +86 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Brian Hung
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,46 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @emoji-components/core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Framework-neutral data, search, and state for emoji pickers, with no dependencies.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm i @emoji-components/core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { createEmojiPicker, createEmojiStore, createEmojiSuggestions } from "@emoji-components/core";
|
|
15
|
+
|
|
16
|
+
const input = document.querySelector("input")!;
|
|
17
|
+
const store = createEmojiStore();
|
|
18
|
+
await store.load();
|
|
19
|
+
|
|
20
|
+
const picker = createEmojiPicker(store, { frequent: 16 });
|
|
21
|
+
picker.subscribe(() => console.log(picker.getSnapshot().sections));
|
|
22
|
+
picker.setQuery("cat");
|
|
23
|
+
|
|
24
|
+
const typeahead = createEmojiSuggestions(store, { onChoose: (s) => console.log(typeahead.textFor(s)) });
|
|
25
|
+
input.addEventListener("keydown", (e) => typeahead.keydown(e));
|
|
26
|
+
const stop = typeahead.syncCombobox(input);
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`data` defaults to the English dataset of [`@emoji-components/data`](../data/README.md) on jsDelivr, pinned to this version. Uncontrolled frequently used emoji and skin tone persist to `localStorage`; `store.customEmojiErrors()` reports a custom emoji loader that failed, and `store.load()` runs it again.
|
|
30
|
+
|
|
31
|
+
## Entry points
|
|
32
|
+
|
|
33
|
+
| Import | Contents |
|
|
34
|
+
|---|---|
|
|
35
|
+
| `@emoji-components/core` | `createEmojiStore`, `createEmojiPicker`, `createEmojiSuggestions`, `createSearch`, `lexicalSearch`, `resolveEmoji`, `findShortcodeTrigger`, `isComposingKey`, `localeData`, `twemojiUrl`, unified-code helpers |
|
|
36
|
+
| `/grid` | Grid layout and keyboard moves, for your own virtualizer |
|
|
37
|
+
| `/native` | Whether the system font draws an emoji |
|
|
38
|
+
| `/build` | `buildDataset` from `emojibase-data` (install it alongside), also as the `npx @emoji-components/core build-data <locale>` CLI |
|
|
39
|
+
|
|
40
|
+
## Docs
|
|
41
|
+
|
|
42
|
+
[emoji.brianhung.me](https://emoji.brianhung.me)
|
|
43
|
+
|
|
44
|
+
## License
|
|
45
|
+
|
|
46
|
+
MIT. Third-party notices: [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Third-Party Notices
|
|
2
|
+
|
|
3
|
+
## Emoji data
|
|
4
|
+
|
|
5
|
+
Datasets that `build-data` writes are built from [emojibase-data](https://github.com/milesj/emojibase), MIT License, Copyright (c) 2017-2019 Miles Johnson. Its iamcal shortcodes come from Emojibase's copy of [iamcal/emoji-data](https://github.com/iamcal/emoji-data), MIT License, Copyright (c) 2013 Cal Henderson.
|
|
6
|
+
|
|
7
|
+
## Twemoji
|
|
8
|
+
|
|
9
|
+
With the default `imageUrl`, emoji images load from [jdecked/twemoji](https://github.com/jdecked/twemoji). The graphics are Copyright 2019 Twitter, Inc and other contributors, licensed under [CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/). Apps that show them should include this attribution. This package does not redistribute the graphics.
|
|
10
|
+
|
|
11
|
+
## MIT License
|
|
12
|
+
|
|
13
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
14
|
+
|
|
15
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
16
|
+
|
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/dist/build.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Dataset } from './index.js';
|
|
2
|
+
|
|
3
|
+
type Load = <T>(file: string) => T;
|
|
4
|
+
type BuildDataOptions = {
|
|
5
|
+
locale: string;
|
|
6
|
+
shortcodes?: readonly string[];
|
|
7
|
+
load?: Load;
|
|
8
|
+
};
|
|
9
|
+
type BuildDataResult = {
|
|
10
|
+
dataset: Dataset;
|
|
11
|
+
stats: {
|
|
12
|
+
emoji: number;
|
|
13
|
+
withShortcodes: number;
|
|
14
|
+
computedTones: number;
|
|
15
|
+
toneExceptions: number;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
declare function findEmojibase(): {
|
|
19
|
+
root: string;
|
|
20
|
+
load: Load;
|
|
21
|
+
} | undefined;
|
|
22
|
+
declare const emojibaseLocales: (root: string) => string[];
|
|
23
|
+
declare const defaultShortcodeSets: (locale: string) => string[];
|
|
24
|
+
declare function buildDataset({ locale, shortcodes: sets, load }: BuildDataOptions): BuildDataResult;
|
|
25
|
+
|
|
26
|
+
export { buildDataset, defaultShortcodeSets, emojibaseLocales, findEmojibase };
|
|
27
|
+
export type { BuildDataOptions, BuildDataResult };
|
package/dist/build.js
ADDED
package/dist/change.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// src/change.ts
|
|
2
|
+
function createChangeDetails(reason, event, trigger) {
|
|
3
|
+
let canceled = false;
|
|
4
|
+
let propagates = false;
|
|
5
|
+
return {
|
|
6
|
+
reason,
|
|
7
|
+
event: event ?? new Event("emoji-components"),
|
|
8
|
+
trigger,
|
|
9
|
+
cancel: () => void (canceled = true),
|
|
10
|
+
get isCanceled() {
|
|
11
|
+
return canceled;
|
|
12
|
+
},
|
|
13
|
+
allowPropagation: () => void (propagates = true),
|
|
14
|
+
get isPropagationAllowed() {
|
|
15
|
+
return propagates;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function controllable({ controlled, own, onChange, write, writeControlled }) {
|
|
20
|
+
const value = () => {
|
|
21
|
+
const outer = controlled();
|
|
22
|
+
return outer === void 0 ? own() : outer;
|
|
23
|
+
};
|
|
24
|
+
return {
|
|
25
|
+
value,
|
|
26
|
+
set(next, details) {
|
|
27
|
+
if (Object.is(next, value())) return void 0;
|
|
28
|
+
onChange()?.(next, details);
|
|
29
|
+
if (details.isCanceled) return details;
|
|
30
|
+
if (controlled() === void 0) write(next);
|
|
31
|
+
else writeControlled?.(next);
|
|
32
|
+
return details;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export {
|
|
37
|
+
controllable,
|
|
38
|
+
createChangeDetails
|
|
39
|
+
};
|
|
40
|
+
//# sourceMappingURL=change.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/change.ts"],"sourcesContent":["export interface ChangeDetails<Reason extends string = string> {\n reason: Reason;\n event: Event;\n trigger: Element | undefined;\n cancel(): void;\n readonly isCanceled: boolean;\n allowPropagation(): void;\n readonly isPropagationAllowed: boolean;\n}\n\nexport function createChangeDetails<Reason extends string>(reason: Reason, event?: Event, trigger?: Element): ChangeDetails<Reason> {\n let canceled = false;\n let propagates = false;\n return {\n reason,\n event: event ?? new Event(\"emoji-components\"),\n trigger,\n cancel: () => void (canceled = true),\n get isCanceled() {\n return canceled;\n },\n allowPropagation: () => void (propagates = true),\n get isPropagationAllowed() {\n return propagates;\n },\n };\n}\n\nexport interface Controllable<T, D extends ChangeDetails> {\n value(): T;\n set(next: T, details: D): D | undefined;\n}\n\nexport interface ControllableOptions<T, D extends ChangeDetails> {\n controlled(): T | undefined;\n own(): T;\n onChange(): ((value: T, details: D) => void) | undefined;\n write(value: T): void;\n // A controlled value can keep a local copy until its owner answers\n writeControlled?(value: T): void;\n}\n\n// Controlled or own state with Base UI's onChange(value, details): the owner hears first and can cancel()\nexport function controllable<T, D extends ChangeDetails>({ controlled, own, onChange, write, writeControlled }: ControllableOptions<T, D>): Controllable<T, D> {\n const value = () => {\n const outer = controlled();\n return outer === undefined ? own() : outer;\n };\n return {\n value,\n set(next, details) {\n if (Object.is(next, value())) return undefined;\n onChange()?.(next, details);\n if (details.isCanceled) return details;\n if (controlled() === undefined) write(next);\n else writeControlled?.(next);\n return details;\n },\n };\n}\n"],"mappings":";AAUO,SAAS,oBAA2C,QAAgB,OAAe,SAA0C;AAClI,MAAI,WAAW;AACf,MAAI,aAAa;AACjB,SAAO;AAAA,IACL;AAAA,IACA,OAAO,SAAS,IAAI,MAAM,kBAAkB;AAAA,IAC5C;AAAA,IACA,QAAQ,MAAM,MAAM,WAAW;AAAA,IAC/B,IAAI,aAAa;AACf,aAAO;AAAA,IACT;AAAA,IACA,kBAAkB,MAAM,MAAM,aAAa;AAAA,IAC3C,IAAI,uBAAuB;AACzB,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAiBO,SAAS,aAAyC,EAAE,YAAY,KAAK,UAAU,OAAO,gBAAgB,GAAkD;AAC7J,QAAM,QAAQ,MAAM;AAClB,UAAM,QAAQ,WAAW;AACzB,WAAO,UAAU,SAAY,IAAI,IAAI;AAAA,EACvC;AACA,SAAO;AAAA,IACL;AAAA,IACA,IAAI,MAAM,SAAS;AACjB,UAAI,OAAO,GAAG,MAAM,MAAM,CAAC,EAAG,QAAO;AACrC,eAAS,IAAI,MAAM,OAAO;AAC1B,UAAI,QAAQ,WAAY,QAAO;AAC/B,UAAI,WAAW,MAAM,OAAW,OAAM,IAAI;AAAA,UACrC,mBAAkB,IAAI;AAC3B,aAAO;AAAA,IACT;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// scripts/build-data.ts
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import { createRequire } from "module";
|
|
4
|
+
import path from "path";
|
|
5
|
+
|
|
6
|
+
// ../../node_modules/emojibase/esm/index.js
|
|
7
|
+
var SEQUENCE_REMOVAL_PATTERN = /200D|FE0E|FE0F/g;
|
|
8
|
+
var GROUP_KEY_PEOPLE_BODY = "people-body";
|
|
9
|
+
var GROUP_KEY_ANIMALS_NATURE = "animals-nature";
|
|
10
|
+
var GROUP_KEY_FOOD_DRINK = "food-drink";
|
|
11
|
+
var GROUP_KEY_TRAVEL_PLACES = "travel-places";
|
|
12
|
+
var GROUP_KEY_ACTIVITIES = "activities";
|
|
13
|
+
var GROUP_KEY_OBJECTS = "objects";
|
|
14
|
+
var GROUP_KEY_SYMBOLS = "symbols";
|
|
15
|
+
var GROUP_KEY_FLAGS = "flags";
|
|
16
|
+
function joinShortcodesToEmoji(emoji, shortcodeDatasets) {
|
|
17
|
+
if (shortcodeDatasets.length === 0) return emoji;
|
|
18
|
+
const list = new Set(emoji.shortcodes);
|
|
19
|
+
shortcodeDatasets.forEach((dataset) => {
|
|
20
|
+
const shortcodes = dataset[emoji.hexcode];
|
|
21
|
+
if (Array.isArray(shortcodes)) shortcodes.forEach((code) => list.add(code));
|
|
22
|
+
else if (shortcodes) list.add(shortcodes);
|
|
23
|
+
});
|
|
24
|
+
emoji.shortcodes = [...list];
|
|
25
|
+
if (emoji.skins) emoji.skins.forEach((skin) => {
|
|
26
|
+
joinShortcodesToEmoji(skin, shortcodeDatasets);
|
|
27
|
+
});
|
|
28
|
+
return emoji;
|
|
29
|
+
}
|
|
30
|
+
var STRIP_PATTERN = new RegExp(`(-| )?(${SEQUENCE_REMOVAL_PATTERN.source})`, "g");
|
|
31
|
+
|
|
32
|
+
// src/dataset.ts
|
|
33
|
+
var GROUPS = ["smileys-people", "animals-nature", "food-drink", "travel-places", "activities", "objects", "symbols", "flags"];
|
|
34
|
+
var SKIN_TONES = ["1f3fb", "1f3fc", "1f3fd", "1f3fe", "1f3ff"];
|
|
35
|
+
var TONE_CODES = new Set(SKIN_TONES);
|
|
36
|
+
function withTone(unified, tone) {
|
|
37
|
+
const [first, ...rest] = unified.split("-");
|
|
38
|
+
if (rest[0] === "fe0f") rest.shift();
|
|
39
|
+
return [first, SKIN_TONES[tone - 1], ...rest].join("-");
|
|
40
|
+
}
|
|
41
|
+
var nativeToUnified = (text) => [...text].map((c) => c.codePointAt(0).toString(16)).join("-");
|
|
42
|
+
|
|
43
|
+
// scripts/build-data.ts
|
|
44
|
+
function findEmojibase() {
|
|
45
|
+
for (const base of [path.join(process.cwd(), "package.json"), import.meta.url]) {
|
|
46
|
+
const require2 = createRequire(base);
|
|
47
|
+
try {
|
|
48
|
+
const root = path.dirname(require2.resolve("emojibase-data/package.json"));
|
|
49
|
+
return { root, load: (file) => require2(`emojibase-data/${file}`) };
|
|
50
|
+
} catch {
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return void 0;
|
|
54
|
+
}
|
|
55
|
+
var emojibaseLocales = (root) => fs.readdirSync(root).filter((name) => fs.existsSync(path.join(root, name, "data.json"))).sort();
|
|
56
|
+
var isEnglish = (locale) => locale.split("-")[0] === "en";
|
|
57
|
+
var defaultShortcodeSets = (locale) => isEnglish(locale) ? ["iamcal"] : ["iamcal", "cldr"];
|
|
58
|
+
var GROUP_OF = { 0: 0, 1: 0, 3: 1, 4: 2, 5: 3, 6: 4, 7: 5, 8: 6, 9: 7 };
|
|
59
|
+
var MESSAGE_KEY = {
|
|
60
|
+
"smileys-people": GROUP_KEY_PEOPLE_BODY,
|
|
61
|
+
"animals-nature": GROUP_KEY_ANIMALS_NATURE,
|
|
62
|
+
"food-drink": GROUP_KEY_FOOD_DRINK,
|
|
63
|
+
"travel-places": GROUP_KEY_TRAVEL_PLACES,
|
|
64
|
+
activities: GROUP_KEY_ACTIVITIES,
|
|
65
|
+
objects: GROUP_KEY_OBJECTS,
|
|
66
|
+
symbols: GROUP_KEY_SYMBOLS,
|
|
67
|
+
flags: GROUP_KEY_FLAGS
|
|
68
|
+
};
|
|
69
|
+
var ENGLISH_GROUPS = ["Smileys & People", "Animals & Nature", "Food & Drink", "Travel & Places", "Activities", "Objects", "Symbols", "Flags"];
|
|
70
|
+
var toArray = (v) => v === void 0 ? [] : Array.isArray(v) ? v : [v];
|
|
71
|
+
var loadShortcodes = (load, locale, sets) => sets.map((set) => {
|
|
72
|
+
const codes = tryLoad(load, `${locale}/shortcodes/${set}.json`) ?? tryLoad(load, `en/shortcodes/${set}.json`);
|
|
73
|
+
if (!codes) throw new Error(`emojibase-data has no "${set}" shortcodes`);
|
|
74
|
+
return codes;
|
|
75
|
+
});
|
|
76
|
+
function tryLoad(load, file) {
|
|
77
|
+
try {
|
|
78
|
+
return load(file);
|
|
79
|
+
} catch {
|
|
80
|
+
return void 0;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function buildDataset({ locale, shortcodes: sets = defaultShortcodeSets(locale), load = findEmojibase()?.load }) {
|
|
84
|
+
if (!load) throw new Error("emojibase-data is not installed");
|
|
85
|
+
const records = tryLoad(load, `${locale}/data.json`);
|
|
86
|
+
if (!records) throw new Error(`emojibase-data has no locale "${locale}"`);
|
|
87
|
+
const messages = load(`${locale}/messages.json`);
|
|
88
|
+
const { version } = load("package.json");
|
|
89
|
+
const shortcodes = loadShortcodes(load, locale, sets);
|
|
90
|
+
const lower = (s) => s.toLocaleLowerCase(locale);
|
|
91
|
+
let computedTones = 0;
|
|
92
|
+
const toneExceptions = {};
|
|
93
|
+
const emojis = [];
|
|
94
|
+
for (const r of [...records].sort((a, b) => (a.order ?? 0) - (b.order ?? 0))) {
|
|
95
|
+
const group = GROUP_OF[r.group ?? -1];
|
|
96
|
+
if (group === void 0) continue;
|
|
97
|
+
const unified = nativeToUnified(r.emoji);
|
|
98
|
+
const tones = (r.skins ?? []).filter((s) => toArray(s.tone).length > 0 && new Set(toArray(s.tone)).size === 1).sort((a, b) => toArray(a.tone)[0] - toArray(b.tone)[0]).filter((s, i, all) => all.findIndex((x) => toArray(x.tone)[0] === toArray(s.tone)[0]) === i).map((s) => nativeToUnified(s.emoji));
|
|
99
|
+
let hasTones = 0;
|
|
100
|
+
if (tones.length === 5) {
|
|
101
|
+
hasTones = 1;
|
|
102
|
+
if (tones.every((t, i) => t === withTone(unified, i + 1))) computedTones++;
|
|
103
|
+
else toneExceptions[unified] = tones;
|
|
104
|
+
} else if (tones.length > 0) {
|
|
105
|
+
toneExceptions[unified] = tones;
|
|
106
|
+
}
|
|
107
|
+
const tags = [...toArray(r.tags).map(lower), ...toArray(r.emoticon)];
|
|
108
|
+
emojis.push([unified, lower(r.label), [...new Set(tags)], joinShortcodesToEmoji({ ...r, skins: void 0 }, shortcodes).shortcodes ?? [], group, r.version, hasTones]);
|
|
109
|
+
}
|
|
110
|
+
const capitalize = (s) => s.charAt(0).toLocaleUpperCase(locale) + s.slice(1);
|
|
111
|
+
const groups = isEnglish(locale) ? ENGLISH_GROUPS : GROUPS.map((key) => capitalize(messages.groups.find((g) => g.key === MESSAGE_KEY[key])?.message ?? key));
|
|
112
|
+
return {
|
|
113
|
+
dataset: { format: 1, locale, emojibase: version, groups, emojis, toneExceptions },
|
|
114
|
+
stats: { emoji: emojis.length, withShortcodes: emojis.filter((e) => e[3].length).length, computedTones, toneExceptions: Object.keys(toneExceptions).length }
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export {
|
|
119
|
+
findEmojibase,
|
|
120
|
+
emojibaseLocales,
|
|
121
|
+
defaultShortcodeSets,
|
|
122
|
+
buildDataset
|
|
123
|
+
};
|
|
124
|
+
/*! Bundled license information:
|
|
125
|
+
|
|
126
|
+
emojibase/esm/index.js:
|
|
127
|
+
(**
|
|
128
|
+
* @copyright 2017-2019, Miles Johnson
|
|
129
|
+
* @license https://opensource.org/licenses/MIT
|
|
130
|
+
*)
|
|
131
|
+
*/
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
buildDataset,
|
|
4
|
+
defaultShortcodeSets,
|
|
5
|
+
emojibaseLocales,
|
|
6
|
+
findEmojibase
|
|
7
|
+
} from "./chunk-UG4NPOXS.js";
|
|
8
|
+
|
|
9
|
+
// scripts/cli.ts
|
|
10
|
+
import fs from "fs";
|
|
11
|
+
import path from "path";
|
|
12
|
+
import { parseArgs } from "util";
|
|
13
|
+
import { gzipSync } from "zlib";
|
|
14
|
+
var USAGE = `Usage: emoji-components build-data <locale> [--out <file>] [--shortcodes <sets>]
|
|
15
|
+
|
|
16
|
+
Builds a dataset for <locale> from emojibase-data.
|
|
17
|
+
|
|
18
|
+
--out Where to write it. Default: ./<locale>.json
|
|
19
|
+
--shortcodes emojibase shortcode sets, comma-separated, in priority order:
|
|
20
|
+
iamcal, cldr, cldr-native, emojibase, emojibase-native,
|
|
21
|
+
emojibase-legacy, github, joypixels. A set the locale lacks
|
|
22
|
+
comes from English.
|
|
23
|
+
Default: iamcal for English, iamcal,cldr otherwise.
|
|
24
|
+
|
|
25
|
+
Example: npx @emoji-components/core build-data ja --out public/emoji/ja.json`;
|
|
26
|
+
var INSTALL = `emoji-components build-data reads emojibase-data. Install it in your project:
|
|
27
|
+
|
|
28
|
+
npm install --save-dev emojibase-data
|
|
29
|
+
|
|
30
|
+
or run both together: npx -p emojibase-data -p @emoji-components/core emoji-components build-data <locale>`;
|
|
31
|
+
function fail(message) {
|
|
32
|
+
console.error(message);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
function main(argv) {
|
|
36
|
+
const { values, positionals } = parseArgs({
|
|
37
|
+
args: argv,
|
|
38
|
+
allowPositionals: true,
|
|
39
|
+
options: { out: { type: "string", short: "o" }, shortcodes: { type: "string" }, help: { type: "boolean", short: "h" } }
|
|
40
|
+
});
|
|
41
|
+
const [command, locale] = positionals;
|
|
42
|
+
if (values.help || !command) return console.log(USAGE);
|
|
43
|
+
if (command !== "build-data") fail(`Unknown command "${command}".
|
|
44
|
+
|
|
45
|
+
${USAGE}`);
|
|
46
|
+
if (!locale) fail(USAGE);
|
|
47
|
+
const emojibase = findEmojibase();
|
|
48
|
+
if (!emojibase) fail(INSTALL);
|
|
49
|
+
const locales = emojibaseLocales(emojibase.root);
|
|
50
|
+
if (!locales.includes(locale)) fail(`emojibase-data has no locale "${locale}". Available: ${locales.join(", ")}`);
|
|
51
|
+
const shortcodes = values.shortcodes?.split(",").map((s) => s.trim()).filter(Boolean) ?? defaultShortcodeSets(locale);
|
|
52
|
+
const { dataset, stats } = buildDataset({ locale, shortcodes, load: emojibase.load });
|
|
53
|
+
const out = path.resolve(values.out ?? `${locale}.json`);
|
|
54
|
+
const json = JSON.stringify(dataset);
|
|
55
|
+
fs.mkdirSync(path.dirname(out), { recursive: true });
|
|
56
|
+
fs.writeFileSync(out, json);
|
|
57
|
+
const shown = path.relative(process.cwd(), out);
|
|
58
|
+
console.log(
|
|
59
|
+
`${shown.startsWith("..") ? out : shown}: ${stats.emoji} emoji, ${stats.withShortcodes} with shortcodes (${shortcodes.join(", ")}), ${stats.computedTones} tone sets computed, ${stats.toneExceptions} exceptions, ${(json.length / 1024).toFixed(1)} KB raw / ${(gzipSync(json, { level: 9 }).length / 1024).toFixed(1)} KB gz`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
main(process.argv.slice(2));
|
|
64
|
+
} catch (error) {
|
|
65
|
+
fail(error instanceof Error ? error.message : String(error));
|
|
66
|
+
}
|
package/dist/custom.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// src/custom.ts
|
|
2
|
+
import { toError } from "./reactive.js";
|
|
3
|
+
import { tokenizerFor } from "./search/tokenize.js";
|
|
4
|
+
var uniqueById = (list) => {
|
|
5
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6
|
+
return list.filter((e) => !seen.has(e.id) && seen.add(e.id));
|
|
7
|
+
};
|
|
8
|
+
async function loadCustomEmojis(source, signal) {
|
|
9
|
+
try {
|
|
10
|
+
return { emojis: uniqueById(typeof source === "function" ? await source({ signal }) : source ?? []), errors: [] };
|
|
11
|
+
} catch (e) {
|
|
12
|
+
return { emojis: [], errors: [toError(e)] };
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
var splitWords = (text) => text.replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").split(/[^\p{L}\p{N}]+/u).filter(Boolean).map((w) => w.toLowerCase());
|
|
16
|
+
function customRecord(c, { order = 0, base } = {}) {
|
|
17
|
+
const shortcodes = c.shortcodes ?? [];
|
|
18
|
+
const words = /* @__PURE__ */ new Set([...c.tags ?? [], ...splitWords(c.id), ...shortcodes.flatMap(splitWords)]);
|
|
19
|
+
const tokenize = tokenizerFor();
|
|
20
|
+
if (base) for (const word of [...tokenize(base.name), ...base.tags]) words.add(word);
|
|
21
|
+
for (const word of tokenize(c.name)) words.delete(word);
|
|
22
|
+
return {
|
|
23
|
+
unified: c.id,
|
|
24
|
+
emoji: "",
|
|
25
|
+
name: c.name,
|
|
26
|
+
tags: [...words],
|
|
27
|
+
shortcodes,
|
|
28
|
+
group: "custom",
|
|
29
|
+
version: 0,
|
|
30
|
+
tones: [],
|
|
31
|
+
order,
|
|
32
|
+
imageUrl: c.imageUrl,
|
|
33
|
+
...c.stillUrl ? { stillUrl: c.stillUrl } : {},
|
|
34
|
+
...c.animated ? { animated: true } : {},
|
|
35
|
+
...c.width ? { width: c.width } : {},
|
|
36
|
+
...c.height ? { height: c.height } : {}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
var customRecords = (customs, firstOrder, base) => customs.map((c, i) => customRecord(c, { order: firstOrder + i, base: c.base ? base(c.base) : void 0 }));
|
|
40
|
+
export {
|
|
41
|
+
customRecord,
|
|
42
|
+
customRecords,
|
|
43
|
+
loadCustomEmojis,
|
|
44
|
+
splitWords,
|
|
45
|
+
uniqueById
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=custom.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/custom.ts"],"sourcesContent":["import type { EmojiRecord } from \"./dataset\";\nimport { toError } from \"./reactive\";\nimport { tokenizerFor } from \"./search/tokenize\";\n\nexport interface CustomEmoji {\n id: string;\n name: string;\n imageUrl: string;\n shortcodes?: string[];\n tags?: string[];\n group?: string;\n width?: number;\n height?: number;\n animated?: boolean;\n stillUrl?: string;\n base?: string;\n}\n\nexport type CustomEmojiLoader = (options: { signal: AbortSignal }) => Promise<readonly CustomEmoji[]>;\n\nexport type CustomEmojiSource = readonly CustomEmoji[] | CustomEmojiLoader;\n\nexport const uniqueById = (list: readonly CustomEmoji[]): readonly CustomEmoji[] => {\n const seen = new Set<string>();\n return list.filter((e) => !seen.has(e.id) && seen.add(e.id));\n};\n\nexport async function loadCustomEmojis(source: CustomEmojiSource | undefined, signal: AbortSignal): Promise<{ emojis: readonly CustomEmoji[]; errors: Error[] }> {\n try {\n return { emojis: uniqueById(typeof source === \"function\" ? await source({ signal }) : (source ?? [])), errors: [] };\n } catch (e) {\n return { emojis: [], errors: [toError(e)] };\n }\n}\n\nexport const splitWords = (text: string): string[] =>\n text\n .replace(/([a-z\\d])([A-Z])/g, \"$1 $2\")\n .replace(/([A-Z]+)([A-Z][a-z])/g, \"$1 $2\")\n .split(/[^\\p{L}\\p{N}]+/u)\n .filter(Boolean)\n .map((w) => w.toLowerCase());\n\n// The record a custom emoji becomes: search words come from its id, shortcodes, tags, and the emoji it is based on\nexport function customRecord(c: CustomEmoji, { order = 0, base }: { order?: number; base?: EmojiRecord } = {}): EmojiRecord {\n const shortcodes = c.shortcodes ?? [];\n const words = new Set([...(c.tags ?? []), ...splitWords(c.id), ...shortcodes.flatMap(splitWords)]);\n const tokenize = tokenizerFor();\n if (base) for (const word of [...tokenize(base.name), ...base.tags]) words.add(word);\n for (const word of tokenize(c.name)) words.delete(word);\n return {\n unified: c.id,\n emoji: \"\",\n name: c.name,\n tags: [...words],\n shortcodes,\n group: \"custom\",\n version: 0,\n tones: [],\n order,\n imageUrl: c.imageUrl,\n ...(c.stillUrl ? { stillUrl: c.stillUrl } : {}),\n ...(c.animated ? { animated: true } : {}),\n ...(c.width ? { width: c.width } : {}),\n ...(c.height ? { height: c.height } : {}),\n };\n}\n\nexport const customRecords = (customs: readonly CustomEmoji[], firstOrder: number, base: (unified: string) => EmojiRecord | undefined): EmojiRecord[] =>\n customs.map((c, i) => customRecord(c, { order: firstOrder + i, base: c.base ? base(c.base) : undefined }));\n"],"mappings":";AACA,SAAS,eAAe;AACxB,SAAS,oBAAoB;AAoBtB,IAAM,aAAa,CAAC,SAAyD;AAClF,QAAM,OAAO,oBAAI,IAAY;AAC7B,SAAO,KAAK,OAAO,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE,EAAE,KAAK,KAAK,IAAI,EAAE,EAAE,CAAC;AAC7D;AAEA,eAAsB,iBAAiB,QAAuC,QAAmF;AAC/J,MAAI;AACF,WAAO,EAAE,QAAQ,WAAW,OAAO,WAAW,aAAa,MAAM,OAAO,EAAE,OAAO,CAAC,IAAK,UAAU,CAAC,CAAE,GAAG,QAAQ,CAAC,EAAE;AAAA,EACpH,SAAS,GAAG;AACV,WAAO,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,EAAE;AAAA,EAC5C;AACF;AAEO,IAAM,aAAa,CAAC,SACzB,KACG,QAAQ,qBAAqB,OAAO,EACpC,QAAQ,yBAAyB,OAAO,EACxC,MAAM,iBAAiB,EACvB,OAAO,OAAO,EACd,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC;AAGxB,SAAS,aAAa,GAAgB,EAAE,QAAQ,GAAG,KAAK,IAA4C,CAAC,GAAgB;AAC1H,QAAM,aAAa,EAAE,cAAc,CAAC;AACpC,QAAM,QAAQ,oBAAI,IAAI,CAAC,GAAI,EAAE,QAAQ,CAAC,GAAI,GAAG,WAAW,EAAE,EAAE,GAAG,GAAG,WAAW,QAAQ,UAAU,CAAC,CAAC;AACjG,QAAM,WAAW,aAAa;AAC9B,MAAI,KAAM,YAAW,QAAQ,CAAC,GAAG,SAAS,KAAK,IAAI,GAAG,GAAG,KAAK,IAAI,EAAG,OAAM,IAAI,IAAI;AACnF,aAAW,QAAQ,SAAS,EAAE,IAAI,EAAG,OAAM,OAAO,IAAI;AACtD,SAAO;AAAA,IACL,SAAS,EAAE;AAAA,IACX,OAAO;AAAA,IACP,MAAM,EAAE;AAAA,IACR,MAAM,CAAC,GAAG,KAAK;AAAA,IACf;AAAA,IACA,OAAO;AAAA,IACP,SAAS;AAAA,IACT,OAAO,CAAC;AAAA,IACR;AAAA,IACA,UAAU,EAAE;AAAA,IACZ,GAAI,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,IAAI,CAAC;AAAA,IAC7C,GAAI,EAAE,WAAW,EAAE,UAAU,KAAK,IAAI,CAAC;AAAA,IACvC,GAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAAA,IACpC,GAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,IAAI,CAAC;AAAA,EACzC;AACF;AAEO,IAAM,gBAAgB,CAAC,SAAiC,YAAoB,SACjF,QAAQ,IAAI,CAAC,GAAG,MAAM,aAAa,GAAG,EAAE,OAAO,aAAa,GAAG,MAAM,EAAE,OAAO,KAAK,EAAE,IAAI,IAAI,OAAU,CAAC,CAAC;","names":[]}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
declare const GROUPS: readonly ["smileys-people", "animals-nature", "food-drink", "travel-places", "activities", "objects", "symbols", "flags"];
|
|
2
|
+
type Group = (typeof GROUPS)[number];
|
|
3
|
+
type DatasetEmoji = [unified: string, name: string, tags: string[], shortcodes: string[], group: number, version: number, hasTones: 0 | 1];
|
|
4
|
+
type Dataset = {
|
|
5
|
+
format: 1;
|
|
6
|
+
locale: string;
|
|
7
|
+
emojibase: string;
|
|
8
|
+
groups: string[];
|
|
9
|
+
emojis: DatasetEmoji[];
|
|
10
|
+
toneExceptions: Record<string, string[]>;
|
|
11
|
+
};
|
|
12
|
+
type EmojiDataInputEntry = ({
|
|
13
|
+
emoji: string;
|
|
14
|
+
unified?: never;
|
|
15
|
+
} | {
|
|
16
|
+
unified: string;
|
|
17
|
+
emoji?: never;
|
|
18
|
+
}) & {
|
|
19
|
+
name: string;
|
|
20
|
+
tags?: readonly string[];
|
|
21
|
+
shortcodes?: readonly string[];
|
|
22
|
+
group: Group;
|
|
23
|
+
version?: number;
|
|
24
|
+
};
|
|
25
|
+
type EmojiDataInput = {
|
|
26
|
+
locale: string;
|
|
27
|
+
groups: readonly {
|
|
28
|
+
id: Group;
|
|
29
|
+
name: string;
|
|
30
|
+
}[];
|
|
31
|
+
emojis: readonly EmojiDataInputEntry[];
|
|
32
|
+
};
|
|
33
|
+
type SkinToneValue = 0 | 1 | 2 | 3 | 4 | 5;
|
|
34
|
+
declare const SKIN_TONE_VALUES: readonly SkinToneValue[];
|
|
35
|
+
declare const withoutTone: (unified: string) => string;
|
|
36
|
+
declare function withTone(unified: string, tone: number): string;
|
|
37
|
+
type EmojiRecord = {
|
|
38
|
+
unified: string;
|
|
39
|
+
emoji: string;
|
|
40
|
+
name: string;
|
|
41
|
+
tags: readonly string[];
|
|
42
|
+
shortcodes: readonly string[];
|
|
43
|
+
group: Group | "custom";
|
|
44
|
+
version: number;
|
|
45
|
+
tones: readonly string[];
|
|
46
|
+
order: number;
|
|
47
|
+
imageUrl?: string;
|
|
48
|
+
stillUrl?: string;
|
|
49
|
+
animated?: boolean;
|
|
50
|
+
width?: number;
|
|
51
|
+
height?: number;
|
|
52
|
+
};
|
|
53
|
+
declare const isUnified: (value: string) => boolean;
|
|
54
|
+
declare const unifiedToNative: (unified: string) => string;
|
|
55
|
+
declare const nativeToUnified: (text: string) => string;
|
|
56
|
+
|
|
57
|
+
export { withoutTone as b, SKIN_TONE_VALUES as c, isUnified as i, nativeToUnified as n, unifiedToNative as u, withTone as w };
|
|
58
|
+
export type { Dataset as D, EmojiRecord as E, Group as G, SkinToneValue as S, EmojiDataInput as a };
|
package/dist/dataset.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// src/dataset.ts
|
|
2
|
+
var GROUPS = ["smileys-people", "animals-nature", "food-drink", "travel-places", "activities", "objects", "symbols", "flags"];
|
|
3
|
+
var SKIN_TONES = ["1f3fb", "1f3fc", "1f3fd", "1f3fe", "1f3ff"];
|
|
4
|
+
var SKIN_TONE_VALUES = [0, 1, 2, 3, 4, 5];
|
|
5
|
+
var TONE_CODES = new Set(SKIN_TONES);
|
|
6
|
+
var withoutTone = (unified) => unified.toLowerCase().split("-").filter((part) => !TONE_CODES.has(part)).join("-");
|
|
7
|
+
var withoutFe0f = (unified) => unified.replace(/-fe0f/gi, "");
|
|
8
|
+
function withTone(unified, tone) {
|
|
9
|
+
const [first, ...rest] = unified.split("-");
|
|
10
|
+
if (rest[0] === "fe0f") rest.shift();
|
|
11
|
+
return [first, SKIN_TONES[tone - 1], ...rest].join("-");
|
|
12
|
+
}
|
|
13
|
+
var UNIFIED = /^[0-9a-f]{2,6}(?:-[0-9a-f]{2,6})*$/i;
|
|
14
|
+
var isUnified = (value) => UNIFIED.test(value);
|
|
15
|
+
var unifiedToNative = (unified) => isUnified(unified) ? String.fromCodePoint(...unified.split("-").map((hex) => parseInt(hex, 16))) : "";
|
|
16
|
+
var nativeToUnified = (text) => [...text].map((c) => c.codePointAt(0).toString(16)).join("-");
|
|
17
|
+
var isDataset = (value) => "format" in value && value.format === 1 && "toneExceptions" in value && Array.isArray(value.groups) && (value.emojis.length === 0 || Array.isArray(value.emojis[0]));
|
|
18
|
+
function toDataset(data) {
|
|
19
|
+
if (isDataset(data)) return data;
|
|
20
|
+
return {
|
|
21
|
+
format: 1,
|
|
22
|
+
locale: data.locale,
|
|
23
|
+
emojibase: "",
|
|
24
|
+
groups: GROUPS.map((id) => data.groups.find((g) => g.id === id)?.name ?? id),
|
|
25
|
+
emojis: data.emojis.map((e) => [e.unified?.toLowerCase() ?? nativeToUnified(e.emoji), e.name, e.tags ?? [], e.shortcodes ?? [], GROUPS.indexOf(e.group), e.version ?? 0, 0]),
|
|
26
|
+
toneExceptions: {}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function expandDataset(dataset) {
|
|
30
|
+
return dataset.emojis.map(([unified, name, tags, shortcodes, group, version, hasTones], order) => ({
|
|
31
|
+
unified,
|
|
32
|
+
emoji: unifiedToNative(unified),
|
|
33
|
+
name,
|
|
34
|
+
tags,
|
|
35
|
+
shortcodes,
|
|
36
|
+
group: GROUPS[group] ?? "symbols",
|
|
37
|
+
version,
|
|
38
|
+
tones: dataset.toneExceptions[unified] ?? (hasTones ? [1, 2, 3, 4, 5].map((t) => withTone(unified, t)) : []),
|
|
39
|
+
order
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
export {
|
|
43
|
+
GROUPS,
|
|
44
|
+
SKIN_TONES,
|
|
45
|
+
SKIN_TONE_VALUES,
|
|
46
|
+
expandDataset,
|
|
47
|
+
isDataset,
|
|
48
|
+
isUnified,
|
|
49
|
+
nativeToUnified,
|
|
50
|
+
toDataset,
|
|
51
|
+
unifiedToNative,
|
|
52
|
+
withTone,
|
|
53
|
+
withoutFe0f,
|
|
54
|
+
withoutTone
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=dataset.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/dataset.ts"],"sourcesContent":["export const GROUPS = [\"smileys-people\", \"animals-nature\", \"food-drink\", \"travel-places\", \"activities\", \"objects\", \"symbols\", \"flags\"] as const;\n\nexport type Group = (typeof GROUPS)[number];\n\nexport type DatasetEmoji = [unified: string, name: string, tags: string[], shortcodes: string[], group: number, version: number, hasTones: 0 | 1];\n\nexport type Dataset = {\n format: 1;\n locale: string;\n emojibase: string;\n groups: string[];\n emojis: DatasetEmoji[];\n toneExceptions: Record<string, string[]>;\n};\n\nexport type EmojiDataInputEntry = ({ emoji: string; unified?: never } | { unified: string; emoji?: never }) & {\n name: string;\n tags?: readonly string[];\n shortcodes?: readonly string[];\n group: Group;\n version?: number;\n};\n\nexport type EmojiDataInput = {\n locale: string;\n groups: readonly { id: Group; name: string }[];\n emojis: readonly EmojiDataInputEntry[];\n};\n\nexport const SKIN_TONES = [\"1f3fb\", \"1f3fc\", \"1f3fd\", \"1f3fe\", \"1f3ff\"] as const;\n\nexport type SkinToneValue = 0 | 1 | 2 | 3 | 4 | 5;\n\nexport const SKIN_TONE_VALUES: readonly SkinToneValue[] = [0, 1, 2, 3, 4, 5];\n\nconst TONE_CODES = new Set<string>(SKIN_TONES);\n\nexport const withoutTone = (unified: string) =>\n unified\n .toLowerCase()\n .split(\"-\")\n .filter((part) => !TONE_CODES.has(part))\n .join(\"-\");\n\nexport const withoutFe0f = (unified: string) => unified.replace(/-fe0f/gi, \"\");\n\nexport function withTone(unified: string, tone: number): string {\n const [first, ...rest] = unified.split(\"-\");\n if (rest[0] === \"fe0f\") rest.shift();\n return [first, SKIN_TONES[tone - 1], ...rest].join(\"-\");\n}\n\nexport type EmojiRecord = {\n unified: string;\n emoji: string;\n name: string;\n tags: readonly string[];\n shortcodes: readonly string[];\n group: Group | \"custom\";\n version: number;\n tones: readonly string[];\n order: number;\n imageUrl?: string;\n stillUrl?: string;\n animated?: boolean;\n width?: number;\n height?: number;\n};\n\nconst UNIFIED = /^[0-9a-f]{2,6}(?:-[0-9a-f]{2,6})*$/i;\n\nexport const isUnified = (value: string) => UNIFIED.test(value);\n\nexport const unifiedToNative = (unified: string) => (isUnified(unified) ? String.fromCodePoint(...unified.split(\"-\").map((hex) => parseInt(hex, 16))) : \"\");\n\nexport const nativeToUnified = (text: string) => [...text].map((c) => c.codePointAt(0)!.toString(16)).join(\"-\");\n\n// A prebuilt dataset, not an EmojiDataInput to convert: format 1, with tone exceptions and emojis as tuples\nexport const isDataset = (value: Dataset | EmojiDataInput): value is Dataset =>\n \"format\" in value && value.format === 1 && \"toneExceptions\" in value && Array.isArray(value.groups) && (value.emojis.length === 0 || Array.isArray(value.emojis[0]));\n\nexport function toDataset(data: Dataset | EmojiDataInput): Dataset {\n if (isDataset(data)) return data;\n return {\n format: 1,\n locale: data.locale,\n emojibase: \"\",\n groups: GROUPS.map((id) => data.groups.find((g) => g.id === id)?.name ?? id),\n emojis: data.emojis.map((e) => [e.unified?.toLowerCase() ?? nativeToUnified(e.emoji!), e.name, e.tags ?? [], e.shortcodes ?? [], GROUPS.indexOf(e.group), e.version ?? 0, 0] as DatasetEmoji),\n toneExceptions: {},\n };\n}\n\nexport function expandDataset(dataset: Dataset): EmojiRecord[] {\n return dataset.emojis.map(([unified, name, tags, shortcodes, group, version, hasTones], order) => ({\n unified,\n emoji: unifiedToNative(unified),\n name,\n tags,\n shortcodes,\n group: GROUPS[group] ?? \"symbols\",\n version,\n tones: dataset.toneExceptions[unified] ?? (hasTones ? [1, 2, 3, 4, 5].map((t) => withTone(unified, t)) : []),\n order,\n }));\n}\n"],"mappings":";AAAO,IAAM,SAAS,CAAC,kBAAkB,kBAAkB,cAAc,iBAAiB,cAAc,WAAW,WAAW,OAAO;AA6B9H,IAAM,aAAa,CAAC,SAAS,SAAS,SAAS,SAAS,OAAO;AAI/D,IAAM,mBAA6C,CAAC,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAE3E,IAAM,aAAa,IAAI,IAAY,UAAU;AAEtC,IAAM,cAAc,CAAC,YAC1B,QACG,YAAY,EACZ,MAAM,GAAG,EACT,OAAO,CAAC,SAAS,CAAC,WAAW,IAAI,IAAI,CAAC,EACtC,KAAK,GAAG;AAEN,IAAM,cAAc,CAAC,YAAoB,QAAQ,QAAQ,WAAW,EAAE;AAEtE,SAAS,SAAS,SAAiB,MAAsB;AAC9D,QAAM,CAAC,OAAO,GAAG,IAAI,IAAI,QAAQ,MAAM,GAAG;AAC1C,MAAI,KAAK,CAAC,MAAM,OAAQ,MAAK,MAAM;AACnC,SAAO,CAAC,OAAO,WAAW,OAAO,CAAC,GAAG,GAAG,IAAI,EAAE,KAAK,GAAG;AACxD;AAmBA,IAAM,UAAU;AAET,IAAM,YAAY,CAAC,UAAkB,QAAQ,KAAK,KAAK;AAEvD,IAAM,kBAAkB,CAAC,YAAqB,UAAU,OAAO,IAAI,OAAO,cAAc,GAAG,QAAQ,MAAM,GAAG,EAAE,IAAI,CAAC,QAAQ,SAAS,KAAK,EAAE,CAAC,CAAC,IAAI;AAEjJ,IAAM,kBAAkB,CAAC,SAAiB,CAAC,GAAG,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,EAAG,SAAS,EAAE,CAAC,EAAE,KAAK,GAAG;AAGvG,IAAM,YAAY,CAAC,UACxB,YAAY,SAAS,MAAM,WAAW,KAAK,oBAAoB,SAAS,MAAM,QAAQ,MAAM,MAAM,MAAM,MAAM,OAAO,WAAW,KAAK,MAAM,QAAQ,MAAM,OAAO,CAAC,CAAC;AAE7J,SAAS,UAAU,MAAyC;AACjE,MAAI,UAAU,IAAI,EAAG,QAAO;AAC5B,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,QAAQ,KAAK;AAAA,IACb,WAAW;AAAA,IACX,QAAQ,OAAO,IAAI,CAAC,OAAO,KAAK,OAAO,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,QAAQ,EAAE;AAAA,IAC3E,QAAQ,KAAK,OAAO,IAAI,CAAC,MAAM,CAAC,EAAE,SAAS,YAAY,KAAK,gBAAgB,EAAE,KAAM,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,GAAG,EAAE,cAAc,CAAC,GAAG,OAAO,QAAQ,EAAE,KAAK,GAAG,EAAE,WAAW,GAAG,CAAC,CAAiB;AAAA,IAC5L,gBAAgB,CAAC;AAAA,EACnB;AACF;AAEO,SAAS,cAAc,SAAiC;AAC7D,SAAO,QAAQ,OAAO,IAAI,CAAC,CAAC,SAAS,MAAM,MAAM,YAAY,OAAO,SAAS,QAAQ,GAAG,WAAW;AAAA,IACjG;AAAA,IACA,OAAO,gBAAgB,OAAO;AAAA,IAC9B;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO,OAAO,KAAK,KAAK;AAAA,IACxB;AAAA,IACA,OAAO,QAAQ,eAAe,OAAO,MAAM,WAAW,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC,EAAE,IAAI,CAAC,MAAM,SAAS,SAAS,CAAC,CAAC,IAAI,CAAC;AAAA,IAC1G;AAAA,EACF,EAAE;AACJ;","names":[]}
|