@emoji-components/core 1.0.0-alpha.1 → 1.0.0-alpha.3

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 CHANGED
@@ -13,28 +13,29 @@ npm i @emoji-components/core
13
13
  ```ts
14
14
  import { createEmojiPicker, createEmojiStore, createEmojiSuggestions } from "@emoji-components/core";
15
15
 
16
+ const input = document.querySelector("input")!;
16
17
  const store = createEmojiStore();
17
18
  await store.load();
18
19
 
19
20
  const picker = createEmojiPicker(store, { frequent: 16 });
20
- picker.subscribe(() => render(picker.getSnapshot().sections));
21
+ picker.subscribe(() => console.log(picker.getSnapshot().sections));
21
22
  picker.setQuery("cat");
22
23
 
23
- const typeahead = createEmojiSuggestions(store, { onChoose: (s) => insert(typeahead.textFor(s)) });
24
- input.addEventListener("keydown", (e) => typeahead.keydown(e));
25
- const stop = typeahead.syncCombobox(input);
24
+ const suggestions = createEmojiSuggestions(store, { onChoose: (s) => console.log(suggestions.textFor(s)) });
25
+ input.addEventListener("keydown", (e) => suggestions.keydown(e));
26
+ const stop = suggestions.syncCombobox(input);
26
27
  ```
27
28
 
28
- `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.packErrors()` reports a custom emoji loader that failed.
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.
29
30
 
30
31
  ## Entry points
31
32
 
32
33
  | Import | Contents |
33
34
  |---|---|
34
- | `@emoji-components/core` | `createEmojiStore`, `createEmojiPicker`, `createEmojiSuggestions`, `createSearch`, `lexicalSearch`, `resolveEmoji`, `findShortcodeTrigger`, `isComposingKey`, `localeData`, `twemojiUrl`, unified-code helpers |
35
+ | `@emoji-components/core` | `createEmojiStore`, `createEmojiPicker`, `createEmojiSuggestions`, `createEmojiSearch`, `lexicalSearch`, `resolveEmoji`, `findShortcodeTrigger`, `isComposingKey`, `localeData`, `twemojiUrl`, unified-code helpers |
35
36
  | `/grid` | Grid layout and keyboard moves, for your own virtualizer |
36
37
  | `/native` | Whether the system font draws an emoji |
37
- | `/build` | `buildDataset` from `emojibase-data`, also as the `npx @emoji-components/core build-data <locale>` CLI |
38
+ | `/build` | `buildDataset` from `emojibase-data` (install it alongside), also as the `npx @emoji-components/core build-data <locale>` CLI |
38
39
 
39
40
  ## Docs
40
41
 
package/dist/build.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  defaultShortcodeSets,
4
4
  emojibaseLocales,
5
5
  findEmojibase
6
- } from "./chunk-UG4NPOXS.js";
6
+ } from "./chunk-MJPQEHGK.js";
7
7
  export {
8
8
  buildDataset,
9
9
  defaultShortcodeSets,
@@ -5,6 +5,7 @@ import path from "path";
5
5
 
6
6
  // ../../node_modules/emojibase/esm/index.js
7
7
  var SEQUENCE_REMOVAL_PATTERN = /200D|FE0E|FE0F/g;
8
+ var GROUP_KEY_SMILEYS_EMOTION = "smileys-emotion";
8
9
  var GROUP_KEY_PEOPLE_BODY = "people-body";
9
10
  var GROUP_KEY_ANIMALS_NATURE = "animals-nature";
10
11
  var GROUP_KEY_FOOD_DRINK = "food-drink";
@@ -57,7 +58,7 @@ var isEnglish = (locale) => locale.split("-")[0] === "en";
57
58
  var defaultShortcodeSets = (locale) => isEnglish(locale) ? ["iamcal"] : ["iamcal", "cldr"];
58
59
  var GROUP_OF = { 0: 0, 1: 0, 3: 1, 4: 2, 5: 3, 6: 4, 7: 5, 8: 6, 9: 7 };
59
60
  var MESSAGE_KEY = {
60
- "smileys-people": GROUP_KEY_PEOPLE_BODY,
61
+ "smileys-people": [GROUP_KEY_SMILEYS_EMOTION, GROUP_KEY_PEOPLE_BODY],
61
62
  "animals-nature": GROUP_KEY_ANIMALS_NATURE,
62
63
  "food-drink": GROUP_KEY_FOOD_DRINK,
63
64
  "travel-places": GROUP_KEY_TRAVEL_PLACES,
@@ -108,7 +109,9 @@ function buildDataset({ locale, shortcodes: sets = defaultShortcodeSets(locale),
108
109
  emojis.push([unified, lower(r.label), [...new Set(tags)], joinShortcodesToEmoji({ ...r, skins: void 0 }, shortcodes).shortcodes ?? [], group, r.version, hasTones]);
109
110
  }
110
111
  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
+ const message = (key) => messages.groups.find((g) => g.key === key)?.message ?? key;
113
+ const list = new Intl.ListFormat(locale, { type: "conjunction" });
114
+ const groups = isEnglish(locale) ? ENGLISH_GROUPS : GROUPS.map((key) => capitalize(list.format(toArray(MESSAGE_KEY[key]).map(message))));
112
115
  return {
113
116
  dataset: { format: 1, locale, emojibase: version, groups, emojis, toneExceptions },
114
117
  stats: { emoji: emojis.length, withShortcodes: emojis.filter((e) => e[3].length).length, computedTones, toneExceptions: Object.keys(toneExceptions).length }
package/dist/cli.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  defaultShortcodeSets,
5
5
  emojibaseLocales,
6
6
  findEmojibase
7
- } from "./chunk-UG4NPOXS.js";
7
+ } from "./chunk-MJPQEHGK.js";
8
8
 
9
9
  // scripts/cli.ts
10
10
  import fs from "fs";
package/dist/frequents.js CHANGED
@@ -3,7 +3,6 @@ import { controllable, createChangeDetails } from "./change.js";
3
3
  import { cached, createStore } from "./reactive.js";
4
4
  var HALF_LIFE = 14 * 864e5;
5
5
  var MAX = 64;
6
- var DEFAULTS = Object.fromEntries(["2764-fe0f", "1f44d", "1f44e", "1f602", "203c-fe0f", "2753"].map((u, i) => [u, { count: 0, lastUsed: -i }]));
7
6
  var NONE = {};
8
7
  var decayed = ({ count, lastUsed }, now) => count * 0.5 ** ((now - lastUsed) / HALF_LIFE);
9
8
  var ranked = (entries, now) => Object.entries(entries).sort((a, b) => decayed(b[1], now) - decayed(a[1], now) || b[1].lastUsed - a[1].lastUsed);
@@ -31,7 +30,7 @@ function reduceFrequents(entries, action) {
31
30
  var reduce = (state, action) => action.type === "own" ? action.entries === state.own ? state : { ...state, own: action.entries } : action.entries === state.controlled ? state : { ...state, controlled: action.entries };
32
31
  function createFrequencyStore({
33
32
  frequents,
34
- defaultFrequents = DEFAULTS,
33
+ defaultFrequents = NONE,
35
34
  onFrequentsChange,
36
35
  deferStorage
37
36
  } = {}) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/frequents.ts"],"sourcesContent":["import { controllable, createChangeDetails, type ChangeDetails } from \"./change\";\nimport { cached, createStore } from \"./reactive\";\n\n// count is the use count decayed by half every 14 days, as of lastUsed\nexport type FrequentEntry = { count: number; lastUsed: number };\nexport type Frequents = Readonly<Record<string, FrequentEntry>>;\nexport type FrequentsChangeDetails = ChangeDetails<\"pick\" | \"remove\" | \"clear\"> & { emoji?: string };\n\nexport interface FrequencyStore {\n pick(unified: string): void;\n remove(unified: string): void;\n clear(): void;\n top(n: number): readonly string[];\n score(unified: string): number;\n entries(): Frequents;\n subscribe(listener: () => void): () => void;\n}\n\ntype FrequencyStoreOptions = {\n frequents?: Frequents;\n defaultFrequents?: Frequents;\n onFrequentsChange?: (frequents: Frequents, details: FrequentsChangeDetails) => void;\n deferStorage?: boolean;\n};\n\nconst HALF_LIFE = 14 * 864e5;\nconst MAX = 64;\n\n// Real entries that were never used: remove() takes them out and any pick ranks above them\nconst DEFAULTS: Frequents = Object.fromEntries([\"2764-fe0f\", \"1f44d\", \"1f44e\", \"1f602\", \"203c-fe0f\", \"2753\"].map((u, i) => [u, { count: 0, lastUsed: -i }]));\nconst NONE: Frequents = {};\n\nconst decayed = ({ count, lastUsed }: FrequentEntry, now: number) => count * 0.5 ** ((now - lastUsed) / HALF_LIFE);\n// Ties go to the later use: digit-only keys such as \"2753\" would otherwise sort first by object key order\nconst ranked = (entries: Frequents, now: number) => Object.entries(entries).sort((a, b) => decayed(b[1], now) - decayed(a[1], now) || b[1].lastUsed - a[1].lastUsed);\n// The new pick stays: it would rank last against a full list of older uses and never build up a count\nconst capped = (entries: Frequents, now: number, keep: string): Frequents => {\n const top = ranked(entries, now).slice(0, MAX);\n return Object.fromEntries(top.some(([u]) => u === keep) ? top : [...top.slice(0, -1), [keep, entries[keep]!]]);\n};\n\ntype FrequentsAction = { type: \"pick\"; emoji: string; now: number } | { type: \"remove\"; emoji: string } | { type: \"clear\" };\n\nfunction reduceFrequents(entries: Frequents, action: FrequentsAction): Frequents {\n switch (action.type) {\n case \"pick\": {\n const prev = entries[action.emoji];\n return capped({ ...entries, [action.emoji]: { count: (prev ? decayed(prev, action.now) : 0) + 1, lastUsed: action.now } }, action.now, action.emoji);\n }\n case \"remove\": {\n if (!(action.emoji in entries)) return entries;\n const { [action.emoji]: _, ...rest } = entries;\n return rest;\n }\n case \"clear\":\n return NONE;\n default:\n return action satisfies never;\n }\n}\n\ntype FrequencyState = { own: Frequents; controlled: Frequents | undefined };\ntype FrequencyAction = { type: \"own\"; entries: Frequents } | { type: \"control\"; entries: Frequents | undefined };\n\nconst reduce = (state: FrequencyState, action: FrequencyAction): FrequencyState =>\n action.type === \"own\" ? (action.entries === state.own ? state : { ...state, own: action.entries }) : action.entries === state.controlled ? state : { ...state, controlled: action.entries };\n\nexport function createFrequencyStore({\n frequents,\n defaultFrequents = DEFAULTS,\n onFrequentsChange,\n deferStorage,\n}: FrequencyStoreOptions = {}): FrequencyStore & { control(value: Frequents | undefined, onChange: FrequencyStoreOptions[\"onFrequentsChange\"]): void } {\n const storage = persisted(\"emoji-components:frequents\");\n const read = () => parse(storage.read()) ?? defaultFrequents;\n const store = createStore<FrequencyState, FrequencyAction>({ own: deferStorage ? defaultFrequents : read(), controlled: frequents }, reduce);\n let onChange = onFrequentsChange;\n const tops = new WeakMap<Frequents, Map<number, readonly string[]>>();\n const value = controllable<Frequents, FrequentsChangeDetails>({\n controlled: () => store.getSnapshot().controlled,\n own: () => store.getSnapshot().own,\n onChange: () => onChange,\n write(next) {\n storage.write(JSON.stringify(next));\n store.dispatch({ type: \"own\", entries: next });\n },\n });\n const entries = value.value;\n const change = (action: FrequentsAction) => value.set(reduceFrequents(entries(), action), Object.assign(createChangeDetails(action.type), { emoji: action.type === \"clear\" ? undefined : action.emoji }));\n\n return {\n pick: (emoji) => void change({ type: \"pick\", emoji, now: Date.now() }),\n remove: (emoji) => void change({ type: \"remove\", emoji }),\n clear: () => void change({ type: \"clear\" }),\n top(n) {\n const state = entries();\n return cached(\n cached(tops, state, () => new Map()),\n n,\n // Decay keeps the order at any reference time; the newest use avoids reading the clock during render\n () =>\n ranked(state, Math.max(0, ...Object.values(state).map((e) => e.lastUsed)))\n .slice(0, n)\n .map(([u]) => u),\n );\n },\n score(unified) {\n const entry = entries()[unified];\n return entry ? decayed(entry, Date.now()) : 0;\n },\n entries,\n subscribe: readOnFirstSubscribe(store.subscribe, () => store.dispatch({ type: \"own\", entries: read() })),\n control(next, handler) {\n onChange = handler;\n store.dispatch({ type: \"control\", entries: next });\n },\n };\n}\n\n// A server-rendered page hydrates with the defaults; storage is read once something subscribes after mount\nexport function readOnFirstSubscribe(subscribe: (listener: () => void) => () => void, sync: () => void) {\n let synced = false;\n return (listener: () => void) => {\n const unsubscribe = subscribe(listener);\n if (!synced) {\n synced = true;\n sync();\n }\n return unsubscribe;\n };\n}\n\nconst isEntry = (value: unknown): value is FrequentEntry => typeof (value as FrequentEntry)?.count === \"number\" && typeof (value as FrequentEntry).lastUsed === \"number\";\n\nfunction parse(raw: string | null): Frequents | undefined {\n try {\n const value: unknown = raw ? JSON.parse(raw) : undefined;\n return value && typeof value === \"object\" ? Object.fromEntries(Object.entries(value).filter(([, entry]) => isEntry(entry))) : undefined;\n } catch {\n return undefined;\n }\n}\n\n// Node 25+ defines a localStorage global (a warning, or a file shared by every request); only a browser's is per user\nconst browserStorage = () => (typeof window === \"undefined\" ? undefined : window.localStorage);\n\nexport function persisted(key: string): { read(): string | null; write(value: string): void } {\n let memory: string | null = null;\n return {\n read() {\n try {\n return browserStorage()?.getItem(key) ?? memory;\n } catch {\n return memory;\n }\n },\n write(value) {\n memory = value;\n try {\n browserStorage()?.setItem(key, value);\n } catch {}\n },\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/frequents.ts"],"sourcesContent":["import { controllable, createChangeDetails, type ChangeDetails } from \"./change\";\nimport { cached, createStore } from \"./reactive\";\n\n// count is the use count decayed by half every 14 days, as of lastUsed\nexport type FrequentEntry = { count: number; lastUsed: number };\nexport type Frequents = Readonly<Record<string, FrequentEntry>>;\nexport type FrequentsChangeDetails = ChangeDetails<\"pick\" | \"remove\" | \"clear\"> & { emoji?: string };\n\nexport interface FrequencyStore {\n pick(unified: string): void;\n remove(unified: string): void;\n clear(): void;\n top(n: number): readonly string[];\n score(unified: string): number;\n entries(): Frequents;\n subscribe(listener: () => void): () => void;\n}\n\ntype FrequencyStoreOptions = {\n frequents?: Frequents;\n defaultFrequents?: Frequents;\n onFrequentsChange?: (frequents: Frequents, details: FrequentsChangeDetails) => void;\n deferStorage?: boolean;\n};\n\nconst HALF_LIFE = 14 * 864e5;\nconst MAX = 64;\n\nconst NONE: Frequents = {};\n\nconst decayed = ({ count, lastUsed }: FrequentEntry, now: number) => count * 0.5 ** ((now - lastUsed) / HALF_LIFE);\n// Ties go to the later use: digit-only keys such as \"2753\" would otherwise sort first by object key order\nconst ranked = (entries: Frequents, now: number) => Object.entries(entries).sort((a, b) => decayed(b[1], now) - decayed(a[1], now) || b[1].lastUsed - a[1].lastUsed);\n// The new pick stays: it would rank last against a full list of older uses and never build up a count\nconst capped = (entries: Frequents, now: number, keep: string): Frequents => {\n const top = ranked(entries, now).slice(0, MAX);\n return Object.fromEntries(top.some(([u]) => u === keep) ? top : [...top.slice(0, -1), [keep, entries[keep]!]]);\n};\n\ntype FrequentsAction = { type: \"pick\"; emoji: string; now: number } | { type: \"remove\"; emoji: string } | { type: \"clear\" };\n\nfunction reduceFrequents(entries: Frequents, action: FrequentsAction): Frequents {\n switch (action.type) {\n case \"pick\": {\n const prev = entries[action.emoji];\n return capped({ ...entries, [action.emoji]: { count: (prev ? decayed(prev, action.now) : 0) + 1, lastUsed: action.now } }, action.now, action.emoji);\n }\n case \"remove\": {\n if (!(action.emoji in entries)) return entries;\n const { [action.emoji]: _, ...rest } = entries;\n return rest;\n }\n case \"clear\":\n return NONE;\n default:\n return action satisfies never;\n }\n}\n\ntype FrequencyState = { own: Frequents; controlled: Frequents | undefined };\ntype FrequencyAction = { type: \"own\"; entries: Frequents } | { type: \"control\"; entries: Frequents | undefined };\n\nconst reduce = (state: FrequencyState, action: FrequencyAction): FrequencyState =>\n action.type === \"own\" ? (action.entries === state.own ? state : { ...state, own: action.entries }) : action.entries === state.controlled ? state : { ...state, controlled: action.entries };\n\nexport function createFrequencyStore({\n frequents,\n defaultFrequents = NONE,\n onFrequentsChange,\n deferStorage,\n}: FrequencyStoreOptions = {}): FrequencyStore & { control(value: Frequents | undefined, onChange: FrequencyStoreOptions[\"onFrequentsChange\"]): void } {\n const storage = persisted(\"emoji-components:frequents\");\n const read = () => parse(storage.read()) ?? defaultFrequents;\n const store = createStore<FrequencyState, FrequencyAction>({ own: deferStorage ? defaultFrequents : read(), controlled: frequents }, reduce);\n let onChange = onFrequentsChange;\n const tops = new WeakMap<Frequents, Map<number, readonly string[]>>();\n const value = controllable<Frequents, FrequentsChangeDetails>({\n controlled: () => store.getSnapshot().controlled,\n own: () => store.getSnapshot().own,\n onChange: () => onChange,\n write(next) {\n storage.write(JSON.stringify(next));\n store.dispatch({ type: \"own\", entries: next });\n },\n });\n const entries = value.value;\n const change = (action: FrequentsAction) => value.set(reduceFrequents(entries(), action), Object.assign(createChangeDetails(action.type), { emoji: action.type === \"clear\" ? undefined : action.emoji }));\n\n return {\n pick: (emoji) => void change({ type: \"pick\", emoji, now: Date.now() }),\n remove: (emoji) => void change({ type: \"remove\", emoji }),\n clear: () => void change({ type: \"clear\" }),\n top(n) {\n const state = entries();\n return cached(\n cached(tops, state, () => new Map()),\n n,\n // Decay keeps the order at any reference time; the newest use avoids reading the clock during render\n () =>\n ranked(state, Math.max(0, ...Object.values(state).map((e) => e.lastUsed)))\n .slice(0, n)\n .map(([u]) => u),\n );\n },\n score(unified) {\n const entry = entries()[unified];\n return entry ? decayed(entry, Date.now()) : 0;\n },\n entries,\n subscribe: readOnFirstSubscribe(store.subscribe, () => store.dispatch({ type: \"own\", entries: read() })),\n control(next, handler) {\n onChange = handler;\n store.dispatch({ type: \"control\", entries: next });\n },\n };\n}\n\n// A server-rendered page hydrates with the defaults; storage is read once something subscribes after mount\nexport function readOnFirstSubscribe(subscribe: (listener: () => void) => () => void, sync: () => void) {\n let synced = false;\n return (listener: () => void) => {\n const unsubscribe = subscribe(listener);\n if (!synced) {\n synced = true;\n sync();\n }\n return unsubscribe;\n };\n}\n\nconst isEntry = (value: unknown): value is FrequentEntry => typeof (value as FrequentEntry)?.count === \"number\" && typeof (value as FrequentEntry).lastUsed === \"number\";\n\nfunction parse(raw: string | null): Frequents | undefined {\n try {\n const value: unknown = raw ? JSON.parse(raw) : undefined;\n return value && typeof value === \"object\" ? Object.fromEntries(Object.entries(value).filter(([, entry]) => isEntry(entry))) : undefined;\n } catch {\n return undefined;\n }\n}\n\n// Node 25+ defines a localStorage global (a warning, or a file shared by every request); only a browser's is per user\nconst browserStorage = () => (typeof window === \"undefined\" ? undefined : window.localStorage);\n\nexport function persisted(key: string): { read(): string | null; write(value: string): void } {\n let memory: string | null = null;\n return {\n read() {\n try {\n return browserStorage()?.getItem(key) ?? memory;\n } catch {\n return memory;\n }\n },\n write(value) {\n memory = value;\n try {\n browserStorage()?.setItem(key, value);\n } catch {}\n },\n };\n}\n"],"mappings":"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","names":[]}
package/dist/index.d.ts CHANGED
@@ -1,28 +1,32 @@
1
- import { E as EmojiStore, C as CompletionOrder, a as ShortcodeCompletion, b as EmojiSelection, c as ChangeDetails, d as EmojiLabels, e as CategoryOption, f as SearchResultsMode, g as Category, h as EmojiSection, i as EmojiItem } from './sections-BfhdYJn0.js';
1
+ import { E as EmojiStore, C as CompletionOrder, a as ShortcodeCompletion, b as EmojiSelection, c as ChangeDetails, d as EmojiLabels, e as CategoryOption, f as SearchResultsMode, g as Category, h as EmojiSection, i as EmojiItem } from './sections-D7PEWAVf.js';
2
2
  import { Subscribe } from './internal.js';
3
- export { j as CustomEmoji, k as CustomEmojiLoader, l as CustomEmojiSource, m as EmojiDataSource, n as EmojiInput, o as EmojiSearch, p as EmojiStoreOptions, q as EmojiStoreStatus, F as Frequents, r as FrequentsChangeDetails, R as Renderer, s as ResolvedEmoji, t as SkinToneChangeDetails, u as createEmojiStore, v as findShortcodeTrigger, w as isComposingKey, x as lexicalSearch, y as localeData, z as resolveEmoji, A as twemojiUrl } from './sections-BfhdYJn0.js';
3
+ export { j as CustomEmoji, k as CustomEmojiLoader, l as CustomEmojiSource, m as EmojiDataSource, n as EmojiInput, o as EmojiSearch, p as EmojiStoreOptions, q as EmojiStoreStatus, F as Frequents, r as FrequentsChangeDetails, R as Renderer, s as ResolvedEmoji, t as SkinToneChangeDetails, u as createEmojiStore, v as findShortcodeTrigger, w as isComposingKey, x as lexicalSearch, y as localeData, z as resolveEmoji, A as twemojiUrl } from './sections-D7PEWAVf.js';
4
4
  import { E as EmojiRecord, S as SkinToneValue } from './dataset-BQY5KQKL.js';
5
5
  export { D as Dataset, a as EmojiDataInput, n as nativeToUnified, u as unifiedToNative, w as withTone, b as withoutTone } from './dataset-BQY5KQKL.js';
6
6
 
7
7
  interface EmojiSearchState {
8
8
  results: readonly EmojiRecord[];
9
9
  pending: boolean;
10
+ error: Error | null;
10
11
  }
11
12
  interface EmojiSearchQuery {
12
13
  query: string;
13
14
  limit: number;
14
15
  }
15
- interface EmojiSearchRunner {
16
+ interface EmojiSearchStore {
16
17
  getSnapshot(): EmojiSearchState;
17
18
  subscribe: Subscribe;
18
19
  setQuery(query: string, limit: number): void;
19
20
  abort(): void;
20
21
  }
21
- declare function createSearch(store: EmojiStore, initial?: EmojiSearchQuery): EmojiSearchRunner;
22
+ declare function createEmojiSearch(store: EmojiStore, initial?: EmojiSearchQuery): EmojiSearchStore;
22
23
 
23
24
  interface KeyLike {
24
25
  key: string;
25
26
  shiftKey?: boolean;
27
+ metaKey?: boolean;
28
+ ctrlKey?: boolean;
29
+ altKey?: boolean;
26
30
  isComposing?: boolean;
27
31
  keyCode?: number;
28
32
  preventDefault(): void;
@@ -93,6 +97,8 @@ type HighlightChangeDetails = ChangeDetails<"keyboard" | "pointer" | "imperative
93
97
  };
94
98
  type QueryChangeDetails = ChangeDetails<"input-change" | "clear-press" | "escape-key" | "imperative-action" | "none">;
95
99
  type CollapsedCategoriesChangeDetails = ChangeDetails<"trigger-press" | "none">;
100
+ type ValueChangeDetails = ChangeDetails<"item-press" | "imperative-action">;
101
+ type ReactionsOpenChangeDetails = ChangeDetails<"trigger-press" | "item-press" | "escape-key" | "none">;
96
102
  interface ChangeSource<D extends ChangeDetails> {
97
103
  reason?: D["reason"];
98
104
  event?: Event;
@@ -100,9 +106,9 @@ interface ChangeSource<D extends ChangeDetails> {
100
106
  interface EmojiPickerOptions<Icon = unknown> {
101
107
  labels?: EmojiLabels;
102
108
  categories?: readonly CategoryOption<Icon>[];
103
- frequent?: number | false;
104
- suggested?: readonly string[];
109
+ frequent?: number | false | readonly string[];
105
110
  emojiVersion?: number;
111
+ drawable?: (record: EmojiRecord, unified: string) => boolean;
106
112
  searchResults?: SearchResultsMode;
107
113
  searchLimit?: number;
108
114
  query?: string;
@@ -114,8 +120,15 @@ interface EmojiPickerOptions<Icon = unknown> {
114
120
  highlight?: string | null;
115
121
  defaultHighlight?: string | null;
116
122
  onHighlightChange?: (unified: string | null, details: HighlightChangeDetails) => void;
123
+ value?: string | null;
124
+ defaultValue?: string | null;
125
+ onValueChange?: (unified: string | null, details: ValueChangeDetails) => void;
126
+ onSelect?: (selection: EmojiSelection, details: ValueChangeDetails) => void;
117
127
  disableVariations?: boolean;
118
128
  reactions?: readonly string[] | number;
129
+ reactionsOpen?: boolean;
130
+ defaultReactionsOpen?: boolean;
131
+ onReactionsOpenChange?: (open: boolean, details: ReactionsOpenChangeDetails) => void;
119
132
  }
120
133
  type FocusMode = "virtual" | "roving";
121
134
  interface EmojiPickerSnapshot<Icon = unknown> {
@@ -123,6 +136,7 @@ interface EmojiPickerSnapshot<Icon = unknown> {
123
136
  searching: boolean;
124
137
  results: readonly EmojiRecord[];
125
138
  pending: boolean;
139
+ searchError: Error | null;
126
140
  categories: readonly Category<Icon>[];
127
141
  categorySections: readonly EmojiSection[];
128
142
  sections: readonly EmojiSection[];
@@ -135,6 +149,8 @@ interface EmojiPickerSnapshot<Icon = unknown> {
135
149
  activeSection: string | null;
136
150
  variations: string | null;
137
151
  reactions: readonly string[] | undefined;
152
+ reactionsOpen: boolean;
153
+ value: string | null | undefined;
138
154
  tabStop: string | null;
139
155
  pressed: string | null;
140
156
  }
@@ -154,6 +170,8 @@ interface EmojiPickerStore<Icon = unknown> {
154
170
  setTabStop(id: string | null): void;
155
171
  press(id: string | null): void;
156
172
  choose(id?: string | null): EmojiSelection | undefined;
173
+ pick(id?: string | null, source?: ChangeSource<ValueChangeDetails>): EmojiSelection | undefined;
174
+ setReactionsOpen(open: boolean, source?: ChangeSource<ReactionsOpenChangeDetails>): ReactionsOpenChangeDetails | undefined;
157
175
  openVariations(id: string | null): boolean;
158
176
  closeVariations(): void;
159
177
  registerInput(): () => void;
@@ -163,5 +181,5 @@ interface EmojiPickerStore<Icon = unknown> {
163
181
  }
164
182
  declare function createEmojiPicker<Icon = unknown>(store: EmojiStore, options?: EmojiPickerOptions<Icon>): EmojiPickerStore<Icon>;
165
183
 
166
- export { Category, CategoryOption, ChangeDetails, CompletionOrder, EmojiItem, EmojiLabels, EmojiRecord, EmojiSection, EmojiSelection, EmojiStore, SearchResultsMode, ShortcodeCompletion, SkinToneValue, createEmojiPicker, createEmojiSuggestions, createSearch };
167
- export type { CollapsedCategoriesChangeDetails, EmojiPickerOptions, EmojiPickerSnapshot, EmojiPickerStore, EmojiSearchState, EmojiSuggestionsOptions, EmojiSuggestionsStore, EmojiTrigger, HighlightChangeDetails, KeyLike, QueryChangeDetails };
184
+ export { Category, CategoryOption, ChangeDetails, CompletionOrder, EmojiItem, EmojiLabels, EmojiRecord, EmojiSection, EmojiSelection, EmojiStore, SearchResultsMode, ShortcodeCompletion, SkinToneValue, createEmojiPicker, createEmojiSearch, createEmojiSuggestions };
185
+ export type { CollapsedCategoriesChangeDetails, EmojiPickerOptions, EmojiPickerSnapshot, EmojiPickerStore, EmojiSearchState, EmojiSearchStore, EmojiSuggestionsOptions, EmojiSuggestionsStore, EmojiTrigger, HighlightChangeDetails, KeyLike, QueryChangeDetails, ReactionsOpenChangeDetails, ValueChangeDetails };
package/dist/index.js CHANGED
@@ -3,16 +3,16 @@ import { createEmojiStore } from "./store.js";
3
3
  import { localeData, twemojiUrl } from "./platform.js";
4
4
  import { nativeToUnified, unifiedToNative, withTone, withoutTone } from "./dataset.js";
5
5
  import { lexicalSearch } from "./search/search.js";
6
- import { createSearch } from "./search/query.js";
6
+ import { createEmojiSearch } from "./search/query.js";
7
7
  import { resolveEmoji } from "./resolve.js";
8
8
  import { findShortcodeTrigger, isComposingKey } from "./shortcodes.js";
9
9
  import { createEmojiSuggestions } from "./suggestions.js";
10
10
  import { createEmojiPicker } from "./picker/picker.js";
11
11
  export {
12
12
  createEmojiPicker,
13
+ createEmojiSearch,
13
14
  createEmojiStore,
14
15
  createEmojiSuggestions,
15
- createSearch,
16
16
  findShortcodeTrigger,
17
17
  isComposingKey,
18
18
  lexicalSearch,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { createEmojiStore } from \"./store\";\nexport type { CustomEmoji, CustomEmojiLoader, CustomEmojiSource, EmojiDataSource, EmojiSelection, EmojiStore, EmojiStoreOptions, EmojiStoreStatus, SkinToneChangeDetails } from \"./store\";\nexport type { ChangeDetails } from \"./change\";\nexport type { Frequents, FrequentsChangeDetails } from \"./frequents\";\nexport { localeData, twemojiUrl } from \"./platform\";\nexport type { Renderer } from \"./platform\";\nexport { nativeToUnified, unifiedToNative, withTone, withoutTone } from \"./dataset\";\nexport type { Dataset, EmojiDataInput, EmojiRecord, SkinToneValue } from \"./dataset\";\nexport { lexicalSearch } from \"./search/search\";\nexport type { EmojiSearch } from \"./search/search\";\nexport type { CompletionOrder, ShortcodeCompletion } from \"./search/search-index\";\nexport { createSearch } from \"./search/query\";\nexport type { EmojiSearchState } from \"./search/query\";\nexport type { EmojiLabels } from \"./labels\";\nexport { resolveEmoji } from \"./resolve\";\nexport type { EmojiInput, ResolvedEmoji } from \"./resolve\";\nexport { findShortcodeTrigger, isComposingKey } from \"./shortcodes\";\nexport { createEmojiSuggestions } from \"./suggestions\";\nexport type { EmojiSuggestionsStore, EmojiSuggestionsOptions, EmojiTrigger, KeyLike } from \"./suggestions\";\nexport { createEmojiPicker } from \"./picker/picker\";\nexport type { CollapsedCategoriesChangeDetails, EmojiPickerStore, EmojiPickerOptions, EmojiPickerSnapshot, HighlightChangeDetails, QueryChangeDetails } from \"./picker/picker\";\nexport type { Category, CategoryOption, EmojiItem, EmojiSection, SearchResultsMode } from \"./picker/sections\";\n"],"mappings":";AAAA,SAAS,wBAAwB;AAIjC,SAAS,YAAY,kBAAkB;AAEvC,SAAS,iBAAiB,iBAAiB,UAAU,mBAAmB;AAExE,SAAS,qBAAqB;AAG9B,SAAS,oBAAoB;AAG7B,SAAS,oBAAoB;AAE7B,SAAS,sBAAsB,sBAAsB;AACrD,SAAS,8BAA8B;AAEvC,SAAS,yBAAyB;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { createEmojiStore } from \"./store\";\nexport type { CustomEmoji, CustomEmojiLoader, CustomEmojiSource, EmojiDataSource, EmojiSelection, EmojiStore, EmojiStoreOptions, EmojiStoreStatus, SkinToneChangeDetails } from \"./store\";\nexport type { ChangeDetails } from \"./change\";\nexport type { Frequents, FrequentsChangeDetails } from \"./frequents\";\nexport { localeData, twemojiUrl } from \"./platform\";\nexport type { Renderer } from \"./platform\";\nexport { nativeToUnified, unifiedToNative, withTone, withoutTone } from \"./dataset\";\nexport type { Dataset, EmojiDataInput, EmojiRecord, SkinToneValue } from \"./dataset\";\nexport { lexicalSearch } from \"./search/search\";\nexport type { EmojiSearch } from \"./search/search\";\nexport type { CompletionOrder, ShortcodeCompletion } from \"./search/search-index\";\nexport { createEmojiSearch } from \"./search/query\";\nexport type { EmojiSearchState, EmojiSearchStore } from \"./search/query\";\nexport type { EmojiLabels } from \"./labels\";\nexport { resolveEmoji } from \"./resolve\";\nexport type { EmojiInput, ResolvedEmoji } from \"./resolve\";\nexport { findShortcodeTrigger, isComposingKey } from \"./shortcodes\";\nexport { createEmojiSuggestions } from \"./suggestions\";\nexport type { EmojiSuggestionsStore, EmojiSuggestionsOptions, EmojiTrigger, KeyLike } from \"./suggestions\";\nexport { createEmojiPicker } from \"./picker/picker\";\nexport type { CollapsedCategoriesChangeDetails, EmojiPickerStore, EmojiPickerOptions, EmojiPickerSnapshot, HighlightChangeDetails, QueryChangeDetails, ReactionsOpenChangeDetails, ValueChangeDetails } from \"./picker/picker\";\nexport type { Category, CategoryOption, EmojiItem, EmojiSection, SearchResultsMode } from \"./picker/sections\";\n"],"mappings":";AAAA,SAAS,wBAAwB;AAIjC,SAAS,YAAY,kBAAkB;AAEvC,SAAS,iBAAiB,iBAAiB,UAAU,mBAAmB;AAExE,SAAS,qBAAqB;AAG9B,SAAS,yBAAyB;AAGlC,SAAS,oBAAoB;AAE7B,SAAS,sBAAsB,sBAAsB;AACrD,SAAS,8BAA8B;AAEvC,SAAS,yBAAyB;","names":[]}
@@ -1,6 +1,6 @@
1
- import { S as Subscribe } from './sections-BfhdYJn0.js';
1
+ import { S as Subscribe } from './sections-D7PEWAVf.js';
2
2
  import { EmojiSection } from './index.js';
3
- export { T as TWEMOJI_EMOJI_VERSION, B as createChangeDetails, D as customRecord, G as isCustomEmoji, H as localizeLabels, I as missingEmojiText, J as nameWithTone, K as shallowEqual, L as subscribeAll } from './sections-BfhdYJn0.js';
3
+ export { T as TWEMOJI_EMOJI_VERSION, B as createChangeDetails, D as customRecord, G as isCustomEmoji, H as localizeLabels, I as missingEmojiText, J as nameWithTone, K as shallowEqual, L as subscribeAll } from './sections-D7PEWAVf.js';
4
4
  export { c as SKIN_TONE_VALUES, i as isUnified } from './dataset-BQY5KQKL.js';
5
5
  import { EmojiGrid, EmojiGridMove } from './grid.js';
6
6
  export { P as PROBE_BATCH, o as onNativeSupportChange } from './native-B0Oa7IDL.js';
package/dist/labels.js CHANGED
@@ -21,11 +21,12 @@ var defaultLabels = {
21
21
  emojiList: "Emoji",
22
22
  resultCount: plural("en", COUNT),
23
23
  categoryNavigation: "Category navigation",
24
- suggested: "Frequently used",
24
+ frequent: "Frequently used",
25
25
  custom: "Custom",
26
+ suggestions: "Emoji suggestions",
26
27
  reactions: "Reactions",
27
28
  expandReactions: "More emoji",
28
- previewCaption: "What's your mood?",
29
+ previewCaption: "Pick an emoji",
29
30
  skinTone: "Skin tone",
30
31
  skinTones: ["Default skin tone", "Light skin tone", "Medium-light skin tone", "Medium skin tone", "Medium-dark skin tone", "Dark skin tone"],
31
32
  withSkinTone: (name, tone) => `${name}: ${TONE_SUFFIXES[tone - 1]}`
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/labels.ts"],"sourcesContent":["type PluralForms = { other: string } & Partial<Record<Intl.LDMLPluralRule | `=${number}`, string>>;\n\n// The default resultCount; apps plug their own i18n library into labels.resultCount\nexport function plural(locale: string, forms: PluralForms): (count: number) => string {\n const rules = new Intl.PluralRules(locale);\n const number = new Intl.NumberFormat(locale);\n return (count) => {\n const form = forms[`=${count}`] ?? forms[rules.select(count)] ?? forms.other;\n return form.replaceAll(\"#\", number.format(count));\n };\n}\n\nexport type SkinToneNames = readonly [string, string, string, string, string, string];\n\nexport type EmojiLabels = {\n picker: string;\n searchPlaceholder: string;\n searchLabel: string;\n searchClear: string;\n searchResults: string;\n emojiList: string;\n resultCount: (count: number) => string;\n categoryNavigation: string;\n suggested: string;\n custom: string;\n reactions: string;\n expandReactions: string;\n previewCaption: string;\n skinTone: string;\n skinTones: SkinToneNames;\n withSkinTone: (name: string, tone: number) => string;\n};\n\nconst COUNT: PluralForms = { \"=0\": \"No results\", one: \"# result\", other: \"# results\" };\n\n// English, with the app's overrides: a locale alone doesn't translate the strings, so it doesn't change their plural rules either\nexport function localizeLabels(overrides?: Partial<EmojiLabels>): EmojiLabels {\n return overrides ? { ...defaultLabels, ...overrides } : defaultLabels;\n}\n\nconst TONE_SUFFIXES = [\"light skin tone\", \"medium-light skin tone\", \"medium skin tone\", \"medium-dark skin tone\", \"dark skin tone\"];\n\nexport const defaultLabels: EmojiLabels = {\n picker: \"Emoji picker\",\n searchPlaceholder: \"Search\",\n searchLabel: \"Type to search for an emoji\",\n searchClear: \"Clear\",\n searchResults: \"Search results\",\n emojiList: \"Emoji\",\n resultCount: plural(\"en\", COUNT),\n categoryNavigation: \"Category navigation\",\n suggested: \"Frequently used\",\n custom: \"Custom\",\n reactions: \"Reactions\",\n expandReactions: \"More emoji\",\n previewCaption: \"What's your mood?\",\n skinTone: \"Skin tone\",\n skinTones: [\"Default skin tone\", \"Light skin tone\", \"Medium-light skin tone\", \"Medium skin tone\", \"Medium-dark skin tone\", \"Dark skin tone\"],\n withSkinTone: (name, tone) => `${name}: ${TONE_SUFFIXES[tone - 1]}`,\n};\n"],"mappings":";AAGO,SAAS,OAAO,QAAgB,OAA+C;AACpF,QAAM,QAAQ,IAAI,KAAK,YAAY,MAAM;AACzC,QAAM,SAAS,IAAI,KAAK,aAAa,MAAM;AAC3C,SAAO,CAAC,UAAU;AAChB,UAAM,OAAO,MAAM,IAAI,KAAK,EAAE,KAAK,MAAM,MAAM,OAAO,KAAK,CAAC,KAAK,MAAM;AACvE,WAAO,KAAK,WAAW,KAAK,OAAO,OAAO,KAAK,CAAC;AAAA,EAClD;AACF;AAuBA,IAAM,QAAqB,EAAE,MAAM,cAAc,KAAK,YAAY,OAAO,YAAY;AAG9E,SAAS,eAAe,WAA+C;AAC5E,SAAO,YAAY,EAAE,GAAG,eAAe,GAAG,UAAU,IAAI;AAC1D;AAEA,IAAM,gBAAgB,CAAC,mBAAmB,0BAA0B,oBAAoB,yBAAyB,gBAAgB;AAE1H,IAAM,gBAA6B;AAAA,EACxC,QAAQ;AAAA,EACR,mBAAmB;AAAA,EACnB,aAAa;AAAA,EACb,aAAa;AAAA,EACb,eAAe;AAAA,EACf,WAAW;AAAA,EACX,aAAa,OAAO,MAAM,KAAK;AAAA,EAC/B,oBAAoB;AAAA,EACpB,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,iBAAiB;AAAA,EACjB,gBAAgB;AAAA,EAChB,UAAU;AAAA,EACV,WAAW,CAAC,qBAAqB,mBAAmB,0BAA0B,oBAAoB,yBAAyB,gBAAgB;AAAA,EAC3I,cAAc,CAAC,MAAM,SAAS,GAAG,IAAI,KAAK,cAAc,OAAO,CAAC,CAAC;AACnE;","names":[]}
1
+ {"version":3,"sources":["../src/labels.ts"],"sourcesContent":["type PluralForms = { other: string } & Partial<Record<Intl.LDMLPluralRule | `=${number}`, string>>;\n\n// The default resultCount; apps plug their own i18n library into labels.resultCount\nexport function plural(locale: string, forms: PluralForms): (count: number) => string {\n const rules = new Intl.PluralRules(locale);\n const number = new Intl.NumberFormat(locale);\n return (count) => {\n const form = forms[`=${count}`] ?? forms[rules.select(count)] ?? forms.other;\n return form.replaceAll(\"#\", number.format(count));\n };\n}\n\nexport type SkinToneNames = readonly [string, string, string, string, string, string];\n\nexport type EmojiLabels = {\n picker: string;\n searchPlaceholder: string;\n searchLabel: string;\n searchClear: string;\n searchResults: string;\n emojiList: string;\n resultCount: (count: number) => string;\n categoryNavigation: string;\n frequent: string;\n custom: string;\n suggestions: string;\n reactions: string;\n expandReactions: string;\n previewCaption: string;\n skinTone: string;\n skinTones: SkinToneNames;\n withSkinTone: (name: string, tone: number) => string;\n};\n\nconst COUNT: PluralForms = { \"=0\": \"No results\", one: \"# result\", other: \"# results\" };\n\n// English, with the app's overrides: a locale alone doesn't translate the strings, so it doesn't change their plural rules either\nexport function localizeLabels(overrides?: Partial<EmojiLabels>): EmojiLabels {\n return overrides ? { ...defaultLabels, ...overrides } : defaultLabels;\n}\n\nconst TONE_SUFFIXES = [\"light skin tone\", \"medium-light skin tone\", \"medium skin tone\", \"medium-dark skin tone\", \"dark skin tone\"];\n\nexport const defaultLabels: EmojiLabels = {\n picker: \"Emoji picker\",\n searchPlaceholder: \"Search\",\n searchLabel: \"Type to search for an emoji\",\n searchClear: \"Clear\",\n searchResults: \"Search results\",\n emojiList: \"Emoji\",\n resultCount: plural(\"en\", COUNT),\n categoryNavigation: \"Category navigation\",\n frequent: \"Frequently used\",\n custom: \"Custom\",\n suggestions: \"Emoji suggestions\",\n reactions: \"Reactions\",\n expandReactions: \"More emoji\",\n previewCaption: \"Pick an emoji\",\n skinTone: \"Skin tone\",\n skinTones: [\"Default skin tone\", \"Light skin tone\", \"Medium-light skin tone\", \"Medium skin tone\", \"Medium-dark skin tone\", \"Dark skin tone\"],\n withSkinTone: (name, tone) => `${name}: ${TONE_SUFFIXES[tone - 1]}`,\n};\n"],"mappings":";AAGO,SAAS,OAAO,QAAgB,OAA+C;AACpF,QAAM,QAAQ,IAAI,KAAK,YAAY,MAAM;AACzC,QAAM,SAAS,IAAI,KAAK,aAAa,MAAM;AAC3C,SAAO,CAAC,UAAU;AAChB,UAAM,OAAO,MAAM,IAAI,KAAK,EAAE,KAAK,MAAM,MAAM,OAAO,KAAK,CAAC,KAAK,MAAM;AACvE,WAAO,KAAK,WAAW,KAAK,OAAO,OAAO,KAAK,CAAC;AAAA,EAClD;AACF;AAwBA,IAAM,QAAqB,EAAE,MAAM,cAAc,KAAK,YAAY,OAAO,YAAY;AAG9E,SAAS,eAAe,WAA+C;AAC5E,SAAO,YAAY,EAAE,GAAG,eAAe,GAAG,UAAU,IAAI;AAC1D;AAEA,IAAM,gBAAgB,CAAC,mBAAmB,0BAA0B,oBAAoB,yBAAyB,gBAAgB;AAE1H,IAAM,gBAA6B;AAAA,EACxC,QAAQ;AAAA,EACR,mBAAmB;AAAA,EACnB,aAAa;AAAA,EACb,aAAa;AAAA,EACb,eAAe;AAAA,EACf,WAAW;AAAA,EACX,aAAa,OAAO,MAAM,KAAK;AAAA,EAC/B,oBAAoB;AAAA,EACpB,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,WAAW;AAAA,EACX,iBAAiB;AAAA,EACjB,gBAAgB;AAAA,EAChB,UAAU;AAAA,EACV,WAAW,CAAC,qBAAqB,mBAAmB,0BAA0B,oBAAoB,yBAAyB,gBAAgB;AAAA,EAC3I,cAAc,CAAC,MAAM,SAAS,GAAG,IAAI,KAAK,cAAc,OAAO,CAAC,CAAC;AACnE;","names":[]}
@@ -2,7 +2,7 @@
2
2
  import { controllable, createChangeDetails } from "../change.js";
3
3
  import { withoutTone } from "../dataset.js";
4
4
  import { createStore, memo, patch, shallowEqual, stable, subscribeAll } from "../reactive.js";
5
- import { createSearch } from "../search/query.js";
5
+ import { createEmojiSearch } from "../search/query.js";
6
6
  import {
7
7
  categoryList,
8
8
  categorySections,
@@ -11,7 +11,8 @@ import {
11
11
  itemFactory,
12
12
  resultSections,
13
13
  setCategoryCollapsed,
14
- shownIn
14
+ shownIn,
15
+ toned
15
16
  } from "./sections.js";
16
17
  var reduceInput = (input, action) => {
17
18
  switch (action.type) {
@@ -57,7 +58,9 @@ function createEmojiPicker(store, options = {}) {
57
58
  domFocus: false,
58
59
  held: null,
59
60
  tabStop: null,
60
- pressed: null
61
+ pressed: null,
62
+ value: options.defaultValue ?? null,
63
+ reactionsOpen: options.defaultReactionsOpen ?? true
61
64
  },
62
65
  patch
63
66
  );
@@ -65,12 +68,13 @@ function createEmojiPicker(store, options = {}) {
65
68
  const opts = () => input.getSnapshot().options;
66
69
  const searchOf = (state, current) => ({ query: queryOf(state, current), limit: current.searchLimit ?? DEFAULT_SEARCH_LIMIT });
67
70
  const initial = searchOf(ui.getSnapshot(), options);
68
- const search = createSearch(store, initial);
71
+ const search = createEmojiSearch(store, initial);
69
72
  const syncSearch = () => {
70
73
  const { query: query2, limit } = searchOf(ui.getSnapshot(), opts());
71
74
  search.setQuery(query2, limit);
72
75
  };
73
76
  let kept = { query: "" };
77
+ const tracks = (current) => options.defaultValue !== void 0 || current.value !== void 0 || current.onValueChange !== void 0;
74
78
  const items = memo(itemFactory);
75
79
  const categoriesOf = memo(
76
80
  (groupNames, customGroups, categories, labels) => categoryList(
@@ -81,16 +85,22 @@ function createEmojiPicker(store, options = {}) {
81
85
  )
82
86
  );
83
87
  const sectionsOf = memo(
84
- (records, customGroups, ready, categories, suggested, collapsed2, emojiVersion, tone, item) => ready ? categorySections({ categories, records, suggested, customGroup: (u) => customGroups.get(u), collapsed: collapsed2, emojiVersion, get: store.get, tone, item }) : NO_SECTIONS
88
+ (records, customGroups, ready, categories, frequent, collapsed2, emojiVersion, tone, item) => ready ? categorySections({ categories, records, frequent, customGroup: (u) => customGroups.get(u), collapsed: collapsed2, emojiVersion, get: store.get, tone, item }) : NO_SECTIONS
85
89
  );
86
- const resultsOf = memo((results, emojiVersion) => emojiVersion == null ? results : results.filter(shownIn(emojiVersion)));
90
+ const resultsOf = memo((results, emojiVersion, drawable, tone) => {
91
+ const shown = emojiVersion == null ? results : results.filter(shownIn(emojiVersion));
92
+ return drawable ? shown.filter((r) => drawable(r, toned(r, tone))) : shown;
93
+ });
87
94
  const resultSectionsOf = memo((results, mode, categories, labels, groupNames, tone, item) => {
88
95
  const name = (group) => categories.find((c) => c.id === group)?.name ?? (group === "custom" ? labels.custom : groupName(groupNames, group));
89
96
  return resultSections(results, mode, name, tone, item, labels.searchResults);
90
97
  });
91
98
  const activeOf = memo(findItem);
92
99
  const keep = stable();
93
- const frequentCount = () => (opts().frequent ?? DEFAULT_FREQUENT) || 1;
100
+ const frequentCount = () => {
101
+ const { frequent = DEFAULT_FREQUENT } = opts();
102
+ return typeof frequent === "number" ? frequent || 1 : 1;
103
+ };
94
104
  const reactionsOf = (reactions) => typeof reactions === "number" ? store.frequent(reactions) : reactions;
95
105
  const select = memo(
96
106
  (state, current, ready, records, tone, frequent, found, storeLabels) => {
@@ -100,19 +110,21 @@ function createEmojiPicker(store, options = {}) {
100
110
  const customGroups = store.customGroups();
101
111
  const query2 = queryOf(state, current);
102
112
  const searching = query2.trim().length > 0;
103
- const results = resultsOf(found.results, current.emojiVersion);
113
+ const results = resultsOf(found.results, current.emojiVersion, current.drawable, tone);
104
114
  const categories = categoriesOf(groupNames, customGroups, current.categories, labels);
105
- const suggested = current.suggested ?? (current.frequent === false ? NO_IDS : frequent);
115
+ const shownFrequent = Array.isArray(current.frequent) ? current.frequent : current.frequent === false ? NO_IDS : frequent;
106
116
  const collapsed2 = collapsedOf(state, current);
107
- const categorySections2 = sectionsOf(records, customGroups, ready, categories, suggested, collapsed2, current.emojiVersion, tone, item);
117
+ const categorySections2 = sectionsOf(records, customGroups, ready, categories, shownFrequent, collapsed2, current.emojiVersion, tone, item);
108
118
  const sections = searching ? resultSectionsOf(results, current.searchResults ?? "ranked", categories, labels, groupNames, tone, item) : categorySections2;
109
119
  const highlighted2 = resolveKey(sections, current.highlight === void 0 ? state.highlighted : current.highlight, state.highlighted);
110
120
  const virtual = (state.inputs > 0 || !state.mounted) && !state.domFocus;
121
+ const reactions = state.held && typeof current.reactions === "number" ? state.held.reactions : reactionsOf(current.reactions);
111
122
  return keep({
112
123
  query: query2,
113
124
  searching,
114
125
  results,
115
126
  pending: found.pending,
127
+ searchError: found.error,
116
128
  categories,
117
129
  categorySections: categorySections2,
118
130
  sections,
@@ -124,7 +136,10 @@ function createEmojiPicker(store, options = {}) {
124
136
  active: activeOf(sections, highlighted2 ?? state.hovered),
125
137
  activeSection: state.activeSection,
126
138
  variations: state.variations,
127
- reactions: state.held ? state.held.reactions : reactionsOf(current.reactions),
139
+ reactions,
140
+ // An empty row, such as frequently used before any pick, shows the picker instead
141
+ reactionsOpen: Boolean(reactions?.length) && (current.reactionsOpen ?? state.reactionsOpen),
142
+ value: tracks(current) ? current.value === void 0 ? state.value : current.value : void 0,
128
143
  tabStop: state.tabStop,
129
144
  pressed: state.pressed
130
145
  });
@@ -132,14 +147,14 @@ function createEmojiPicker(store, options = {}) {
132
147
  );
133
148
  function getSnapshot() {
134
149
  const state = ui.getSnapshot();
135
- return select(state, opts(), store.status() === "ready", store.records(), store.skinTone(), state.held?.suggested ?? store.frequent(frequentCount()), search.getSnapshot(), store.labels());
150
+ return select(state, opts(), store.status() === "ready", store.records(), store.skinTone(), state.held?.frequent ?? store.frequent(frequentCount()), search.getSnapshot(), store.labels());
136
151
  }
137
152
  const set = (fields) => ui.dispatch(fields);
138
153
  const query = controllable({
139
154
  controlled: () => opts().query,
140
155
  own: () => ui.getSnapshot().query,
141
156
  onChange: () => opts().onQueryChange,
142
- write: (value) => set({ query: value })
157
+ write: (value2) => set({ query: value2 })
143
158
  });
144
159
  const collapsed = controllable({
145
160
  controlled: () => input.getSnapshot().draft ?? opts().collapsedCategories,
@@ -158,21 +173,33 @@ function createEmojiPicker(store, options = {}) {
158
173
  },
159
174
  write: (key) => set({ highlighted: key })
160
175
  });
161
- const changeCollapsed = (id, value, { reason = "none", event }) => void collapsed.set(setCategoryCollapsed(collapsed.value(), id, value), createChangeDetails(reason, event));
162
- return {
176
+ const value = controllable({
177
+ controlled: () => opts().value,
178
+ own: () => ui.getSnapshot().value,
179
+ onChange: () => opts().onValueChange,
180
+ write: (unified) => set({ value: unified })
181
+ });
182
+ const reactionsOpen = controllable({
183
+ controlled: () => opts().reactionsOpen,
184
+ own: () => ui.getSnapshot().reactionsOpen,
185
+ onChange: () => opts().onReactionsOpenChange,
186
+ write: (open) => set({ reactionsOpen: open })
187
+ });
188
+ const changeCollapsed = (id, value2, { reason = "none", event }) => void collapsed.set(setCategoryCollapsed(collapsed.value(), id, value2), createChangeDetails(reason, event));
189
+ const self = {
163
190
  getSnapshot,
164
191
  subscribe: subscribeAll(ui.subscribe, input.subscribe, store.subscribe, (listener) => store.frequents.subscribe(() => !ui.getSnapshot().held && listener()), search.subscribe),
165
192
  setOptions(next) {
166
193
  input.dispatch({ type: "options", options: next });
167
194
  syncSearch();
168
195
  },
169
- setQuery(value, { reason = "none", event } = {}) {
170
- const details = query.set(value, createChangeDetails(reason, event));
196
+ setQuery(value2, { reason = "none", event } = {}) {
197
+ const details = query.set(value2, createChangeDetails(reason, event));
171
198
  syncSearch();
172
199
  return details;
173
200
  },
174
201
  toggleCollapsed: (id, source = {}) => changeCollapsed(id, void 0, source),
175
- setCollapsed: (id, value, source = {}) => changeCollapsed(id, value, source),
202
+ setCollapsed: (id, value2, source = {}) => changeCollapsed(id, value2, source),
176
203
  highlight(emoji, { reason = "none", event } = {}) {
177
204
  const { sections, highlighted: current, searching } = getSnapshot();
178
205
  const key = resolveKey(sections, emoji, current);
@@ -195,6 +222,21 @@ function createEmojiPicker(store, options = {}) {
195
222
  setTabStop: (id) => set({ tabStop: id }),
196
223
  press: (id) => set({ pressed: id }),
197
224
  choose: (id = getSnapshot().highlighted) => id == null ? void 0 : store.select(findItem(getSnapshot().sections, id)?.unified ?? id),
225
+ pick(id, { reason = "item-press", event } = {}) {
226
+ const selection = self.choose(id);
227
+ if (!selection) return void 0;
228
+ const details = createChangeDetails(reason, event);
229
+ opts().onSelect?.(selection, details);
230
+ if (details.isCanceled) return selection;
231
+ if (tracks(opts())) value.set(selection.unified, createChangeDetails(reason, event));
232
+ if (opts().reactions && !reactionsOpen.value()) self.setReactionsOpen(true, { reason: "item-press", event });
233
+ return selection;
234
+ },
235
+ setReactionsOpen(open, { reason = "none", event } = {}) {
236
+ const details = reactionsOpen.set(open, createChangeDetails(reason, event));
237
+ if (open && details && !details.isCanceled) self.setQuery("");
238
+ return details;
239
+ },
198
240
  openVariations(id) {
199
241
  const record = findItem(getSnapshot().sections, id)?.record;
200
242
  if (!record?.tones.length || record.imageUrl || opts().disableVariations) return false;
@@ -207,12 +249,13 @@ function createEmojiPicker(store, options = {}) {
207
249
  return () => set({ inputs: ui.getSnapshot().inputs - 1 });
208
250
  },
209
251
  mount: () => set({ mounted: true }),
210
- setDomFocus: (value) => set({ domFocus: value }),
211
- hold(value) {
212
- if (Boolean(ui.getSnapshot().held) === value) return;
213
- set({ held: value ? { suggested: store.frequent(frequentCount()), reactions: reactionsOf(opts().reactions) } : null });
252
+ setDomFocus: (value2) => set({ domFocus: value2 }),
253
+ hold(value2) {
254
+ if (Boolean(ui.getSnapshot().held) === value2) return;
255
+ set({ held: value2 ? { frequent: store.frequent(frequentCount()), reactions: reactionsOf(opts().reactions) } : null });
214
256
  }
215
257
  };
258
+ return self;
216
259
  }
217
260
  export {
218
261
  createEmojiPicker