@emoji-components/core 1.0.0-alpha.1 → 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/README.md CHANGED
@@ -13,19 +13,20 @@ 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
+ const typeahead = createEmojiSuggestions(store, { onChoose: (s) => console.log(typeahead.textFor(s)) });
24
25
  input.addEventListener("keydown", (e) => typeahead.keydown(e));
25
26
  const stop = typeahead.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
 
@@ -34,7 +35,7 @@ const stop = typeahead.syncCombobox(input);
34
35
  | `@emoji-components/core` | `createEmojiStore`, `createEmojiPicker`, `createEmojiSuggestions`, `createSearch`, `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/index.d.ts CHANGED
@@ -1,12 +1,13 @@
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-Bwe4sMgE.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-Bwe4sMgE.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;
@@ -23,6 +24,9 @@ declare function createSearch(store: EmojiStore, initial?: EmojiSearchQuery): Em
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;
@@ -103,6 +107,7 @@ interface EmojiPickerOptions<Icon = unknown> {
103
107
  frequent?: number | false;
104
108
  suggested?: readonly string[];
105
109
  emojiVersion?: number;
110
+ drawable?: (record: EmojiRecord, unified: string) => boolean;
106
111
  searchResults?: SearchResultsMode;
107
112
  searchLimit?: number;
108
113
  query?: string;
@@ -123,6 +128,7 @@ interface EmojiPickerSnapshot<Icon = unknown> {
123
128
  searching: boolean;
124
129
  results: readonly EmojiRecord[];
125
130
  pending: boolean;
131
+ searchError: Error | null;
126
132
  categories: readonly Category<Icon>[];
127
133
  categorySections: readonly EmojiSection[];
128
134
  sections: readonly EmojiSection[];
@@ -1,6 +1,6 @@
1
- import { S as Subscribe } from './sections-BfhdYJn0.js';
1
+ import { S as Subscribe } from './sections-Bwe4sMgE.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-Bwe4sMgE.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';
@@ -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) {
@@ -83,7 +84,10 @@ function createEmojiPicker(store, options = {}) {
83
84
  const sectionsOf = memo(
84
85
  (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
85
86
  );
86
- const resultsOf = memo((results, emojiVersion) => emojiVersion == null ? results : results.filter(shownIn(emojiVersion)));
87
+ const resultsOf = memo((results, emojiVersion, drawable, tone) => {
88
+ const shown = emojiVersion == null ? results : results.filter(shownIn(emojiVersion));
89
+ return drawable ? shown.filter((r) => drawable(r, toned(r, tone))) : shown;
90
+ });
87
91
  const resultSectionsOf = memo((results, mode, categories, labels, groupNames, tone, item) => {
88
92
  const name = (group) => categories.find((c) => c.id === group)?.name ?? (group === "custom" ? labels.custom : groupName(groupNames, group));
89
93
  return resultSections(results, mode, name, tone, item, labels.searchResults);
@@ -100,7 +104,7 @@ function createEmojiPicker(store, options = {}) {
100
104
  const customGroups = store.customGroups();
101
105
  const query2 = queryOf(state, current);
102
106
  const searching = query2.trim().length > 0;
103
- const results = resultsOf(found.results, current.emojiVersion);
107
+ const results = resultsOf(found.results, current.emojiVersion, current.drawable, tone);
104
108
  const categories = categoriesOf(groupNames, customGroups, current.categories, labels);
105
109
  const suggested = current.suggested ?? (current.frequent === false ? NO_IDS : frequent);
106
110
  const collapsed2 = collapsedOf(state, current);
@@ -113,6 +117,7 @@ function createEmojiPicker(store, options = {}) {
113
117
  searching,
114
118
  results,
115
119
  pending: found.pending,
120
+ searchError: found.error,
116
121
  categories,
117
122
  categorySections: categorySections2,
118
123
  sections,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/picker/picker.ts"],"sourcesContent":["import { type ChangeDetails, controllable, createChangeDetails } from \"../change\";\nimport { withoutTone, type EmojiRecord, type SkinToneValue } from \"../dataset\";\nimport type { EmojiLabels } from \"../labels\";\nimport { createStore, memo, patch, shallowEqual, stable, subscribeAll, type Subscribe } from \"../reactive\";\nimport { createSearch, type EmojiSearchState } from \"../search/query\";\nimport type { EmojiSelection, EmojiStore } from \"../store\";\nimport {\n categoryList,\n categorySections,\n findItem,\n groupName,\n itemFactory,\n resultSections,\n setCategoryCollapsed,\n shownIn,\n type Category,\n type CategoryOption,\n type EmojiItem,\n type EmojiSection,\n type ItemFactory,\n type SearchResultsMode,\n} from \"./sections\";\n\nexport type HighlightChangeDetails = ChangeDetails<\"keyboard\" | \"pointer\" | \"imperative-action\" | \"none\"> & { key: string | null };\nexport type QueryChangeDetails = ChangeDetails<\"input-change\" | \"clear-press\" | \"escape-key\" | \"imperative-action\" | \"none\">;\nexport type CollapsedCategoriesChangeDetails = ChangeDetails<\"trigger-press\" | \"none\">;\n\nexport interface ChangeSource<D extends ChangeDetails> {\n reason?: D[\"reason\"];\n event?: Event;\n}\n\nexport interface EmojiPickerOptions<Icon = unknown> {\n labels?: EmojiLabels;\n categories?: readonly CategoryOption<Icon>[];\n frequent?: number | false;\n suggested?: readonly string[];\n emojiVersion?: number;\n searchResults?: SearchResultsMode;\n searchLimit?: number;\n query?: string;\n defaultQuery?: string;\n onQueryChange?: (query: string, details: QueryChangeDetails) => void;\n collapsedCategories?: readonly string[];\n defaultCollapsedCategories?: readonly string[];\n onCollapsedCategoriesChange?: (ids: readonly string[], details: CollapsedCategoriesChangeDetails) => void;\n highlight?: string | null;\n defaultHighlight?: string | null;\n onHighlightChange?: (unified: string | null, details: HighlightChangeDetails) => void;\n disableVariations?: boolean;\n reactions?: readonly string[] | number;\n}\n\n// \"virtual\": a search field or keyboard target keeps DOM focus and the grid follows aria-activedescendant\nexport type FocusMode = \"virtual\" | \"roving\";\n\nexport interface EmojiPickerSnapshot<Icon = unknown> {\n query: string;\n searching: boolean;\n results: readonly EmojiRecord[];\n pending: boolean;\n categories: readonly Category<Icon>[];\n categorySections: readonly EmojiSection[];\n sections: readonly EmojiSection[];\n collapsed: readonly string[];\n skinTone: SkinToneValue;\n focus: FocusMode;\n highlighted: string | null;\n hovered: string | null;\n active: EmojiItem | null;\n activeSection: string | null;\n variations: string | null;\n reactions: readonly string[] | undefined;\n // The grid's one tab stop and the pressed cell, by item id\n tabStop: string | null;\n pressed: string | null;\n}\n\nexport interface EmojiPickerStore<Icon = unknown> {\n getSnapshot(): EmojiPickerSnapshot<Icon>;\n subscribe: Subscribe;\n setOptions(options: EmojiPickerOptions<Icon>): void;\n setQuery(query: string, source?: ChangeSource<QueryChangeDetails>): QueryChangeDetails | undefined;\n toggleCollapsed(id: string, source?: ChangeSource<CollapsedCategoriesChangeDetails>): void;\n setCollapsed(id: string, collapsed: boolean, source?: ChangeSource<CollapsedCategoriesChangeDetails>): void;\n // An item id, or a unified code that resolves to its first item\n highlight(emoji: string | null, source?: ChangeSource<HighlightChangeDetails>): void;\n // The highlight a new set of searched sections should get: the first result, or the same emoji for the same query\n retainHighlight(sections: readonly EmojiSection[], last: string | null): { key: string | null } | null;\n hover(id: string | null): void;\n setActiveSection(id: string | null): void;\n setTabStop(id: string | null): void;\n press(id: string | null): void;\n choose(id?: string | null): EmojiSelection | undefined;\n openVariations(id: string | null): boolean;\n closeVariations(): void;\n // A search field or keyboard target that drives the grid with virtual focus\n registerInput(): () => void;\n mount(): void;\n setDomFocus(focused: boolean): void;\n // Freezes the suggested and reaction rows while the pointer or focus is inside, so a pick doesn't reorder them\n hold(held: boolean): void;\n}\n\ntype Held = { suggested: readonly string[]; reactions: readonly string[] | undefined };\n\ntype Ui = {\n query: string;\n collapsed: readonly string[];\n highlighted: string | null;\n hovered: string | null;\n activeSection: string | null;\n variations: string | null;\n inputs: number;\n mounted: boolean;\n domFocus: boolean;\n held: Held | null;\n tabStop: string | null;\n pressed: string | null;\n};\n\ntype Input<Icon> = { options: EmojiPickerOptions<Icon>; draft: readonly string[] | null };\ntype InputAction<Icon> = { type: \"options\"; options: EmojiPickerOptions<Icon> } | { type: \"draft\"; ids: readonly string[] };\n\nconst reduceInput = <Icon>(input: Input<Icon>, action: InputAction<Icon>): Input<Icon> => {\n switch (action.type) {\n case \"options\":\n return input.draft === null && shallowEqual(input.options, action.options) ? input : { options: action.options, draft: null };\n case \"draft\":\n return { ...input, draft: action.ids };\n default:\n return action satisfies never;\n }\n};\n\n// What the last search highlighted, so new results for the same query keep it\ntype Retained = { query: string; unified?: string };\n\ntype Controlled = Pick<EmojiPickerOptions, \"query\" | \"highlight\" | \"collapsedCategories\">;\nconst queryOf = (ui: Ui, options: Controlled) => options.query ?? ui.query;\nconst collapsedOf = (ui: Ui, options: Controlled) => options.collapsedCategories ?? ui.collapsed;\n\nconst NO_IDS: readonly string[] = [];\nconst NO_SECTIONS: readonly EmojiSection[] = [];\nconst DEFAULT_FREQUENT = 16;\nconst DEFAULT_SEARCH_LIMIT = 200;\n\n// A highlight is an item key or a unified code. A key whose emoji changed tone maps to the same emoji in its section; one that left the sections clears\nfunction resolveKey(sections: readonly EmojiSection[], value: string | null, last: string | null): string | null {\n if (value == null || findItem(sections, value)) return value;\n if (findItem(sections, last)?.unified === value) return last;\n const section = sections.find((s) => value.startsWith(`${s.id}:`));\n const unified = section ? value.slice(section.id.length + 1) : value;\n const items = (section ? [section] : sections).flatMap((s) => s.items);\n return (items.find((i) => i.unified === unified) ?? items.find((i) => withoutTone(i.unified) === withoutTone(unified)))?.id ?? null;\n}\n\nfunction retain(kept: Retained, sections: readonly EmojiSection[], query: string, last: string | null): { kept: Retained; target: EmojiItem | undefined } {\n const all = sections.flatMap((s) => s.items);\n const unified = all.find((i) => i.id === last)?.unified ?? kept.unified;\n const target = (kept.query === query ? all.find((i) => i.unified === unified) : undefined) ?? all[0];\n return { kept: { query, unified: target?.unified }, target };\n}\n\nexport function createEmojiPicker<Icon = unknown>(store: EmojiStore, options: EmojiPickerOptions<Icon> = {}): EmojiPickerStore<Icon> {\n const ui = createStore<Ui, Partial<Ui>>(\n {\n query: options.defaultQuery ?? \"\",\n collapsed: options.defaultCollapsedCategories ?? NO_IDS,\n highlighted: options.defaultHighlight ?? null,\n hovered: null,\n activeSection: null,\n variations: null,\n inputs: 0,\n mounted: false,\n domFocus: false,\n held: null,\n tabStop: null,\n pressed: null,\n },\n patch,\n );\n const input = createStore<Input<Icon>, InputAction<Icon>>({ options, draft: null }, reduceInput);\n const opts = () => input.getSnapshot().options;\n const searchOf = (state: Ui, current: EmojiPickerOptions<Icon>) => ({ query: queryOf(state, current), limit: current.searchLimit ?? DEFAULT_SEARCH_LIMIT });\n const initial = searchOf(ui.getSnapshot(), options);\n const search = createSearch(store, initial);\n const syncSearch = () => {\n const { query, limit } = searchOf(ui.getSnapshot(), opts());\n search.setQuery(query, limit);\n };\n let kept: Retained = { query: \"\" };\n\n const items = memo(itemFactory);\n const categoriesOf = memo((groupNames: readonly string[], customGroups: ReadonlyMap<string, string | undefined>, categories: readonly CategoryOption<Icon>[] | undefined, labels: EmojiLabels) =>\n categoryList(\n categories,\n groupNames,\n labels,\n [...customGroups.values()].filter((g): g is string => !!g),\n ),\n );\n const sectionsOf = memo(\n (\n records: readonly EmojiRecord[],\n customGroups: ReadonlyMap<string, string | undefined>,\n ready: boolean,\n categories: readonly Category<Icon>[],\n suggested: readonly string[],\n collapsed: readonly string[],\n emojiVersion: number | undefined,\n tone: SkinToneValue,\n item: ItemFactory,\n ) => (ready ? categorySections({ categories, records, suggested, customGroup: (u) => customGroups.get(u), collapsed, emojiVersion, get: store.get, tone, item }) : NO_SECTIONS),\n );\n const resultsOf = memo((results: readonly EmojiRecord[], emojiVersion: number | undefined) => (emojiVersion == null ? results : results.filter(shownIn(emojiVersion))));\n const resultSectionsOf = memo((results: readonly EmojiRecord[], mode: SearchResultsMode, categories: readonly Category<Icon>[], labels: EmojiLabels, groupNames: readonly string[], tone: SkinToneValue, item: ItemFactory) => {\n const name = (group: string) => categories.find((c) => c.id === group)?.name ?? (group === \"custom\" ? labels.custom : groupName(groupNames, group));\n return resultSections(results, mode, name, tone, item, labels.searchResults);\n });\n const activeOf = memo(findItem);\n const keep = stable<EmojiPickerSnapshot<Icon>>();\n const frequentCount = () => (opts().frequent ?? DEFAULT_FREQUENT) || 1;\n const reactionsOf = (reactions: EmojiPickerOptions<Icon>[\"reactions\"]) => (typeof reactions === \"number\" ? store.frequent(reactions) : reactions);\n\n const select = memo(\n (state: Ui, current: EmojiPickerOptions<Icon>, ready: boolean, records: readonly EmojiRecord[], tone: SkinToneValue, frequent: readonly string[], found: EmojiSearchState, storeLabels: EmojiLabels): EmojiPickerSnapshot<Icon> => {\n const labels = current.labels ?? storeLabels;\n const item = items(labels);\n const groupNames = ready ? store.groupNames() : NO_IDS;\n const customGroups = store.customGroups();\n const query = queryOf(state, current);\n const searching = query.trim().length > 0;\n const results = resultsOf(found.results, current.emojiVersion);\n const categories = categoriesOf(groupNames, customGroups, current.categories, labels);\n const suggested = current.suggested ?? (current.frequent === false ? NO_IDS : frequent);\n const collapsed = collapsedOf(state, current);\n const categorySections = sectionsOf(records, customGroups, ready, categories, suggested, collapsed, current.emojiVersion, tone, item);\n const sections = searching ? resultSectionsOf(results, current.searchResults ?? \"ranked\", categories, labels, groupNames, tone, item) : categorySections;\n const highlighted = resolveKey(sections, current.highlight === undefined ? state.highlighted : current.highlight, state.highlighted);\n const virtual = (state.inputs > 0 || !state.mounted) && !state.domFocus;\n return keep({\n query,\n searching,\n results,\n pending: found.pending,\n categories,\n categorySections,\n sections,\n collapsed,\n skinTone: tone,\n focus: virtual ? \"virtual\" : \"roving\",\n highlighted,\n hovered: state.hovered,\n active: activeOf(sections, highlighted ?? state.hovered),\n activeSection: state.activeSection,\n variations: state.variations,\n reactions: state.held ? state.held.reactions : reactionsOf(current.reactions),\n tabStop: state.tabStop,\n pressed: state.pressed,\n });\n },\n );\n\n function getSnapshot(): EmojiPickerSnapshot<Icon> {\n const state = ui.getSnapshot();\n return select(state, opts(), store.status() === \"ready\", store.records(), store.skinTone(), state.held?.suggested ?? store.frequent(frequentCount()), search.getSnapshot(), store.labels());\n }\n\n const set = (fields: Partial<Ui>) => ui.dispatch(fields);\n\n const query = controllable<string, QueryChangeDetails>({\n controlled: () => opts().query,\n own: () => ui.getSnapshot().query,\n onChange: () => opts().onQueryChange,\n write: (value) => set({ query: value }),\n });\n\n const collapsed = controllable<readonly string[], CollapsedCategoriesChangeDetails>({\n controlled: () => input.getSnapshot().draft ?? opts().collapsedCategories,\n own: () => ui.getSnapshot().collapsed,\n onChange: () => opts().onCollapsedCategoriesChange,\n write: (ids) => set({ collapsed: ids }),\n writeControlled: (ids) => input.dispatch({ type: \"draft\", ids }),\n });\n\n const unifiedOf = (key: string | null) => findItem(getSnapshot().sections, key)?.unified ?? null;\n // Kept when controlled too: the owner's unified code then resolves to this key, not to the emoji's first section\n const highlighted = controllable<string | null, HighlightChangeDetails>({\n controlled: () => undefined,\n own: () => getSnapshot().highlighted,\n onChange: () => {\n const onChange = opts().onHighlightChange;\n return onChange && ((key, details) => onChange(unifiedOf(key), details));\n },\n write: (key) => set({ highlighted: key }),\n });\n\n const changeCollapsed = (id: string, value: boolean | undefined, { reason = \"none\", event }: ChangeSource<CollapsedCategoriesChangeDetails>) =>\n void collapsed.set(setCategoryCollapsed(collapsed.value(), id, value), createChangeDetails(reason, event));\n\n return {\n getSnapshot,\n subscribe: subscribeAll(ui.subscribe, input.subscribe, store.subscribe, (listener) => store.frequents.subscribe(() => !ui.getSnapshot().held && listener()), search.subscribe),\n setOptions(next) {\n input.dispatch({ type: \"options\", options: next });\n syncSearch();\n },\n setQuery(value, { reason = \"none\", event } = {}) {\n const details = query.set(value, createChangeDetails(reason, event));\n syncSearch();\n return details;\n },\n toggleCollapsed: (id, source = {}) => changeCollapsed(id, undefined, source),\n setCollapsed: (id, value, source = {}) => changeCollapsed(id, value, source),\n highlight(emoji, { reason = \"none\", event } = {}) {\n const { sections, highlighted: current, searching } = getSnapshot();\n const key = resolveKey(sections, emoji, current);\n if (searching && key != null) kept = { ...kept, unified: findItem(sections, key)?.unified };\n highlighted.set(key, Object.assign(createChangeDetails(reason, event), { key }));\n },\n retainHighlight(sections, last) {\n const { searching, query: q } = getSnapshot();\n if (!searching) {\n if (!kept.query) return null;\n kept = { query: \"\" };\n return { key: null };\n }\n const next = retain(kept, sections, q, last);\n kept = next.kept;\n return { key: next.target?.id ?? null };\n },\n hover: (id) => set({ hovered: id }),\n setActiveSection: (id) => set({ activeSection: id }),\n setTabStop: (id) => set({ tabStop: id }),\n press: (id) => set({ pressed: id }),\n choose: (id = getSnapshot().highlighted) => (id == null ? undefined : store.select(findItem(getSnapshot().sections, id)?.unified ?? id)),\n openVariations(id) {\n const record = findItem(getSnapshot().sections, id)?.record;\n if (!record?.tones.length || record.imageUrl || opts().disableVariations) return false;\n set({ variations: id });\n return true;\n },\n closeVariations: () => set({ variations: null }),\n registerInput() {\n set({ inputs: ui.getSnapshot().inputs + 1 });\n return () => set({ inputs: ui.getSnapshot().inputs - 1 });\n },\n mount: () => set({ mounted: true }),\n setDomFocus: (value) => set({ domFocus: value }),\n hold(value) {\n if (Boolean(ui.getSnapshot().held) === value) return;\n set({ held: value ? { suggested: store.frequent(frequentCount()), reactions: reactionsOf(opts().reactions) } : null });\n },\n };\n}\n"],"mappings":"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","names":["query","collapsed","categorySections","highlighted"]}
1
+ {"version":3,"sources":["../../src/picker/picker.ts"],"sourcesContent":["import { type ChangeDetails, controllable, createChangeDetails } from \"../change\";\nimport { withoutTone, type EmojiRecord, type SkinToneValue } from \"../dataset\";\nimport type { EmojiLabels } from \"../labels\";\nimport { createStore, memo, patch, shallowEqual, stable, subscribeAll, type Subscribe } from \"../reactive\";\nimport { createSearch, type EmojiSearchState } from \"../search/query\";\nimport type { EmojiSelection, EmojiStore } from \"../store\";\nimport {\n categoryList,\n categorySections,\n findItem,\n groupName,\n itemFactory,\n resultSections,\n setCategoryCollapsed,\n shownIn,\n toned,\n type Category,\n type CategoryOption,\n type EmojiItem,\n type EmojiSection,\n type ItemFactory,\n type SearchResultsMode,\n} from \"./sections\";\n\nexport type HighlightChangeDetails = ChangeDetails<\"keyboard\" | \"pointer\" | \"imperative-action\" | \"none\"> & { key: string | null };\nexport type QueryChangeDetails = ChangeDetails<\"input-change\" | \"clear-press\" | \"escape-key\" | \"imperative-action\" | \"none\">;\nexport type CollapsedCategoriesChangeDetails = ChangeDetails<\"trigger-press\" | \"none\">;\n\nexport interface ChangeSource<D extends ChangeDetails> {\n reason?: D[\"reason\"];\n event?: Event;\n}\n\nexport interface EmojiPickerOptions<Icon = unknown> {\n labels?: EmojiLabels;\n categories?: readonly CategoryOption<Icon>[];\n frequent?: number | false;\n suggested?: readonly string[];\n emojiVersion?: number;\n // Results it rejects are hidden, as an emoji the system can't draw\n drawable?: (record: EmojiRecord, unified: string) => boolean;\n searchResults?: SearchResultsMode;\n searchLimit?: number;\n query?: string;\n defaultQuery?: string;\n onQueryChange?: (query: string, details: QueryChangeDetails) => void;\n collapsedCategories?: readonly string[];\n defaultCollapsedCategories?: readonly string[];\n onCollapsedCategoriesChange?: (ids: readonly string[], details: CollapsedCategoriesChangeDetails) => void;\n highlight?: string | null;\n defaultHighlight?: string | null;\n onHighlightChange?: (unified: string | null, details: HighlightChangeDetails) => void;\n disableVariations?: boolean;\n reactions?: readonly string[] | number;\n}\n\n// \"virtual\": a search field or keyboard target keeps DOM focus and the grid follows aria-activedescendant\nexport type FocusMode = \"virtual\" | \"roving\";\n\nexport interface EmojiPickerSnapshot<Icon = unknown> {\n query: string;\n searching: boolean;\n results: readonly EmojiRecord[];\n pending: boolean;\n searchError: Error | null;\n categories: readonly Category<Icon>[];\n categorySections: readonly EmojiSection[];\n sections: readonly EmojiSection[];\n collapsed: readonly string[];\n skinTone: SkinToneValue;\n focus: FocusMode;\n highlighted: string | null;\n hovered: string | null;\n active: EmojiItem | null;\n activeSection: string | null;\n variations: string | null;\n reactions: readonly string[] | undefined;\n // The grid's one tab stop and the pressed cell, by item id\n tabStop: string | null;\n pressed: string | null;\n}\n\nexport interface EmojiPickerStore<Icon = unknown> {\n getSnapshot(): EmojiPickerSnapshot<Icon>;\n subscribe: Subscribe;\n setOptions(options: EmojiPickerOptions<Icon>): void;\n setQuery(query: string, source?: ChangeSource<QueryChangeDetails>): QueryChangeDetails | undefined;\n toggleCollapsed(id: string, source?: ChangeSource<CollapsedCategoriesChangeDetails>): void;\n setCollapsed(id: string, collapsed: boolean, source?: ChangeSource<CollapsedCategoriesChangeDetails>): void;\n // An item id, or a unified code that resolves to its first item\n highlight(emoji: string | null, source?: ChangeSource<HighlightChangeDetails>): void;\n // The highlight a new set of searched sections should get: the first result, or the same emoji for the same query\n retainHighlight(sections: readonly EmojiSection[], last: string | null): { key: string | null } | null;\n hover(id: string | null): void;\n setActiveSection(id: string | null): void;\n setTabStop(id: string | null): void;\n press(id: string | null): void;\n choose(id?: string | null): EmojiSelection | undefined;\n openVariations(id: string | null): boolean;\n closeVariations(): void;\n // A search field or keyboard target that drives the grid with virtual focus\n registerInput(): () => void;\n mount(): void;\n setDomFocus(focused: boolean): void;\n // Freezes the suggested and reaction rows while the pointer or focus is inside, so a pick doesn't reorder them\n hold(held: boolean): void;\n}\n\ntype Held = { suggested: readonly string[]; reactions: readonly string[] | undefined };\n\ntype Ui = {\n query: string;\n collapsed: readonly string[];\n highlighted: string | null;\n hovered: string | null;\n activeSection: string | null;\n variations: string | null;\n inputs: number;\n mounted: boolean;\n domFocus: boolean;\n held: Held | null;\n tabStop: string | null;\n pressed: string | null;\n};\n\ntype Input<Icon> = { options: EmojiPickerOptions<Icon>; draft: readonly string[] | null };\ntype InputAction<Icon> = { type: \"options\"; options: EmojiPickerOptions<Icon> } | { type: \"draft\"; ids: readonly string[] };\n\nconst reduceInput = <Icon>(input: Input<Icon>, action: InputAction<Icon>): Input<Icon> => {\n switch (action.type) {\n case \"options\":\n return input.draft === null && shallowEqual(input.options, action.options) ? input : { options: action.options, draft: null };\n case \"draft\":\n return { ...input, draft: action.ids };\n default:\n return action satisfies never;\n }\n};\n\n// What the last search highlighted, so new results for the same query keep it\ntype Retained = { query: string; unified?: string };\n\ntype Controlled = Pick<EmojiPickerOptions, \"query\" | \"highlight\" | \"collapsedCategories\">;\nconst queryOf = (ui: Ui, options: Controlled) => options.query ?? ui.query;\nconst collapsedOf = (ui: Ui, options: Controlled) => options.collapsedCategories ?? ui.collapsed;\n\nconst NO_IDS: readonly string[] = [];\nconst NO_SECTIONS: readonly EmojiSection[] = [];\nconst DEFAULT_FREQUENT = 16;\nconst DEFAULT_SEARCH_LIMIT = 200;\n\n// A highlight is an item key or a unified code. A key whose emoji changed tone maps to the same emoji in its section; one that left the sections clears\nfunction resolveKey(sections: readonly EmojiSection[], value: string | null, last: string | null): string | null {\n if (value == null || findItem(sections, value)) return value;\n if (findItem(sections, last)?.unified === value) return last;\n const section = sections.find((s) => value.startsWith(`${s.id}:`));\n const unified = section ? value.slice(section.id.length + 1) : value;\n const items = (section ? [section] : sections).flatMap((s) => s.items);\n return (items.find((i) => i.unified === unified) ?? items.find((i) => withoutTone(i.unified) === withoutTone(unified)))?.id ?? null;\n}\n\nfunction retain(kept: Retained, sections: readonly EmojiSection[], query: string, last: string | null): { kept: Retained; target: EmojiItem | undefined } {\n const all = sections.flatMap((s) => s.items);\n const unified = all.find((i) => i.id === last)?.unified ?? kept.unified;\n const target = (kept.query === query ? all.find((i) => i.unified === unified) : undefined) ?? all[0];\n return { kept: { query, unified: target?.unified }, target };\n}\n\nexport function createEmojiPicker<Icon = unknown>(store: EmojiStore, options: EmojiPickerOptions<Icon> = {}): EmojiPickerStore<Icon> {\n const ui = createStore<Ui, Partial<Ui>>(\n {\n query: options.defaultQuery ?? \"\",\n collapsed: options.defaultCollapsedCategories ?? NO_IDS,\n highlighted: options.defaultHighlight ?? null,\n hovered: null,\n activeSection: null,\n variations: null,\n inputs: 0,\n mounted: false,\n domFocus: false,\n held: null,\n tabStop: null,\n pressed: null,\n },\n patch,\n );\n const input = createStore<Input<Icon>, InputAction<Icon>>({ options, draft: null }, reduceInput);\n const opts = () => input.getSnapshot().options;\n const searchOf = (state: Ui, current: EmojiPickerOptions<Icon>) => ({ query: queryOf(state, current), limit: current.searchLimit ?? DEFAULT_SEARCH_LIMIT });\n const initial = searchOf(ui.getSnapshot(), options);\n const search = createSearch(store, initial);\n const syncSearch = () => {\n const { query, limit } = searchOf(ui.getSnapshot(), opts());\n search.setQuery(query, limit);\n };\n let kept: Retained = { query: \"\" };\n\n const items = memo(itemFactory);\n const categoriesOf = memo((groupNames: readonly string[], customGroups: ReadonlyMap<string, string | undefined>, categories: readonly CategoryOption<Icon>[] | undefined, labels: EmojiLabels) =>\n categoryList(\n categories,\n groupNames,\n labels,\n [...customGroups.values()].filter((g): g is string => !!g),\n ),\n );\n const sectionsOf = memo(\n (\n records: readonly EmojiRecord[],\n customGroups: ReadonlyMap<string, string | undefined>,\n ready: boolean,\n categories: readonly Category<Icon>[],\n suggested: readonly string[],\n collapsed: readonly string[],\n emojiVersion: number | undefined,\n tone: SkinToneValue,\n item: ItemFactory,\n ) => (ready ? categorySections({ categories, records, suggested, customGroup: (u) => customGroups.get(u), collapsed, emojiVersion, get: store.get, tone, item }) : NO_SECTIONS),\n );\n const resultsOf = memo((results: readonly EmojiRecord[], emojiVersion: number | undefined, drawable: EmojiPickerOptions[\"drawable\"], tone: SkinToneValue) => {\n const shown = emojiVersion == null ? results : results.filter(shownIn(emojiVersion));\n return drawable ? shown.filter((r) => drawable(r, toned(r, tone))) : shown;\n });\n const resultSectionsOf = memo((results: readonly EmojiRecord[], mode: SearchResultsMode, categories: readonly Category<Icon>[], labels: EmojiLabels, groupNames: readonly string[], tone: SkinToneValue, item: ItemFactory) => {\n const name = (group: string) => categories.find((c) => c.id === group)?.name ?? (group === \"custom\" ? labels.custom : groupName(groupNames, group));\n return resultSections(results, mode, name, tone, item, labels.searchResults);\n });\n const activeOf = memo(findItem);\n const keep = stable<EmojiPickerSnapshot<Icon>>();\n const frequentCount = () => (opts().frequent ?? DEFAULT_FREQUENT) || 1;\n const reactionsOf = (reactions: EmojiPickerOptions<Icon>[\"reactions\"]) => (typeof reactions === \"number\" ? store.frequent(reactions) : reactions);\n\n const select = memo(\n (state: Ui, current: EmojiPickerOptions<Icon>, ready: boolean, records: readonly EmojiRecord[], tone: SkinToneValue, frequent: readonly string[], found: EmojiSearchState, storeLabels: EmojiLabels): EmojiPickerSnapshot<Icon> => {\n const labels = current.labels ?? storeLabels;\n const item = items(labels);\n const groupNames = ready ? store.groupNames() : NO_IDS;\n const customGroups = store.customGroups();\n const query = queryOf(state, current);\n const searching = query.trim().length > 0;\n const results = resultsOf(found.results, current.emojiVersion, current.drawable, tone);\n const categories = categoriesOf(groupNames, customGroups, current.categories, labels);\n const suggested = current.suggested ?? (current.frequent === false ? NO_IDS : frequent);\n const collapsed = collapsedOf(state, current);\n const categorySections = sectionsOf(records, customGroups, ready, categories, suggested, collapsed, current.emojiVersion, tone, item);\n const sections = searching ? resultSectionsOf(results, current.searchResults ?? \"ranked\", categories, labels, groupNames, tone, item) : categorySections;\n const highlighted = resolveKey(sections, current.highlight === undefined ? state.highlighted : current.highlight, state.highlighted);\n const virtual = (state.inputs > 0 || !state.mounted) && !state.domFocus;\n return keep({\n query,\n searching,\n results,\n pending: found.pending,\n searchError: found.error,\n categories,\n categorySections,\n sections,\n collapsed,\n skinTone: tone,\n focus: virtual ? \"virtual\" : \"roving\",\n highlighted,\n hovered: state.hovered,\n active: activeOf(sections, highlighted ?? state.hovered),\n activeSection: state.activeSection,\n variations: state.variations,\n reactions: state.held ? state.held.reactions : reactionsOf(current.reactions),\n tabStop: state.tabStop,\n pressed: state.pressed,\n });\n },\n );\n\n function getSnapshot(): EmojiPickerSnapshot<Icon> {\n const state = ui.getSnapshot();\n return select(state, opts(), store.status() === \"ready\", store.records(), store.skinTone(), state.held?.suggested ?? store.frequent(frequentCount()), search.getSnapshot(), store.labels());\n }\n\n const set = (fields: Partial<Ui>) => ui.dispatch(fields);\n\n const query = controllable<string, QueryChangeDetails>({\n controlled: () => opts().query,\n own: () => ui.getSnapshot().query,\n onChange: () => opts().onQueryChange,\n write: (value) => set({ query: value }),\n });\n\n const collapsed = controllable<readonly string[], CollapsedCategoriesChangeDetails>({\n controlled: () => input.getSnapshot().draft ?? opts().collapsedCategories,\n own: () => ui.getSnapshot().collapsed,\n onChange: () => opts().onCollapsedCategoriesChange,\n write: (ids) => set({ collapsed: ids }),\n writeControlled: (ids) => input.dispatch({ type: \"draft\", ids }),\n });\n\n const unifiedOf = (key: string | null) => findItem(getSnapshot().sections, key)?.unified ?? null;\n // Kept when controlled too: the owner's unified code then resolves to this key, not to the emoji's first section\n const highlighted = controllable<string | null, HighlightChangeDetails>({\n controlled: () => undefined,\n own: () => getSnapshot().highlighted,\n onChange: () => {\n const onChange = opts().onHighlightChange;\n return onChange && ((key, details) => onChange(unifiedOf(key), details));\n },\n write: (key) => set({ highlighted: key }),\n });\n\n const changeCollapsed = (id: string, value: boolean | undefined, { reason = \"none\", event }: ChangeSource<CollapsedCategoriesChangeDetails>) =>\n void collapsed.set(setCategoryCollapsed(collapsed.value(), id, value), createChangeDetails(reason, event));\n\n return {\n getSnapshot,\n subscribe: subscribeAll(ui.subscribe, input.subscribe, store.subscribe, (listener) => store.frequents.subscribe(() => !ui.getSnapshot().held && listener()), search.subscribe),\n setOptions(next) {\n input.dispatch({ type: \"options\", options: next });\n syncSearch();\n },\n setQuery(value, { reason = \"none\", event } = {}) {\n const details = query.set(value, createChangeDetails(reason, event));\n syncSearch();\n return details;\n },\n toggleCollapsed: (id, source = {}) => changeCollapsed(id, undefined, source),\n setCollapsed: (id, value, source = {}) => changeCollapsed(id, value, source),\n highlight(emoji, { reason = \"none\", event } = {}) {\n const { sections, highlighted: current, searching } = getSnapshot();\n const key = resolveKey(sections, emoji, current);\n if (searching && key != null) kept = { ...kept, unified: findItem(sections, key)?.unified };\n highlighted.set(key, Object.assign(createChangeDetails(reason, event), { key }));\n },\n retainHighlight(sections, last) {\n const { searching, query: q } = getSnapshot();\n if (!searching) {\n if (!kept.query) return null;\n kept = { query: \"\" };\n return { key: null };\n }\n const next = retain(kept, sections, q, last);\n kept = next.kept;\n return { key: next.target?.id ?? null };\n },\n hover: (id) => set({ hovered: id }),\n setActiveSection: (id) => set({ activeSection: id }),\n setTabStop: (id) => set({ tabStop: id }),\n press: (id) => set({ pressed: id }),\n choose: (id = getSnapshot().highlighted) => (id == null ? undefined : store.select(findItem(getSnapshot().sections, id)?.unified ?? id)),\n openVariations(id) {\n const record = findItem(getSnapshot().sections, id)?.record;\n if (!record?.tones.length || record.imageUrl || opts().disableVariations) return false;\n set({ variations: id });\n return true;\n },\n closeVariations: () => set({ variations: null }),\n registerInput() {\n set({ inputs: ui.getSnapshot().inputs + 1 });\n return () => set({ inputs: ui.getSnapshot().inputs - 1 });\n },\n mount: () => set({ mounted: true }),\n setDomFocus: (value) => set({ domFocus: value }),\n hold(value) {\n if (Boolean(ui.getSnapshot().held) === value) return;\n set({ held: value ? { suggested: store.frequent(frequentCount()), reactions: reactionsOf(opts().reactions) } : null });\n },\n };\n}\n"],"mappings":"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","names":["query","collapsed","categorySections","highlighted"]}
package/dist/platform.js CHANGED
@@ -6,7 +6,7 @@ var config = {
6
6
  // src/platform.ts
7
7
  import { withoutFe0f } from "./dataset.js";
8
8
  var DATA_CDN = "https://cdn.jsdelivr.net/npm/@emoji-components/data";
9
- var localeData = (locale, { shortcodes = "iamcal", baseUrl = `${DATA_CDN}@${true ? "1.0.0-alpha.1" : "latest"}/data` } = {}) => `${baseUrl}/${locale}/${shortcodes}.json`;
9
+ var localeData = (locale, { shortcodes = "iamcal", baseUrl = `${DATA_CDN}@${true ? "1.0.0-alpha.2" : "latest"}/data` } = {}) => `${baseUrl}/${locale}/${shortcodes}.json`;
10
10
  var DEFAULT_DATA_URL = localeData("en");
11
11
  var TWEMOJI_BASE_URL = `https://cdn.jsdelivr.net/gh/jdecked/twemoji@${config.twemoji}/assets/svg/`;
12
12
  var TWEMOJI_EMOJI_VERSION = 17;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../twemoji-font/package.json","../src/platform.ts"],"sourcesContent":["{\n \"name\": \"@emoji-components/twemoji-font\",\n \"version\": \"1.0.0-alpha.1\",\n \"description\": \"Twemoji as a color emoji font: unicode-range web font subsets and a TrueType file.\",\n \"keywords\": [\n \"emoji\",\n \"twemoji\",\n \"font\",\n \"colr\",\n \"woff2\",\n \"ttf\"\n ],\n \"license\": \"CC-BY-4.0\",\n \"author\": \"Brian Hung\",\n \"homepage\": \"https://emoji.brianhung.me\",\n \"bugs\": \"https://github.com/BrianHung/emoji-components/issues\",\n \"type\": \"module\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/BrianHung/emoji-components.git\",\n \"directory\": \"packages/twemoji-font\"\n },\n \"config\": {\n \"twemoji\": \"17.0.3\"\n },\n \"sideEffects\": [\n \"*.css\"\n ],\n \"files\": [\n \"dist/twemoji.css\",\n \"dist/*.woff2\",\n \"dist/Twemoji.ttf\",\n \"LICENSE\",\n \"NOTICE\"\n ],\n \"exports\": {\n \"./twemoji.css\": \"./dist/twemoji.css\",\n \"./Twemoji.ttf\": \"./dist/Twemoji.ttf\",\n \"./package.json\": \"./package.json\"\n },\n \"scripts\": {\n \"build\": \"tsx scripts/build.ts\",\n \"check\": \"tsx scripts/build.ts --check\",\n \"prepack\": \"npm run build\",\n \"test\": \"playwright test\",\n \"typecheck\": \"tsc --noEmit\"\n },\n \"devDependencies\": {\n \"@playwright/test\": \"1.64.1\",\n \"@types/node\": \"^22.20.5\",\n \"emojibase-data\": \"18.0.0\",\n \"tsx\": \"^4.23.15\",\n \"typescript\": \"^5.9.3\"\n }\n}\n","import { config } from \"@emoji-components/twemoji-font/package.json\";\nimport { withoutFe0f } from \"./dataset\";\n\nexport type Renderer = \"native\" | \"image\";\n\ndeclare const __DATA_VERSION__: string | undefined;\n\nconst DATA_CDN = \"https://cdn.jsdelivr.net/npm/@emoji-components/data\";\n\nexport type ShortcodeSet = \"iamcal\" | \"cldr\" | \"cldr-native\" | \"emojibase\" | \"emojibase-native\" | \"emojibase-legacy\" | \"github\" | \"joypixels\";\n\nexport const localeData = (locale: string, { shortcodes = \"iamcal\", baseUrl = `${DATA_CDN}@${typeof __DATA_VERSION__ === \"string\" ? __DATA_VERSION__ : \"latest\"}/data` }: { shortcodes?: ShortcodeSet; baseUrl?: string } = {}) =>\n `${baseUrl}/${locale}/${shortcodes}.json`;\n\nexport const DEFAULT_DATA_URL = localeData(\"en\");\n\nexport const TWEMOJI_BASE_URL = `https://cdn.jsdelivr.net/gh/jdecked/twemoji@${config.twemoji}/assets/svg/`;\nexport const TWEMOJI_EMOJI_VERSION = 17;\n\nexport function twemojiUrl(unified: string, baseUrl = TWEMOJI_BASE_URL): string {\n return `${baseUrl}${unified.includes(\"200d\") ? unified : withoutFe0f(unified)}.svg`;\n}\n"],"mappings":";AAsBE,aAAU;AAAA,EACR,SAAW;AACb;;;ACvBF,SAAS,mBAAmB;AAM5B,IAAM,WAAW;AAIV,IAAM,aAAa,CAAC,QAAgB,EAAE,aAAa,UAAU,UAAU,GAAG,QAAQ,IAAI,OAAuC,kBAAmB,QAAQ,QAAQ,IAAqD,CAAC,MAC3N,GAAG,OAAO,IAAI,MAAM,IAAI,UAAU;AAE7B,IAAM,mBAAmB,WAAW,IAAI;AAExC,IAAM,mBAAmB,+CAA+C,OAAO,OAAO;AACtF,IAAM,wBAAwB;AAE9B,SAAS,WAAW,SAAiB,UAAU,kBAA0B;AAC9E,SAAO,GAAG,OAAO,GAAG,QAAQ,SAAS,MAAM,IAAI,UAAU,YAAY,OAAO,CAAC;AAC/E;","names":[]}
1
+ {"version":3,"sources":["../../twemoji-font/package.json","../src/platform.ts"],"sourcesContent":["{\n \"name\": \"@emoji-components/twemoji-font\",\n \"version\": \"1.0.0-alpha.2\",\n \"description\": \"Twemoji as a color emoji font: unicode-range web font subsets and a TrueType file.\",\n \"keywords\": [\n \"emoji\",\n \"twemoji\",\n \"font\",\n \"colr\",\n \"woff2\",\n \"ttf\"\n ],\n \"license\": \"CC-BY-4.0\",\n \"author\": \"Brian Hung\",\n \"homepage\": \"https://emoji.brianhung.me\",\n \"bugs\": \"https://github.com/BrianHung/emoji-components/issues\",\n \"type\": \"module\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/BrianHung/emoji-components.git\",\n \"directory\": \"packages/twemoji-font\"\n },\n \"config\": {\n \"twemoji\": \"17.0.3\"\n },\n \"sideEffects\": [\n \"*.css\"\n ],\n \"files\": [\n \"dist/twemoji.css\",\n \"dist/*.woff2\",\n \"dist/Twemoji.ttf\",\n \"LICENSE\",\n \"NOTICE\"\n ],\n \"exports\": {\n \"./twemoji.css\": \"./dist/twemoji.css\",\n \"./Twemoji.ttf\": \"./dist/Twemoji.ttf\",\n \"./package.json\": \"./package.json\"\n },\n \"scripts\": {\n \"build\": \"tsx scripts/build.ts\",\n \"check\": \"tsx scripts/build.ts --check\",\n \"prepack\": \"npm run build\",\n \"test\": \"playwright test\",\n \"typecheck\": \"tsc --noEmit\"\n },\n \"devDependencies\": {\n \"@playwright/test\": \"1.64.1\",\n \"@types/node\": \"^22.20.5\",\n \"emojibase-data\": \"18.0.0\",\n \"tsx\": \"^4.23.15\",\n \"typescript\": \"^5.9.3\"\n }\n}\n","import { config } from \"@emoji-components/twemoji-font/package.json\";\nimport { withoutFe0f } from \"./dataset\";\n\nexport type Renderer = \"native\" | \"image\";\n\ndeclare const __DATA_VERSION__: string | undefined;\n\nconst DATA_CDN = \"https://cdn.jsdelivr.net/npm/@emoji-components/data\";\n\nexport type ShortcodeSet = \"iamcal\" | \"cldr\" | \"cldr-native\" | \"emojibase\" | \"emojibase-native\" | \"emojibase-legacy\" | \"github\" | \"joypixels\";\n\nexport const localeData = (locale: string, { shortcodes = \"iamcal\", baseUrl = `${DATA_CDN}@${typeof __DATA_VERSION__ === \"string\" ? __DATA_VERSION__ : \"latest\"}/data` }: { shortcodes?: ShortcodeSet; baseUrl?: string } = {}) =>\n `${baseUrl}/${locale}/${shortcodes}.json`;\n\nexport const DEFAULT_DATA_URL = localeData(\"en\");\n\nexport const TWEMOJI_BASE_URL = `https://cdn.jsdelivr.net/gh/jdecked/twemoji@${config.twemoji}/assets/svg/`;\nexport const TWEMOJI_EMOJI_VERSION = 17;\n\nexport function twemojiUrl(unified: string, baseUrl = TWEMOJI_BASE_URL): string {\n return `${baseUrl}${unified.includes(\"200d\") ? unified : withoutFe0f(unified)}.svg`;\n}\n"],"mappings":";AAsBE,aAAU;AAAA,EACR,SAAW;AACb;;;ACvBF,SAAS,mBAAmB;AAM5B,IAAM,WAAW;AAIV,IAAM,aAAa,CAAC,QAAgB,EAAE,aAAa,UAAU,UAAU,GAAG,QAAQ,IAAI,OAAuC,kBAAmB,QAAQ,QAAQ,IAAqD,CAAC,MAC3N,GAAG,OAAO,IAAI,MAAM,IAAI,UAAU;AAE7B,IAAM,mBAAmB,WAAW,IAAI;AAExC,IAAM,mBAAmB,+CAA+C,OAAO,OAAO;AACtF,IAAM,wBAAwB;AAE9B,SAAS,WAAW,SAAiB,UAAU,kBAA0B;AAC9E,SAAO,GAAG,OAAO,GAAG,QAAQ,SAAS,MAAM,IAAI,UAAU,YAAY,OAAO,CAAC;AAC/E;","names":[]}
package/dist/reactive.js CHANGED
@@ -48,15 +48,16 @@ var subscribeAll = (...subscribes) => (listener) => {
48
48
  };
49
49
  var toError = (e) => e instanceof Error ? e : new Error(String(e));
50
50
  var byUrl = /* @__PURE__ */ new Map();
51
- function fetchJson(url, label) {
52
- return cached(byUrl, url, () => {
53
- const pending = fetch(url, { priority: "low" }).then((response) => {
51
+ function fetchJson(url, label, signal) {
52
+ const request = cached(byUrl, url, () => {
53
+ const pending = fetch(url, { priority: "low", signal }).then((response) => {
54
54
  if (!response.ok) throw new Error(`${label}: ${response.status} ${response.statusText}`);
55
55
  return response.json();
56
56
  });
57
57
  pending.catch(() => byUrl.delete(url));
58
- return pending;
58
+ return { pending, signal };
59
59
  });
60
+ return request.pending.catch((e) => request.signal?.aborted && !signal?.aborted ? fetchJson(url, label, signal) : Promise.reject(e));
60
61
  }
61
62
  export {
62
63
  cached,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/reactive.ts"],"sourcesContent":["export type Listener = () => void;\nexport type Subscribe = (listener: Listener) => () => void;\n\nexport interface Store<State, Action> {\n getSnapshot(): State;\n subscribe: Subscribe;\n dispatch(action: Action): void;\n}\n\nexport function createStore<State, Action>(initial: State, reduce: (state: State, action: Action) => State): Store<State, Action> {\n let state = initial;\n const listeners = new Set<Listener>();\n return {\n getSnapshot: () => state,\n subscribe(listener) {\n listeners.add(listener);\n return () => void listeners.delete(listener);\n },\n dispatch(action) {\n const next = reduce(state, action);\n if (Object.is(next, state)) return;\n state = next;\n for (const listener of listeners) listener();\n },\n };\n}\n\nexport const shallowEqual = <T extends object>(a: T, b: T, eq: (x: T[keyof T], y: T[keyof T]) => boolean = Object.is): boolean => {\n const keys = Object.keys(b) as (keyof T)[];\n return keys.length === Object.keys(a).length && keys.every((k) => eq(a[k], b[k]));\n};\n\n// The patch's fields merged in, or the same object when none of them changes\nexport const patch = <T extends object>(state: T, fields: Partial<T>): T => ((Object.keys(fields) as (keyof T)[]).every((k) => Object.is(state[k], fields[k])) ? state : { ...state, ...fields });\n\nconst sameArgs = (a: readonly unknown[], b: readonly unknown[]) => a.length === b.length && a.every((x, i) => Object.is(x, b[i]));\n\nexport function memo<A extends unknown[], R>(fn: (...args: A) => R): (...args: A) => R {\n let last: { args: A; value: R } | undefined;\n return (...args) => {\n if (!last || !sameArgs(last.args, args)) last = { args, value: fn(...args) };\n return last.value;\n };\n}\n\n// The previous object while every field is the same, so snapshots keep their identity\nexport function stable<T extends object>(): (next: T) => T {\n let last: T | undefined;\n return (next) => (last = last && shallowEqual(last, next) ? last : next);\n}\n\ntype Cache<K, V> = { get(key: K): V | undefined; set(key: K, value: V): unknown };\n\nexport function cached<K, V>(cache: Cache<K, V>, key: K, create: () => V): V {\n const hit = cache.get(key);\n if (hit !== undefined) return hit;\n const value = create();\n cache.set(key, value);\n return value;\n}\n\nexport const subscribeAll =\n (...subscribes: Subscribe[]): Subscribe =>\n (listener) => {\n const offs = subscribes.map((subscribe) => subscribe(listener));\n return () => {\n for (const off of offs) off();\n };\n };\n\nexport const toError = (e: unknown): Error => (e instanceof Error ? e : new Error(String(e)));\n\nconst byUrl = new Map<string, Promise<unknown>>();\n\n// One request per URL; a failure is forgotten so a retry fetches again\nexport function fetchJson<T>(url: string, label: string): Promise<T> {\n return cached(byUrl, url, () => {\n const pending = fetch(url, { priority: \"low\" }).then((response) => {\n if (!response.ok) throw new Error(`${label}: ${response.status} ${response.statusText}`);\n return response.json();\n });\n pending.catch(() => byUrl.delete(url));\n return pending;\n }) as Promise<T>;\n}\n"],"mappings":";AASO,SAAS,YAA2B,SAAgB,QAAuE;AAChI,MAAI,QAAQ;AACZ,QAAM,YAAY,oBAAI,IAAc;AACpC,SAAO;AAAA,IACL,aAAa,MAAM;AAAA,IACnB,UAAU,UAAU;AAClB,gBAAU,IAAI,QAAQ;AACtB,aAAO,MAAM,KAAK,UAAU,OAAO,QAAQ;AAAA,IAC7C;AAAA,IACA,SAAS,QAAQ;AACf,YAAM,OAAO,OAAO,OAAO,MAAM;AACjC,UAAI,OAAO,GAAG,MAAM,KAAK,EAAG;AAC5B,cAAQ;AACR,iBAAW,YAAY,UAAW,UAAS;AAAA,IAC7C;AAAA,EACF;AACF;AAEO,IAAM,eAAe,CAAmB,GAAM,GAAM,KAAgD,OAAO,OAAgB;AAChI,QAAM,OAAO,OAAO,KAAK,CAAC;AAC1B,SAAO,KAAK,WAAW,OAAO,KAAK,CAAC,EAAE,UAAU,KAAK,MAAM,CAAC,MAAM,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;AAClF;AAGO,IAAM,QAAQ,CAAmB,OAAU,WAA4B,OAAO,KAAK,MAAM,EAAkB,MAAM,CAAC,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,QAAQ,EAAE,GAAG,OAAO,GAAG,OAAO;AAE/L,IAAM,WAAW,CAAC,GAAuB,MAA0B,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,CAAC,GAAG,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC;AAEzH,SAAS,KAA6B,IAA0C;AACrF,MAAI;AACJ,SAAO,IAAI,SAAS;AAClB,QAAI,CAAC,QAAQ,CAAC,SAAS,KAAK,MAAM,IAAI,EAAG,QAAO,EAAE,MAAM,OAAO,GAAG,GAAG,IAAI,EAAE;AAC3E,WAAO,KAAK;AAAA,EACd;AACF;AAGO,SAAS,SAA2C;AACzD,MAAI;AACJ,SAAO,CAAC,SAAU,OAAO,QAAQ,aAAa,MAAM,IAAI,IAAI,OAAO;AACrE;AAIO,SAAS,OAAa,OAAoB,KAAQ,QAAoB;AAC3E,QAAM,MAAM,MAAM,IAAI,GAAG;AACzB,MAAI,QAAQ,OAAW,QAAO;AAC9B,QAAM,QAAQ,OAAO;AACrB,QAAM,IAAI,KAAK,KAAK;AACpB,SAAO;AACT;AAEO,IAAM,eACX,IAAI,eACJ,CAAC,aAAa;AACZ,QAAM,OAAO,WAAW,IAAI,CAAC,cAAc,UAAU,QAAQ,CAAC;AAC9D,SAAO,MAAM;AACX,eAAW,OAAO,KAAM,KAAI;AAAA,EAC9B;AACF;AAEK,IAAM,UAAU,CAAC,MAAuB,aAAa,QAAQ,IAAI,IAAI,MAAM,OAAO,CAAC,CAAC;AAE3F,IAAM,QAAQ,oBAAI,IAA8B;AAGzC,SAAS,UAAa,KAAa,OAA2B;AACnE,SAAO,OAAO,OAAO,KAAK,MAAM;AAC9B,UAAM,UAAU,MAAM,KAAK,EAAE,UAAU,MAAM,CAAC,EAAE,KAAK,CAAC,aAAa;AACjE,UAAI,CAAC,SAAS,GAAI,OAAM,IAAI,MAAM,GAAG,KAAK,KAAK,SAAS,MAAM,IAAI,SAAS,UAAU,EAAE;AACvF,aAAO,SAAS,KAAK;AAAA,IACvB,CAAC;AACD,YAAQ,MAAM,MAAM,MAAM,OAAO,GAAG,CAAC;AACrC,WAAO;AAAA,EACT,CAAC;AACH;","names":[]}
1
+ {"version":3,"sources":["../src/reactive.ts"],"sourcesContent":["export type Listener = () => void;\nexport type Subscribe = (listener: Listener) => () => void;\n\nexport interface Store<State, Action> {\n getSnapshot(): State;\n subscribe: Subscribe;\n dispatch(action: Action): void;\n}\n\nexport function createStore<State, Action>(initial: State, reduce: (state: State, action: Action) => State): Store<State, Action> {\n let state = initial;\n const listeners = new Set<Listener>();\n return {\n getSnapshot: () => state,\n subscribe(listener) {\n listeners.add(listener);\n return () => void listeners.delete(listener);\n },\n dispatch(action) {\n const next = reduce(state, action);\n if (Object.is(next, state)) return;\n state = next;\n for (const listener of listeners) listener();\n },\n };\n}\n\nexport const shallowEqual = <T extends object>(a: T, b: T, eq: (x: T[keyof T], y: T[keyof T]) => boolean = Object.is): boolean => {\n const keys = Object.keys(b) as (keyof T)[];\n return keys.length === Object.keys(a).length && keys.every((k) => eq(a[k], b[k]));\n};\n\n// The patch's fields merged in, or the same object when none of them changes\nexport const patch = <T extends object>(state: T, fields: Partial<T>): T => ((Object.keys(fields) as (keyof T)[]).every((k) => Object.is(state[k], fields[k])) ? state : { ...state, ...fields });\n\nconst sameArgs = (a: readonly unknown[], b: readonly unknown[]) => a.length === b.length && a.every((x, i) => Object.is(x, b[i]));\n\nexport function memo<A extends unknown[], R>(fn: (...args: A) => R): (...args: A) => R {\n let last: { args: A; value: R } | undefined;\n return (...args) => {\n if (!last || !sameArgs(last.args, args)) last = { args, value: fn(...args) };\n return last.value;\n };\n}\n\n// The previous object while every field is the same, so snapshots keep their identity\nexport function stable<T extends object>(): (next: T) => T {\n let last: T | undefined;\n return (next) => (last = last && shallowEqual(last, next) ? last : next);\n}\n\ntype Cache<K, V> = { get(key: K): V | undefined; set(key: K, value: V): unknown };\n\nexport function cached<K, V>(cache: Cache<K, V>, key: K, create: () => V): V {\n const hit = cache.get(key);\n if (hit !== undefined) return hit;\n const value = create();\n cache.set(key, value);\n return value;\n}\n\nexport const subscribeAll =\n (...subscribes: Subscribe[]): Subscribe =>\n (listener) => {\n const offs = subscribes.map((subscribe) => subscribe(listener));\n return () => {\n for (const off of offs) off();\n };\n };\n\nexport const toError = (e: unknown): Error => (e instanceof Error ? e : new Error(String(e)));\n\nconst byUrl = new Map<string, { pending: Promise<unknown>; signal?: AbortSignal }>();\n\n// One request per URL; a failure or abort is forgotten, and a caller that shared an aborted request fetches again\nexport function fetchJson<T>(url: string, label: string, signal?: AbortSignal): Promise<T> {\n const request = cached(byUrl, url, () => {\n const pending = fetch(url, { priority: \"low\", signal }).then((response) => {\n if (!response.ok) throw new Error(`${label}: ${response.status} ${response.statusText}`);\n return response.json();\n });\n pending.catch(() => byUrl.delete(url));\n return { pending, signal };\n });\n return request.pending.catch((e: unknown) => (request.signal?.aborted && !signal?.aborted ? fetchJson(url, label, signal) : Promise.reject(e))) as Promise<T>;\n}\n"],"mappings":";AASO,SAAS,YAA2B,SAAgB,QAAuE;AAChI,MAAI,QAAQ;AACZ,QAAM,YAAY,oBAAI,IAAc;AACpC,SAAO;AAAA,IACL,aAAa,MAAM;AAAA,IACnB,UAAU,UAAU;AAClB,gBAAU,IAAI,QAAQ;AACtB,aAAO,MAAM,KAAK,UAAU,OAAO,QAAQ;AAAA,IAC7C;AAAA,IACA,SAAS,QAAQ;AACf,YAAM,OAAO,OAAO,OAAO,MAAM;AACjC,UAAI,OAAO,GAAG,MAAM,KAAK,EAAG;AAC5B,cAAQ;AACR,iBAAW,YAAY,UAAW,UAAS;AAAA,IAC7C;AAAA,EACF;AACF;AAEO,IAAM,eAAe,CAAmB,GAAM,GAAM,KAAgD,OAAO,OAAgB;AAChI,QAAM,OAAO,OAAO,KAAK,CAAC;AAC1B,SAAO,KAAK,WAAW,OAAO,KAAK,CAAC,EAAE,UAAU,KAAK,MAAM,CAAC,MAAM,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;AAClF;AAGO,IAAM,QAAQ,CAAmB,OAAU,WAA4B,OAAO,KAAK,MAAM,EAAkB,MAAM,CAAC,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,QAAQ,EAAE,GAAG,OAAO,GAAG,OAAO;AAE/L,IAAM,WAAW,CAAC,GAAuB,MAA0B,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,CAAC,GAAG,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC;AAEzH,SAAS,KAA6B,IAA0C;AACrF,MAAI;AACJ,SAAO,IAAI,SAAS;AAClB,QAAI,CAAC,QAAQ,CAAC,SAAS,KAAK,MAAM,IAAI,EAAG,QAAO,EAAE,MAAM,OAAO,GAAG,GAAG,IAAI,EAAE;AAC3E,WAAO,KAAK;AAAA,EACd;AACF;AAGO,SAAS,SAA2C;AACzD,MAAI;AACJ,SAAO,CAAC,SAAU,OAAO,QAAQ,aAAa,MAAM,IAAI,IAAI,OAAO;AACrE;AAIO,SAAS,OAAa,OAAoB,KAAQ,QAAoB;AAC3E,QAAM,MAAM,MAAM,IAAI,GAAG;AACzB,MAAI,QAAQ,OAAW,QAAO;AAC9B,QAAM,QAAQ,OAAO;AACrB,QAAM,IAAI,KAAK,KAAK;AACpB,SAAO;AACT;AAEO,IAAM,eACX,IAAI,eACJ,CAAC,aAAa;AACZ,QAAM,OAAO,WAAW,IAAI,CAAC,cAAc,UAAU,QAAQ,CAAC;AAC9D,SAAO,MAAM;AACX,eAAW,OAAO,KAAM,KAAI;AAAA,EAC9B;AACF;AAEK,IAAM,UAAU,CAAC,MAAuB,aAAa,QAAQ,IAAI,IAAI,MAAM,OAAO,CAAC,CAAC;AAE3F,IAAM,QAAQ,oBAAI,IAAiE;AAG5E,SAAS,UAAa,KAAa,OAAe,QAAkC;AACzF,QAAM,UAAU,OAAO,OAAO,KAAK,MAAM;AACvC,UAAM,UAAU,MAAM,KAAK,EAAE,UAAU,OAAO,OAAO,CAAC,EAAE,KAAK,CAAC,aAAa;AACzE,UAAI,CAAC,SAAS,GAAI,OAAM,IAAI,MAAM,GAAG,KAAK,KAAK,SAAS,MAAM,IAAI,SAAS,UAAU,EAAE;AACvF,aAAO,SAAS,KAAK;AAAA,IACvB,CAAC;AACD,YAAQ,MAAM,MAAM,MAAM,OAAO,GAAG,CAAC;AACrC,WAAO,EAAE,SAAS,OAAO;AAAA,EAC3B,CAAC;AACD,SAAO,QAAQ,QAAQ,MAAM,CAAC,MAAgB,QAAQ,QAAQ,WAAW,CAAC,QAAQ,UAAU,UAAU,KAAK,OAAO,MAAM,IAAI,QAAQ,OAAO,CAAC,CAAE;AAChJ;","names":[]}
@@ -1,13 +1,13 @@
1
1
  // src/search/query.ts
2
- import { createStore } from "../reactive.js";
2
+ import { createStore, toError } from "../reactive.js";
3
3
  var NONE = [];
4
- var IDLE = { results: NONE, pending: false };
4
+ var IDLE = { results: NONE, pending: false, error: null };
5
5
  var reduce = (run, action) => {
6
6
  switch (action.type) {
7
7
  case "start":
8
8
  return { ...run, key: action.key, version: action.version, state: action.state, controller: action.controller };
9
9
  case "settle":
10
- return run.controller === action.controller ? { ...run, state: { results: action.results, pending: false }, settled: action.results, controller: null } : run;
10
+ return run.controller === action.controller ? { ...run, key: action.error ? null : run.key, state: { results: action.results, pending: false, error: action.error }, settled: action.results, controller: null } : run;
11
11
  case "abort":
12
12
  return run.controller ? { ...run, key: null, controller: null } : run;
13
13
  default:
@@ -29,11 +29,11 @@ function createSearch(store, initial = { query: "", limit: 0 }) {
29
29
  if (!key) return run.dispatch({ type: "start", key, version, state: IDLE, controller: null });
30
30
  const controller = new AbortController();
31
31
  const out = store.query(query, { limit, signal: controller.signal });
32
- if (Array.isArray(out)) return run.dispatch({ type: "start", key, version, state: { results: out, pending: false }, controller: null });
33
- run.dispatch({ type: "start", key, version, state: { results: current.settled, pending: true }, controller });
32
+ if (Array.isArray(out)) return run.dispatch({ type: "start", key, version, state: { results: out, pending: false, error: null }, controller: null });
33
+ run.dispatch({ type: "start", key, version, state: { results: current.settled, pending: true, error: null }, controller });
34
34
  out.then(
35
- (results) => run.dispatch({ type: "settle", controller, results }),
36
- () => run.dispatch({ type: "settle", controller, results: NONE })
35
+ (results) => run.dispatch({ type: "settle", controller, results, error: null }),
36
+ (e) => run.dispatch({ type: "settle", controller, results: NONE, error: toError(e) })
37
37
  );
38
38
  };
39
39
  const abort = () => {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/search/query.ts"],"sourcesContent":["import type { EmojiRecord } from \"../dataset\";\nimport { createStore, type Subscribe } from \"../reactive\";\nimport type { EmojiStore } from \"../store\";\n\nexport interface EmojiSearchState {\n results: readonly EmojiRecord[];\n pending: boolean;\n}\n\nexport interface EmojiSearchQuery {\n query: string;\n limit: number;\n}\n\nexport interface EmojiSearchRunner {\n getSnapshot(): EmojiSearchState;\n subscribe: Subscribe;\n setQuery(query: string, limit: number): void;\n abort(): void;\n}\n\nconst NONE: readonly EmojiRecord[] = [];\nconst IDLE: EmojiSearchState = { results: NONE, pending: false };\n\n// key is null once aborted, so the next query runs even when it is the same\ntype Run = { key: string | null; version: number; state: EmojiSearchState; controller: AbortController | null; settled: readonly EmojiRecord[] };\ntype RunAction = { type: \"start\"; key: string; version: number; state: EmojiSearchState; controller: AbortController | null } | { type: \"settle\"; controller: AbortController; results: readonly EmojiRecord[] } | { type: \"abort\" };\n\nconst reduce = (run: Run, action: RunAction): Run => {\n switch (action.type) {\n case \"start\":\n return { ...run, key: action.key, version: action.version, state: action.state, controller: action.controller };\n case \"settle\":\n return run.controller === action.controller ? { ...run, state: { results: action.results, pending: false }, settled: action.results, controller: null } : run;\n case \"abort\":\n return run.controller ? { ...run, key: null, controller: null } : run;\n default:\n return action satisfies never;\n }\n};\n\n// The query is an input: setting it starts the run, and getSnapshot only reads; a new data version reruns it while anything listens\nexport function createSearch(store: EmojiStore, initial: EmojiSearchQuery = { query: \"\", limit: 0 }): EmojiSearchRunner {\n const run = createStore<Run, RunAction>({ key: \"\", version: -1, state: IDLE, controller: null, settled: NONE }, reduce);\n let input = initial;\n let watchers = 0;\n let unwatch: (() => void) | undefined;\n\n const refresh = () => {\n const { query, limit } = input;\n const key = store.status() === \"ready\" && query.trim() ? `${limit}:${query}` : \"\";\n const version = store.version();\n const current = run.getSnapshot();\n if (key === current.key && (!key || version === current.version)) return;\n current.controller?.abort();\n if (!key) return run.dispatch({ type: \"start\", key, version, state: IDLE, controller: null });\n const controller = new AbortController();\n const out = store.query(query, { limit, signal: controller.signal });\n if (Array.isArray(out)) return run.dispatch({ type: \"start\", key, version, state: { results: out, pending: false }, controller: null });\n run.dispatch({ type: \"start\", key, version, state: { results: current.settled, pending: true }, controller });\n out.then(\n (results) => run.dispatch({ type: \"settle\", controller, results }),\n () => run.dispatch({ type: \"settle\", controller, results: NONE }),\n );\n };\n\n const abort = () => {\n run.getSnapshot().controller?.abort();\n run.dispatch({ type: \"abort\" });\n };\n\n refresh();\n return {\n getSnapshot: () => run.getSnapshot().state,\n setQuery(query, limit) {\n input = { query, limit };\n refresh();\n },\n abort,\n subscribe(listener) {\n const off = run.subscribe(listener);\n if (watchers++ === 0) {\n unwatch = store.subscribe(refresh);\n refresh();\n }\n return () => {\n off();\n if (--watchers > 0) return;\n unwatch?.();\n abort();\n };\n },\n };\n}\n"],"mappings":";AACA,SAAS,mBAAmC;AAoB5C,IAAM,OAA+B,CAAC;AACtC,IAAM,OAAyB,EAAE,SAAS,MAAM,SAAS,MAAM;AAM/D,IAAM,SAAS,CAAC,KAAU,WAA2B;AACnD,UAAQ,OAAO,MAAM;AAAA,IACnB,KAAK;AACH,aAAO,EAAE,GAAG,KAAK,KAAK,OAAO,KAAK,SAAS,OAAO,SAAS,OAAO,OAAO,OAAO,YAAY,OAAO,WAAW;AAAA,IAChH,KAAK;AACH,aAAO,IAAI,eAAe,OAAO,aAAa,EAAE,GAAG,KAAK,OAAO,EAAE,SAAS,OAAO,SAAS,SAAS,MAAM,GAAG,SAAS,OAAO,SAAS,YAAY,KAAK,IAAI;AAAA,IAC5J,KAAK;AACH,aAAO,IAAI,aAAa,EAAE,GAAG,KAAK,KAAK,MAAM,YAAY,KAAK,IAAI;AAAA,IACpE;AACE,aAAO;AAAA,EACX;AACF;AAGO,SAAS,aAAa,OAAmB,UAA4B,EAAE,OAAO,IAAI,OAAO,EAAE,GAAsB;AACtH,QAAM,MAAM,YAA4B,EAAE,KAAK,IAAI,SAAS,IAAI,OAAO,MAAM,YAAY,MAAM,SAAS,KAAK,GAAG,MAAM;AACtH,MAAI,QAAQ;AACZ,MAAI,WAAW;AACf,MAAI;AAEJ,QAAM,UAAU,MAAM;AACpB,UAAM,EAAE,OAAO,MAAM,IAAI;AACzB,UAAM,MAAM,MAAM,OAAO,MAAM,WAAW,MAAM,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,KAAK;AAC/E,UAAM,UAAU,MAAM,QAAQ;AAC9B,UAAM,UAAU,IAAI,YAAY;AAChC,QAAI,QAAQ,QAAQ,QAAQ,CAAC,OAAO,YAAY,QAAQ,SAAU;AAClE,YAAQ,YAAY,MAAM;AAC1B,QAAI,CAAC,IAAK,QAAO,IAAI,SAAS,EAAE,MAAM,SAAS,KAAK,SAAS,OAAO,MAAM,YAAY,KAAK,CAAC;AAC5F,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,MAAM,MAAM,MAAM,OAAO,EAAE,OAAO,QAAQ,WAAW,OAAO,CAAC;AACnE,QAAI,MAAM,QAAQ,GAAG,EAAG,QAAO,IAAI,SAAS,EAAE,MAAM,SAAS,KAAK,SAAS,OAAO,EAAE,SAAS,KAAK,SAAS,MAAM,GAAG,YAAY,KAAK,CAAC;AACtI,QAAI,SAAS,EAAE,MAAM,SAAS,KAAK,SAAS,OAAO,EAAE,SAAS,QAAQ,SAAS,SAAS,KAAK,GAAG,WAAW,CAAC;AAC5G,QAAI;AAAA,MACF,CAAC,YAAY,IAAI,SAAS,EAAE,MAAM,UAAU,YAAY,QAAQ,CAAC;AAAA,MACjE,MAAM,IAAI,SAAS,EAAE,MAAM,UAAU,YAAY,SAAS,KAAK,CAAC;AAAA,IAClE;AAAA,EACF;AAEA,QAAM,QAAQ,MAAM;AAClB,QAAI,YAAY,EAAE,YAAY,MAAM;AACpC,QAAI,SAAS,EAAE,MAAM,QAAQ,CAAC;AAAA,EAChC;AAEA,UAAQ;AACR,SAAO;AAAA,IACL,aAAa,MAAM,IAAI,YAAY,EAAE;AAAA,IACrC,SAAS,OAAO,OAAO;AACrB,cAAQ,EAAE,OAAO,MAAM;AACvB,cAAQ;AAAA,IACV;AAAA,IACA;AAAA,IACA,UAAU,UAAU;AAClB,YAAM,MAAM,IAAI,UAAU,QAAQ;AAClC,UAAI,eAAe,GAAG;AACpB,kBAAU,MAAM,UAAU,OAAO;AACjC,gBAAQ;AAAA,MACV;AACA,aAAO,MAAM;AACX,YAAI;AACJ,YAAI,EAAE,WAAW,EAAG;AACpB,kBAAU;AACV,cAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../../src/search/query.ts"],"sourcesContent":["import type { EmojiRecord } from \"../dataset\";\nimport { createStore, toError, type Subscribe } from \"../reactive\";\nimport type { EmojiStore } from \"../store\";\n\nexport interface EmojiSearchState {\n results: readonly EmojiRecord[];\n pending: boolean;\n error: Error | null;\n}\n\nexport interface EmojiSearchQuery {\n query: string;\n limit: number;\n}\n\nexport interface EmojiSearchRunner {\n getSnapshot(): EmojiSearchState;\n subscribe: Subscribe;\n setQuery(query: string, limit: number): void;\n abort(): void;\n}\n\nconst NONE: readonly EmojiRecord[] = [];\nconst IDLE: EmojiSearchState = { results: NONE, pending: false, error: null };\n\n// key is null once aborted or failed, so the next query runs even when it is the same\ntype Run = { key: string | null; version: number; state: EmojiSearchState; controller: AbortController | null; settled: readonly EmojiRecord[] };\ntype RunAction =\n | { type: \"start\"; key: string; version: number; state: EmojiSearchState; controller: AbortController | null }\n | { type: \"settle\"; controller: AbortController; results: readonly EmojiRecord[]; error: Error | null }\n | { type: \"abort\" };\n\nconst reduce = (run: Run, action: RunAction): Run => {\n switch (action.type) {\n case \"start\":\n return { ...run, key: action.key, version: action.version, state: action.state, controller: action.controller };\n case \"settle\":\n return run.controller === action.controller ? { ...run, key: action.error ? null : run.key, state: { results: action.results, pending: false, error: action.error }, settled: action.results, controller: null } : run;\n case \"abort\":\n return run.controller ? { ...run, key: null, controller: null } : run;\n default:\n return action satisfies never;\n }\n};\n\n// The query is an input: setting it starts the run, and getSnapshot only reads; a new data version reruns it while anything listens\nexport function createSearch(store: EmojiStore, initial: EmojiSearchQuery = { query: \"\", limit: 0 }): EmojiSearchRunner {\n const run = createStore<Run, RunAction>({ key: \"\", version: -1, state: IDLE, controller: null, settled: NONE }, reduce);\n let input = initial;\n let watchers = 0;\n let unwatch: (() => void) | undefined;\n\n const refresh = () => {\n const { query, limit } = input;\n const key = store.status() === \"ready\" && query.trim() ? `${limit}:${query}` : \"\";\n const version = store.version();\n const current = run.getSnapshot();\n if (key === current.key && (!key || version === current.version)) return;\n current.controller?.abort();\n if (!key) return run.dispatch({ type: \"start\", key, version, state: IDLE, controller: null });\n const controller = new AbortController();\n const out = store.query(query, { limit, signal: controller.signal });\n if (Array.isArray(out)) return run.dispatch({ type: \"start\", key, version, state: { results: out, pending: false, error: null }, controller: null });\n run.dispatch({ type: \"start\", key, version, state: { results: current.settled, pending: true, error: null }, controller });\n out.then(\n (results) => run.dispatch({ type: \"settle\", controller, results, error: null }),\n (e: unknown) => run.dispatch({ type: \"settle\", controller, results: NONE, error: toError(e) }),\n );\n };\n\n const abort = () => {\n run.getSnapshot().controller?.abort();\n run.dispatch({ type: \"abort\" });\n };\n\n refresh();\n return {\n getSnapshot: () => run.getSnapshot().state,\n setQuery(query, limit) {\n input = { query, limit };\n refresh();\n },\n abort,\n subscribe(listener) {\n const off = run.subscribe(listener);\n if (watchers++ === 0) {\n unwatch = store.subscribe(refresh);\n refresh();\n }\n return () => {\n off();\n if (--watchers > 0) return;\n unwatch?.();\n abort();\n };\n },\n };\n}\n"],"mappings":"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","names":[]}
@@ -154,7 +154,7 @@ interface EmojiStore {
154
154
  records(): readonly EmojiRecord[];
155
155
  groupNames(): readonly string[];
156
156
  customGroups(): ReadonlyMap<string, string | undefined>;
157
- packErrors(): readonly Error[];
157
+ customEmojiErrors(): readonly Error[];
158
158
  locale(): string | undefined;
159
159
  labels(): EmojiLabels;
160
160
  get(unified: string): EmojiRecord | undefined;
package/dist/store.js CHANGED
@@ -13,7 +13,7 @@ var reduce = (state, action) => {
13
13
  case "load":
14
14
  return { ...state, status: state.status === "ready" ? "ready" : action.loading ? "loading" : "idle", loading: action.loading };
15
15
  case "loaded":
16
- return { ...state, status: "ready", error: null, data: action.data, version: state.version + 1 };
16
+ return { ...state, status: "ready", error: null, loading: null, data: action.data, version: state.version + 1 };
17
17
  case "failed":
18
18
  return { ...state, status: "error", error: action.error, loading: null };
19
19
  case "tone":
@@ -26,7 +26,7 @@ var reduce = (state, action) => {
26
26
  };
27
27
  var defaultEngine = lexicalSearch();
28
28
  var CODEPOINTS = /^[\da-f-]+$/i;
29
- var NO_DATA = { records: [], byUnified: /* @__PURE__ */ new Map(), groupNames: [], locale: void 0, customGroups: /* @__PURE__ */ new Map(), packErrors: [] };
29
+ var NO_DATA = { records: [], byUnified: /* @__PURE__ */ new Map(), groupNames: [], locale: void 0, customGroups: /* @__PURE__ */ new Map(), customEmojiErrors: [] };
30
30
  var inline = ({ data, customEmojis }) => typeof data === "object" && typeof customEmojis !== "function" ? [toDataset(data), { emojis: uniqueById(customEmojis ?? []), errors: [] }] : null;
31
31
  var customKey = (source) => typeof source === "function" ? "" : JSON.stringify(source ?? []);
32
32
  var sameData = (a, b) => a.data === b.data || typeof a.data === "function" && typeof b.data === "function" && a.locale === b.locale;
@@ -64,7 +64,7 @@ function createEmojiStore({
64
64
  const every = [...all, ...customRecords(emojis, all.length, (u) => byCode.get(u.toLowerCase()))];
65
65
  const records = base = config.filter ? every.filter(config.filter) : every;
66
66
  const byUnified = new Map(records.flatMap((r) => [[r.unified, r], ...r.tones.map((t) => [t, r])]));
67
- return { records, byUnified, groupNames: dataset.groups, locale: config.locale ?? dataset.locale, customGroups: new Map(emojis.map((c) => [c.id, c.group])), packErrors: errors };
67
+ return { records, byUnified, groupNames: dataset.groups, locale: config.locale ?? dataset.locale, customGroups: new Map(emojis.map((c) => [c.id, c.group])), customEmojiErrors: errors };
68
68
  };
69
69
  const storedTone = () => clampTone(Number(skinToneStorage.read() ?? defaultSkinTone));
70
70
  const state = createStore({ status: "idle", error: null, loading: null, data: { ...NO_DATA, locale: config.locale }, version: 0, ownTone: deferStorage ? clampTone(defaultSkinTone) : storedTone(), controlledTone }, reduce);
@@ -97,13 +97,14 @@ function createEmojiStore({
97
97
  const store = {
98
98
  frequents,
99
99
  customGroups: () => loaded().customGroups,
100
- packErrors: () => loaded().packErrors,
100
+ customEmojiErrors: () => loaded().customEmojiErrors,
101
101
  load() {
102
102
  const { loading } = state.getSnapshot();
103
- if (loading || raw) return loading ?? Promise.resolve();
103
+ if (loading || raw && !raw[1].errors.length) return loading ?? Promise.resolve();
104
104
  const current = controller = new AbortController();
105
105
  const { signal } = current;
106
- const pending = Promise.all([resolve(config.data ?? DEFAULT_DATA_URL, signal).then(toDataset), loadCustomEmojis(config.customEmojis, signal)]).then(
106
+ const dataset = raw ? Promise.resolve(raw[0]) : resolve(config.data ?? DEFAULT_DATA_URL, signal).then(toDataset);
107
+ const pending = Promise.all([dataset, loadCustomEmojis(config.customEmojis, signal)]).then(
107
108
  (next) => {
108
109
  if (controller === current) publish(next);
109
110
  },
@@ -194,7 +195,7 @@ var clampTone = (n) => Number.isInteger(n) && n >= 0 && n <= SKIN_TONES.length ?
194
195
  function resolve(source, signal) {
195
196
  if (typeof source === "function") return source({ signal });
196
197
  if (typeof source !== "string") return Promise.resolve(source);
197
- return fetchJson(source, "emoji data");
198
+ return fetchJson(source, "emoji data", signal);
198
199
  }
199
200
  function idle(fn) {
200
201
  if (typeof requestIdleCallback === "function") requestIdleCallback(fn, { timeout: 2e3 });
package/dist/store.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/store.ts"],"sourcesContent":["import { expandDataset, SKIN_TONES, toDataset, unifiedToNative, type Dataset, type EmojiDataInput, type EmojiRecord, type SkinToneValue } from \"./dataset\";\nimport { controllable, createChangeDetails, type ChangeDetails } from \"./change\";\nimport { customRecords, loadCustomEmojis, uniqueById, type CustomEmoji, type CustomEmojiSource } from \"./custom\";\nimport { createFrequencyStore, persisted, readOnFirstSubscribe, type FrequencyStore, type Frequents, type FrequentsChangeDetails } from \"./frequents\";\nimport { localizeLabels, type EmojiLabels } from \"./labels\";\nimport { DEFAULT_DATA_URL } from \"./platform\";\nimport { cached, createStore, fetchJson, memo, toError } from \"./reactive\";\nimport type { CompletionOrder, SearchIndex, ShortcodeCompletion } from \"./search/search-index\";\nimport { indexFor, isThenable, lexicalSearch, type EmojiSearch, type SearchHit } from \"./search/search\";\nimport { formatShortcode, replaceShortcodes, toneOf } from \"./shortcodes\";\n\nexport type EmojiDataSource = Dataset | EmojiDataInput | string | ((options: { signal: AbortSignal }) => Promise<Dataset | EmojiDataInput>);\n\nexport type { CustomEmoji, CustomEmojiLoader, CustomEmojiSource } from \"./custom\";\n\nexport interface EmojiStoreOptions {\n data?: EmojiDataSource;\n customEmojis?: CustomEmojiSource;\n filter?: (record: EmojiRecord) => boolean;\n search?: EmojiSearch;\n locale?: string;\n labels?: Partial<EmojiLabels>;\n skinTone?: SkinToneValue;\n defaultSkinTone?: SkinToneValue;\n onSkinToneChange?: (tone: SkinToneValue, details: SkinToneChangeDetails) => void;\n frequents?: Frequents;\n defaultFrequents?: Frequents;\n onFrequentsChange?: (frequents: Frequents, details: FrequentsChangeDetails) => void;\n // Server-rendered pages hydrate with the defaults, then read storage once something subscribes\n deferStorage?: boolean;\n}\n\ntype EmojiStoreUpdate = Pick<EmojiStoreOptions, \"data\" | \"customEmojis\" | \"filter\" | \"search\" | \"locale\" | \"skinTone\" | \"onSkinToneChange\" | \"frequents\" | \"onFrequentsChange\">;\n\nexport type SkinToneChangeDetails = ChangeDetails<\"item-press\" | \"none\">;\n\nexport type EmojiStoreStatus = \"idle\" | \"loading\" | \"ready\" | \"error\";\n\nexport interface EmojiSelection {\n emoji: string;\n unified: string;\n baseUnified: string;\n name: string;\n shortcode?: string;\n shortcodes: readonly string[];\n skinTone: SkinToneValue;\n isCustom: boolean;\n imageUrl?: string;\n}\n\nexport interface EmojiStore {\n readonly frequents: FrequencyStore;\n subscribe(listener: () => void): () => void;\n setOptions(options: EmojiStoreUpdate): void;\n load(): Promise<void>;\n prepare(): void;\n status(): EmojiStoreStatus;\n // Changes whenever the records, locale, or search engine do: a key for anything derived from them\n version(): number;\n error(): Error | null;\n records(): readonly EmojiRecord[];\n groupNames(): readonly string[];\n customGroups(): ReadonlyMap<string, string | undefined>;\n packErrors(): readonly Error[];\n locale(): string | undefined;\n labels(): EmojiLabels;\n get(unified: string): EmojiRecord | undefined;\n frequent(n: number): readonly string[];\n fromShortcode(code: string): EmojiRecord | undefined;\n query(text: string, options?: { limit?: number; signal?: AbortSignal }): EmojiRecord[] | Promise<EmojiRecord[]>;\n completeShortcode(prefix: string, limit?: number, order?: CompletionOrder): ShortcodeCompletion[];\n replaceShortcodes(text: string): string;\n skinTone(): SkinToneValue;\n setSkinTone(tone: SkinToneValue, options?: { reason?: SkinToneChangeDetails[\"reason\"]; event?: Event }): void;\n withSkinTone(unified: string): string;\n select(unified: string, options?: { count?: boolean }): EmojiSelection | undefined;\n}\n\ntype EmojiData = {\n records: readonly EmojiRecord[];\n byUnified: ReadonlyMap<string, EmojiRecord>;\n groupNames: readonly string[];\n locale: string | undefined;\n customGroups: ReadonlyMap<string, string | undefined>;\n packErrors: readonly Error[];\n};\n\ntype State = {\n status: EmojiStoreStatus;\n error: Error | null;\n loading: Promise<void> | null;\n data: EmojiData;\n version: number;\n ownTone: SkinToneValue;\n controlledTone: SkinToneValue | undefined;\n};\n\ntype Action = { type: \"load\"; loading: Promise<void> | null } | { type: \"loaded\"; data: EmojiData } | { type: \"failed\"; error: Error } | { type: \"tone\"; tone: SkinToneValue } | { type: \"control\"; tone: SkinToneValue | undefined };\n\nconst reduce = (state: State, action: Action): State => {\n switch (action.type) {\n case \"load\":\n return { ...state, status: state.status === \"ready\" ? \"ready\" : action.loading ? \"loading\" : \"idle\", loading: action.loading };\n case \"loaded\":\n return { ...state, status: \"ready\", error: null, data: action.data, version: state.version + 1 };\n case \"failed\":\n return { ...state, status: \"error\", error: action.error, loading: null };\n case \"tone\":\n return action.tone === state.ownTone ? state : { ...state, ownTone: action.tone };\n case \"control\":\n return action.tone === state.controlledTone ? state : { ...state, controlledTone: action.tone };\n default:\n return action satisfies never;\n }\n};\n\ntype Source = Pick<EmojiStoreOptions, \"data\" | \"customEmojis\" | \"filter\" | \"search\" | \"locale\">;\n\nconst defaultEngine = lexicalSearch();\nconst CODEPOINTS = /^[\\da-f-]+$/i;\nconst NO_DATA: EmojiData = { records: [], byUnified: new Map(), groupNames: [], locale: undefined, customGroups: new Map(), packErrors: [] };\n\ntype Raw = [dataset: Dataset, packs: { emojis: readonly CustomEmoji[]; errors: readonly Error[] }];\n\nconst inline = ({ data, customEmojis }: Source): Raw | null => (typeof data === \"object\" && typeof customEmojis !== \"function\" ? [toDataset(data), { emojis: uniqueById(customEmojis ?? []), errors: [] }] : null);\n\n// A loader or list written inline is new every render: loaders count as the same source, lists by their contents\nconst customKey = (source: CustomEmojiSource | undefined) => (typeof source === \"function\" ? \"\" : JSON.stringify(source ?? []));\nconst sameData = (a: Source, b: Source) => a.data === b.data || (typeof a.data === \"function\" && typeof b.data === \"function\" && a.locale === b.locale);\nconst sameSource = (a: Source, b: Source) => sameData(a, b) && (a.customEmojis === b.customEmojis || customKey(a.customEmojis) === customKey(b.customEmojis));\nconst sameItems = <T>(a: readonly T[], b: readonly T[]) => a.length === b.length && a.every((x, i) => x === b[i]);\n\nconst lookup = (data: EmojiData, unified: string) => data.byUnified.get(unified.toLowerCase()) ?? data.byUnified.get(unified);\n\nconst toned = (data: EmojiData, tone: SkinToneValue, unified: string): string => {\n const record = lookup(data, unified);\n return (tone && record?.tones[tone - 1]) || unified;\n};\n\nexport function createEmojiStore({\n frequents: controlledFrequents,\n defaultFrequents,\n onFrequentsChange,\n skinTone: controlledTone,\n defaultSkinTone = 0,\n onSkinToneChange,\n labels: labelOverrides,\n deferStorage = false,\n ...source\n}: EmojiStoreOptions = {}): EmojiStore {\n const frequents = createFrequencyStore({ frequents: controlledFrequents, defaultFrequents, onFrequentsChange, deferStorage });\n const skinToneStorage = persisted(\"emoji-components:skin-tone\");\n let config: Source = source;\n let raw: Raw | null = null;\n let base: readonly EmojiRecord[] = [];\n let controller: AbortController | undefined;\n let onToneChange = onSkinToneChange;\n const expand = memo(expandDataset);\n const build = (next: Raw): EmojiData => {\n raw = next;\n const [dataset, { emojis, errors }] = next;\n const all = expand(dataset);\n const byCode = new Map(all.map((r) => [r.unified, r]));\n const every = [...all, ...customRecords(emojis, all.length, (u) => byCode.get(u.toLowerCase()))];\n const records = (base = config.filter ? every.filter(config.filter) : every);\n const byUnified = new Map(records.flatMap((r) => [[r.unified, r] as const, ...r.tones.map((t) => [t, r] as const)]));\n return { records, byUnified, groupNames: dataset.groups, locale: config.locale ?? dataset.locale, customGroups: new Map(emojis.map((c) => [c.id, c.group])), packErrors: errors };\n };\n const storedTone = () => clampTone(Number(skinToneStorage.read() ?? defaultSkinTone));\n const state = createStore<State, Action>({ status: \"idle\", error: null, loading: null, data: { ...NO_DATA, locale: config.locale }, version: 0, ownTone: deferStorage ? clampTone(defaultSkinTone) : storedTone(), controlledTone }, reduce);\n const loaded = () => state.getSnapshot().data;\n const ready = () => state.getSnapshot().status === \"ready\";\n const publish = (next: Raw) => state.dispatch({ type: \"loaded\", data: build(next) });\n const reload = () => {\n const { status } = state.getSnapshot();\n controller?.abort();\n controller = undefined;\n raw = inline(config);\n state.dispatch({ type: \"load\", loading: null });\n if (raw) publish(raw);\n else if (status !== \"idle\") store.load().catch(() => {});\n };\n const known = memo((ranked: readonly string[], data: EmojiData, isReady: boolean) => ranked.filter((u) => (isReady ? lookup(data, u) : CODEPOINTS.test(u))));\n const tops = new WeakMap<readonly string[], Map<number, readonly string[]>>();\n const labels = localizeLabels(labelOverrides);\n const indexOf = memo((records: readonly EmojiRecord[], locale: string | undefined) => indexFor(records, { locale }));\n\n const tone = controllable<SkinToneValue, SkinToneChangeDetails>({\n controlled: () => state.getSnapshot().controlledTone,\n own: () => state.getSnapshot().ownTone,\n onChange: () => onToneChange,\n write(value) {\n skinToneStorage.write(String(value));\n state.dispatch({ type: \"tone\", tone: value });\n },\n });\n\n const store: EmojiStore = {\n frequents,\n customGroups: (): ReadonlyMap<string, string | undefined> => loaded().customGroups,\n packErrors: (): readonly Error[] => loaded().packErrors,\n\n load(): Promise<void> {\n const { loading } = state.getSnapshot();\n if (loading || raw) return loading ?? Promise.resolve();\n const current = (controller = new AbortController());\n const { signal } = current;\n const pending = Promise.all([resolve(config.data ?? DEFAULT_DATA_URL, signal).then(toDataset), loadCustomEmojis(config.customEmojis, signal)]).then(\n (next) => {\n if (controller === current) publish(next);\n },\n (e: unknown) => {\n if (controller !== current) return;\n const error = toError(e);\n state.dispatch({ type: \"failed\", error });\n throw error;\n },\n );\n state.dispatch({ type: \"load\", loading: pending });\n return pending;\n },\n\n setOptions({ skinTone, onSkinToneChange, frequents: entries, onFrequentsChange, ...next }) {\n onToneChange = onSkinToneChange;\n state.dispatch({ type: \"control\", tone: skinTone });\n frequents.control(entries, onFrequentsChange);\n const previous = config;\n config = next;\n if (!sameSource(previous, next)) return reload();\n if (!raw) return;\n const kept = base;\n const data = next.filter === previous.filter && next.locale === previous.locale ? { ...loaded() } : build(raw);\n if (next.locale !== previous.locale || !sameItems(kept, base) || next.search !== previous.search) state.dispatch({ type: \"loaded\", data });\n },\n\n prepare() {\n idle(() =>\n store\n .load()\n .then(() => idle(index))\n .catch(() => {}),\n );\n },\n\n status: () => state.getSnapshot().status,\n version: () => state.getSnapshot().version,\n error: () => state.getSnapshot().error,\n records: (): readonly EmojiRecord[] => loaded().records,\n groupNames: () => loaded().groupNames,\n locale: () => loaded().locale,\n labels: (): EmojiLabels => labels,\n\n get: (unified: string) => lookup(loaded(), unified),\n\n frequent(n: number): readonly string[] {\n const all = known(frequents.top(Infinity), loaded(), ready());\n return cached(\n cached(tops, all, () => new Map()),\n n,\n () => all.slice(0, n),\n );\n },\n\n fromShortcode: (code) => (ready() ? index().byShortcode(code) : undefined),\n\n query(text, { limit = 200, signal = new AbortController().signal } = {}) {\n if (!ready() || !text.trim()) return [];\n const resolve = (hits: readonly SearchHit[]) => [...new Set(hits.map((hit) => store.get(hit.unified)).filter((r): r is EmojiRecord => r !== undefined))].slice(0, limit);\n const result = (config.search ?? defaultEngine)(text, { records: loaded().records, limit, signal, frequency: (u) => frequents.score(u), locale: loaded().locale });\n return isThenable(result) ? Promise.resolve(result).then(resolve) : resolve(result);\n },\n completeShortcode: (prefix, limit, order) => (ready() ? index().completeShortcode(prefix, limit, order) : []),\n replaceShortcodes: (text) => (ready() ? replaceShortcodes(text, index()) : text),\n\n skinTone: () => clampTone(tone.value()),\n setSkinTone(next, { reason = \"none\", event } = {}) {\n const value = clampTone(next);\n if (value !== store.skinTone()) tone.set(value, createChangeDetails(reason, event));\n },\n select(unified, { count = true } = {}) {\n const record = store.get(unified);\n if (!record) return undefined;\n const tone = record.imageUrl ? 0 : toneOf(unified);\n const resolved = tone ? (record.tones[tone - 1] ?? record.unified) : record.unified;\n if (count) frequents.pick(record.unified);\n return {\n emoji: record.imageUrl ? \"\" : unifiedToNative(resolved),\n unified: resolved,\n baseUnified: record.unified,\n name: record.name,\n shortcode: formatShortcode(record, tone),\n shortcodes: record.shortcodes,\n skinTone: tone,\n isCustom: Boolean(record.imageUrl),\n imageUrl: record.imageUrl,\n };\n },\n\n withSkinTone: (unified) => toned(loaded(), store.skinTone(), unified),\n\n subscribe: readOnFirstSubscribe(state.subscribe, () => state.dispatch({ type: \"tone\", tone: storedTone() })),\n };\n const index = (): SearchIndex => indexOf(loaded().records, loaded().locale);\n reload();\n return store;\n}\n\nconst clampTone = (n: number): SkinToneValue => (Number.isInteger(n) && n >= 0 && n <= SKIN_TONES.length ? (n as SkinToneValue) : 0);\n\nfunction resolve(source: EmojiDataSource, signal: AbortSignal): Promise<Dataset | EmojiDataInput> {\n if (typeof source === \"function\") return source({ signal });\n if (typeof source !== \"string\") return Promise.resolve(source);\n return fetchJson<Dataset | EmojiDataInput>(source, \"emoji data\");\n}\n\nfunction idle(fn: () => void) {\n if (typeof requestIdleCallback === \"function\") requestIdleCallback(fn, { timeout: 2000 });\n else setTimeout(fn, 1);\n}\n"],"mappings":"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","names":["onSkinToneChange","onFrequentsChange","resolve","tone"]}
1
+ {"version":3,"sources":["../src/store.ts"],"sourcesContent":["import { expandDataset, SKIN_TONES, toDataset, unifiedToNative, type Dataset, type EmojiDataInput, type EmojiRecord, type SkinToneValue } from \"./dataset\";\nimport { controllable, createChangeDetails, type ChangeDetails } from \"./change\";\nimport { customRecords, loadCustomEmojis, uniqueById, type CustomEmoji, type CustomEmojiSource } from \"./custom\";\nimport { createFrequencyStore, persisted, readOnFirstSubscribe, type FrequencyStore, type Frequents, type FrequentsChangeDetails } from \"./frequents\";\nimport { localizeLabels, type EmojiLabels } from \"./labels\";\nimport { DEFAULT_DATA_URL } from \"./platform\";\nimport { cached, createStore, fetchJson, memo, toError } from \"./reactive\";\nimport type { CompletionOrder, SearchIndex, ShortcodeCompletion } from \"./search/search-index\";\nimport { indexFor, isThenable, lexicalSearch, type EmojiSearch, type SearchHit } from \"./search/search\";\nimport { formatShortcode, replaceShortcodes, toneOf } from \"./shortcodes\";\n\nexport type EmojiDataSource = Dataset | EmojiDataInput | string | ((options: { signal: AbortSignal }) => Promise<Dataset | EmojiDataInput>);\n\nexport type { CustomEmoji, CustomEmojiLoader, CustomEmojiSource } from \"./custom\";\n\nexport interface EmojiStoreOptions {\n data?: EmojiDataSource;\n customEmojis?: CustomEmojiSource;\n filter?: (record: EmojiRecord) => boolean;\n search?: EmojiSearch;\n locale?: string;\n labels?: Partial<EmojiLabels>;\n skinTone?: SkinToneValue;\n defaultSkinTone?: SkinToneValue;\n onSkinToneChange?: (tone: SkinToneValue, details: SkinToneChangeDetails) => void;\n frequents?: Frequents;\n defaultFrequents?: Frequents;\n onFrequentsChange?: (frequents: Frequents, details: FrequentsChangeDetails) => void;\n // Server-rendered pages hydrate with the defaults, then read storage once something subscribes\n deferStorage?: boolean;\n}\n\ntype EmojiStoreUpdate = Pick<EmojiStoreOptions, \"data\" | \"customEmojis\" | \"filter\" | \"search\" | \"locale\" | \"skinTone\" | \"onSkinToneChange\" | \"frequents\" | \"onFrequentsChange\">;\n\nexport type SkinToneChangeDetails = ChangeDetails<\"item-press\" | \"none\">;\n\nexport type EmojiStoreStatus = \"idle\" | \"loading\" | \"ready\" | \"error\";\n\nexport interface EmojiSelection {\n emoji: string;\n unified: string;\n baseUnified: string;\n name: string;\n shortcode?: string;\n shortcodes: readonly string[];\n skinTone: SkinToneValue;\n isCustom: boolean;\n imageUrl?: string;\n}\n\nexport interface EmojiStore {\n readonly frequents: FrequencyStore;\n subscribe(listener: () => void): () => void;\n setOptions(options: EmojiStoreUpdate): void;\n load(): Promise<void>;\n prepare(): void;\n status(): EmojiStoreStatus;\n // Changes whenever the records, locale, or search engine do: a key for anything derived from them\n version(): number;\n error(): Error | null;\n records(): readonly EmojiRecord[];\n groupNames(): readonly string[];\n customGroups(): ReadonlyMap<string, string | undefined>;\n customEmojiErrors(): readonly Error[];\n locale(): string | undefined;\n labels(): EmojiLabels;\n get(unified: string): EmojiRecord | undefined;\n frequent(n: number): readonly string[];\n fromShortcode(code: string): EmojiRecord | undefined;\n query(text: string, options?: { limit?: number; signal?: AbortSignal }): EmojiRecord[] | Promise<EmojiRecord[]>;\n completeShortcode(prefix: string, limit?: number, order?: CompletionOrder): ShortcodeCompletion[];\n replaceShortcodes(text: string): string;\n skinTone(): SkinToneValue;\n setSkinTone(tone: SkinToneValue, options?: { reason?: SkinToneChangeDetails[\"reason\"]; event?: Event }): void;\n withSkinTone(unified: string): string;\n select(unified: string, options?: { count?: boolean }): EmojiSelection | undefined;\n}\n\ntype EmojiData = {\n records: readonly EmojiRecord[];\n byUnified: ReadonlyMap<string, EmojiRecord>;\n groupNames: readonly string[];\n locale: string | undefined;\n customGroups: ReadonlyMap<string, string | undefined>;\n customEmojiErrors: readonly Error[];\n};\n\ntype State = {\n status: EmojiStoreStatus;\n error: Error | null;\n loading: Promise<void> | null;\n data: EmojiData;\n version: number;\n ownTone: SkinToneValue;\n controlledTone: SkinToneValue | undefined;\n};\n\ntype Action = { type: \"load\"; loading: Promise<void> | null } | { type: \"loaded\"; data: EmojiData } | { type: \"failed\"; error: Error } | { type: \"tone\"; tone: SkinToneValue } | { type: \"control\"; tone: SkinToneValue | undefined };\n\nconst reduce = (state: State, action: Action): State => {\n switch (action.type) {\n case \"load\":\n return { ...state, status: state.status === \"ready\" ? \"ready\" : action.loading ? \"loading\" : \"idle\", loading: action.loading };\n case \"loaded\":\n return { ...state, status: \"ready\", error: null, loading: null, data: action.data, version: state.version + 1 };\n case \"failed\":\n return { ...state, status: \"error\", error: action.error, loading: null };\n case \"tone\":\n return action.tone === state.ownTone ? state : { ...state, ownTone: action.tone };\n case \"control\":\n return action.tone === state.controlledTone ? state : { ...state, controlledTone: action.tone };\n default:\n return action satisfies never;\n }\n};\n\ntype Source = Pick<EmojiStoreOptions, \"data\" | \"customEmojis\" | \"filter\" | \"search\" | \"locale\">;\n\nconst defaultEngine = lexicalSearch();\nconst CODEPOINTS = /^[\\da-f-]+$/i;\nconst NO_DATA: EmojiData = { records: [], byUnified: new Map(), groupNames: [], locale: undefined, customGroups: new Map(), customEmojiErrors: [] };\n\ntype Raw = [dataset: Dataset, packs: { emojis: readonly CustomEmoji[]; errors: readonly Error[] }];\n\nconst inline = ({ data, customEmojis }: Source): Raw | null => (typeof data === \"object\" && typeof customEmojis !== \"function\" ? [toDataset(data), { emojis: uniqueById(customEmojis ?? []), errors: [] }] : null);\n\n// A loader or list written inline is new every render: loaders count as the same source, lists by their contents\nconst customKey = (source: CustomEmojiSource | undefined) => (typeof source === \"function\" ? \"\" : JSON.stringify(source ?? []));\nconst sameData = (a: Source, b: Source) => a.data === b.data || (typeof a.data === \"function\" && typeof b.data === \"function\" && a.locale === b.locale);\nconst sameSource = (a: Source, b: Source) => sameData(a, b) && (a.customEmojis === b.customEmojis || customKey(a.customEmojis) === customKey(b.customEmojis));\nconst sameItems = <T>(a: readonly T[], b: readonly T[]) => a.length === b.length && a.every((x, i) => x === b[i]);\n\nconst lookup = (data: EmojiData, unified: string) => data.byUnified.get(unified.toLowerCase()) ?? data.byUnified.get(unified);\n\nconst toned = (data: EmojiData, tone: SkinToneValue, unified: string): string => {\n const record = lookup(data, unified);\n return (tone && record?.tones[tone - 1]) || unified;\n};\n\nexport function createEmojiStore({\n frequents: controlledFrequents,\n defaultFrequents,\n onFrequentsChange,\n skinTone: controlledTone,\n defaultSkinTone = 0,\n onSkinToneChange,\n labels: labelOverrides,\n deferStorage = false,\n ...source\n}: EmojiStoreOptions = {}): EmojiStore {\n const frequents = createFrequencyStore({ frequents: controlledFrequents, defaultFrequents, onFrequentsChange, deferStorage });\n const skinToneStorage = persisted(\"emoji-components:skin-tone\");\n let config: Source = source;\n let raw: Raw | null = null;\n let base: readonly EmojiRecord[] = [];\n let controller: AbortController | undefined;\n let onToneChange = onSkinToneChange;\n const expand = memo(expandDataset);\n const build = (next: Raw): EmojiData => {\n raw = next;\n const [dataset, { emojis, errors }] = next;\n const all = expand(dataset);\n const byCode = new Map(all.map((r) => [r.unified, r]));\n const every = [...all, ...customRecords(emojis, all.length, (u) => byCode.get(u.toLowerCase()))];\n const records = (base = config.filter ? every.filter(config.filter) : every);\n const byUnified = new Map(records.flatMap((r) => [[r.unified, r] as const, ...r.tones.map((t) => [t, r] as const)]));\n return { records, byUnified, groupNames: dataset.groups, locale: config.locale ?? dataset.locale, customGroups: new Map(emojis.map((c) => [c.id, c.group])), customEmojiErrors: errors };\n };\n const storedTone = () => clampTone(Number(skinToneStorage.read() ?? defaultSkinTone));\n const state = createStore<State, Action>({ status: \"idle\", error: null, loading: null, data: { ...NO_DATA, locale: config.locale }, version: 0, ownTone: deferStorage ? clampTone(defaultSkinTone) : storedTone(), controlledTone }, reduce);\n const loaded = () => state.getSnapshot().data;\n const ready = () => state.getSnapshot().status === \"ready\";\n const publish = (next: Raw) => state.dispatch({ type: \"loaded\", data: build(next) });\n const reload = () => {\n const { status } = state.getSnapshot();\n controller?.abort();\n controller = undefined;\n raw = inline(config);\n state.dispatch({ type: \"load\", loading: null });\n if (raw) publish(raw);\n else if (status !== \"idle\") store.load().catch(() => {});\n };\n const known = memo((ranked: readonly string[], data: EmojiData, isReady: boolean) => ranked.filter((u) => (isReady ? lookup(data, u) : CODEPOINTS.test(u))));\n const tops = new WeakMap<readonly string[], Map<number, readonly string[]>>();\n const labels = localizeLabels(labelOverrides);\n const indexOf = memo((records: readonly EmojiRecord[], locale: string | undefined) => indexFor(records, { locale }));\n\n const tone = controllable<SkinToneValue, SkinToneChangeDetails>({\n controlled: () => state.getSnapshot().controlledTone,\n own: () => state.getSnapshot().ownTone,\n onChange: () => onToneChange,\n write(value) {\n skinToneStorage.write(String(value));\n state.dispatch({ type: \"tone\", tone: value });\n },\n });\n\n const store: EmojiStore = {\n frequents,\n customGroups: (): ReadonlyMap<string, string | undefined> => loaded().customGroups,\n customEmojiErrors: (): readonly Error[] => loaded().customEmojiErrors,\n\n load(): Promise<void> {\n const { loading } = state.getSnapshot();\n if (loading || (raw && !raw[1].errors.length)) return loading ?? Promise.resolve();\n const current = (controller = new AbortController());\n const { signal } = current;\n // A failed custom emoji loader runs again; the dataset it loaded with is kept\n const dataset = raw ? Promise.resolve(raw[0]) : resolve(config.data ?? DEFAULT_DATA_URL, signal).then(toDataset);\n const pending = Promise.all([dataset, loadCustomEmojis(config.customEmojis, signal)]).then(\n (next) => {\n if (controller === current) publish(next);\n },\n (e: unknown) => {\n if (controller !== current) return;\n const error = toError(e);\n state.dispatch({ type: \"failed\", error });\n throw error;\n },\n );\n state.dispatch({ type: \"load\", loading: pending });\n return pending;\n },\n\n setOptions({ skinTone, onSkinToneChange, frequents: entries, onFrequentsChange, ...next }) {\n onToneChange = onSkinToneChange;\n state.dispatch({ type: \"control\", tone: skinTone });\n frequents.control(entries, onFrequentsChange);\n const previous = config;\n config = next;\n if (!sameSource(previous, next)) return reload();\n if (!raw) return;\n const kept = base;\n const data = next.filter === previous.filter && next.locale === previous.locale ? { ...loaded() } : build(raw);\n if (next.locale !== previous.locale || !sameItems(kept, base) || next.search !== previous.search) state.dispatch({ type: \"loaded\", data });\n },\n\n prepare() {\n idle(() =>\n store\n .load()\n .then(() => idle(index))\n .catch(() => {}),\n );\n },\n\n status: () => state.getSnapshot().status,\n version: () => state.getSnapshot().version,\n error: () => state.getSnapshot().error,\n records: (): readonly EmojiRecord[] => loaded().records,\n groupNames: () => loaded().groupNames,\n locale: () => loaded().locale,\n labels: (): EmojiLabels => labels,\n\n get: (unified: string) => lookup(loaded(), unified),\n\n frequent(n: number): readonly string[] {\n const all = known(frequents.top(Infinity), loaded(), ready());\n return cached(\n cached(tops, all, () => new Map()),\n n,\n () => all.slice(0, n),\n );\n },\n\n fromShortcode: (code) => (ready() ? index().byShortcode(code) : undefined),\n\n query(text, { limit = 200, signal = new AbortController().signal } = {}) {\n if (!ready() || !text.trim()) return [];\n const resolve = (hits: readonly SearchHit[]) => [...new Set(hits.map((hit) => store.get(hit.unified)).filter((r): r is EmojiRecord => r !== undefined))].slice(0, limit);\n const result = (config.search ?? defaultEngine)(text, { records: loaded().records, limit, signal, frequency: (u) => frequents.score(u), locale: loaded().locale });\n return isThenable(result) ? Promise.resolve(result).then(resolve) : resolve(result);\n },\n completeShortcode: (prefix, limit, order) => (ready() ? index().completeShortcode(prefix, limit, order) : []),\n replaceShortcodes: (text) => (ready() ? replaceShortcodes(text, index()) : text),\n\n skinTone: () => clampTone(tone.value()),\n setSkinTone(next, { reason = \"none\", event } = {}) {\n const value = clampTone(next);\n if (value !== store.skinTone()) tone.set(value, createChangeDetails(reason, event));\n },\n select(unified, { count = true } = {}) {\n const record = store.get(unified);\n if (!record) return undefined;\n const tone = record.imageUrl ? 0 : toneOf(unified);\n const resolved = tone ? (record.tones[tone - 1] ?? record.unified) : record.unified;\n if (count) frequents.pick(record.unified);\n return {\n emoji: record.imageUrl ? \"\" : unifiedToNative(resolved),\n unified: resolved,\n baseUnified: record.unified,\n name: record.name,\n shortcode: formatShortcode(record, tone),\n shortcodes: record.shortcodes,\n skinTone: tone,\n isCustom: Boolean(record.imageUrl),\n imageUrl: record.imageUrl,\n };\n },\n\n withSkinTone: (unified) => toned(loaded(), store.skinTone(), unified),\n\n subscribe: readOnFirstSubscribe(state.subscribe, () => state.dispatch({ type: \"tone\", tone: storedTone() })),\n };\n const index = (): SearchIndex => indexOf(loaded().records, loaded().locale);\n reload();\n return store;\n}\n\nconst clampTone = (n: number): SkinToneValue => (Number.isInteger(n) && n >= 0 && n <= SKIN_TONES.length ? (n as SkinToneValue) : 0);\n\nfunction resolve(source: EmojiDataSource, signal: AbortSignal): Promise<Dataset | EmojiDataInput> {\n if (typeof source === \"function\") return source({ signal });\n if (typeof source !== \"string\") return Promise.resolve(source);\n return fetchJson<Dataset | EmojiDataInput>(source, \"emoji data\", signal);\n}\n\nfunction idle(fn: () => void) {\n if (typeof requestIdleCallback === \"function\") requestIdleCallback(fn, { timeout: 2000 });\n else setTimeout(fn, 1);\n}\n"],"mappings":"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","names":["onSkinToneChange","onFrequentsChange","resolve","tone"]}
@@ -87,12 +87,12 @@ function createEmojiSuggestions(store, options = {}) {
87
87
  opts().onDismiss?.();
88
88
  },
89
89
  keydown(event) {
90
- if (!snapshot().open || isComposingKey(event)) return false;
90
+ if (!snapshot().open || isComposingKey(event) || event.metaKey || event.ctrlKey || event.altKey || event.shiftKey && event.key !== "Escape") return false;
91
91
  const move = KEY_MOVES[event.key];
92
92
  const direction = move === "up" || move === "down" || move === "left" || move === "right" ? move : void 0;
93
93
  if (direction) {
94
94
  if (!self.move(direction)) return false;
95
- } else if (event.key === "Enter" && !event.shiftKey || event.key === "Tab") self.choose();
95
+ } else if (event.key === "Enter" || event.key === "Tab") self.choose();
96
96
  else if (event.key === "Escape") self.dismiss();
97
97
  else return false;
98
98
  event.preventDefault();
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/suggestions.ts"],"sourcesContent":["import { unifiedToNative, type EmojiRecord } from \"./dataset\";\nimport { KEY_MOVES } from \"./keys\";\nimport { createStore, memo, shallowEqual, stable, subscribeAll, type Subscribe } from \"./reactive\";\nimport { createSearch } from \"./search/query\";\nimport type { CompletionOrder, ShortcodeCompletion } from \"./search/search-index\";\nimport { isComposingKey } from \"./shortcodes\";\nimport type { EmojiSelection, EmojiStore } from \"./store\";\nimport { syncAriaAttributes } from \"./sync-attributes\";\n\nexport interface KeyLike {\n key: string;\n shiftKey?: boolean;\n isComposing?: boolean;\n keyCode?: number;\n preventDefault(): void;\n}\n\nexport interface EmojiSuggestionsOptions {\n query?: string;\n resetKey?: string;\n id?: string;\n limit?: number;\n matchBy?: \"shortcode\" | \"search\";\n order?: CompletionOrder;\n columns?: number;\n onChoose?: (suggestion: ShortcodeCompletion) => void;\n onSelect?: (selection: EmojiSelection) => void;\n onDismiss?: () => void;\n}\n\nexport interface EmojiSuggestionsSnapshot {\n query: string;\n open: boolean;\n suggestions: readonly ShortcodeCompletion[];\n highlightedIndex: number;\n activeDescendant: string | undefined;\n}\n\nexport type SuggestionsMove = \"up\" | \"down\" | \"left\" | \"right\";\n\nexport interface EmojiTrigger {\n query: string;\n anchor: () => DOMRect | null;\n replace: (text: string, suggestion: ShortcodeCompletion) => void;\n}\n\nexport type SuggestionsInputProps = {\n role: \"combobox\";\n \"aria-autocomplete\": \"list\";\n \"aria-expanded\": boolean;\n \"aria-controls\": string | undefined;\n \"aria-activedescendant\": string | undefined;\n};\n\nexport type SuggestionsListProps = {\n id: string;\n role: \"listbox\";\n};\n\nexport type SuggestionsOptionProps = {\n id: string;\n role: \"option\";\n \"aria-selected\": boolean;\n \"data-highlighted\": \"\" | undefined;\n};\n\nexport interface EmojiSuggestionsStore {\n getSnapshot(): EmojiSuggestionsSnapshot;\n subscribe: Subscribe;\n setOptions(options: EmojiSuggestionsOptions): void;\n setQuery(query: string, resetKey?: string): void;\n highlight(index: number): void;\n move(direction: SuggestionsMove): boolean;\n choose(index?: number): void;\n // For a selection another UI made and already recorded, such as a picker in the menu\n insert(selection: EmojiSelection): void;\n dismiss(): void;\n keydown(event: KeyLike): boolean;\n textFor(suggestion: ShortcodeCompletion, insert?: \"emoji\" | \"shortcode\"): string;\n getListProps(): SuggestionsListProps;\n getOptionProps(index: number): SuggestionsOptionProps;\n getInputProps(): SuggestionsInputProps;\n // For an editor element the app doesn't render: writes getInputProps() while open and restores the element after\n syncCombobox(el: Element): () => void;\n}\n\nconst NONE: readonly ShortcodeCompletion[] = [];\nconst DEFAULT_LIMIT = 8;\n\ntype Input = { options: EmojiSuggestionsOptions; resetKey: string; generation: number };\ntype Ui = { highlight: { index: number; generation: number }; dismissed: string | null };\ntype UiAction = { type: \"highlight\"; index: number; generation: number } | { type: \"dismiss\"; key: string };\n\nconst resetKeyOf = (options: EmojiSuggestionsOptions) => options.resetKey ?? options.query ?? \"\";\n\nconst reduceInput = (input: Input, options: EmojiSuggestionsOptions): Input => {\n const resetKey = resetKeyOf(options);\n if (resetKey !== input.resetKey) return { options, resetKey, generation: input.generation + 1 };\n return shallowEqual(input.options, options) ? input : { ...input, options };\n};\n\nconst reduceUi = (ui: Ui, action: UiAction): Ui => {\n switch (action.type) {\n case \"highlight\":\n return { ...ui, highlight: { index: action.index, generation: action.generation } };\n case \"dismiss\":\n return ui.dismissed === action.key ? ui : { ...ui, dismissed: action.key };\n default:\n return action satisfies never;\n }\n};\n\nconst completionOf = (record: EmojiRecord): ShortcodeCompletion => ({ record, shortcode: record.shortcodes[0] ?? record.name.replace(/\\s+/g, \"_\"), match: null });\n\nconst highlightOf = (input: Input, ui: Ui) => (ui.highlight.generation === input.generation ? ui.highlight.index : 0);\n\nexport function createEmojiSuggestions(store: EmojiStore, options: EmojiSuggestionsOptions = {}): EmojiSuggestionsStore {\n const input = createStore<Input, EmojiSuggestionsOptions>({ options, resetKey: resetKeyOf(options), generation: 0 }, reduceInput);\n const ui = createStore<Ui, UiAction>({ highlight: { index: 0, generation: 0 }, dismissed: null }, reduceUi);\n const searchOf = ({ query = \"\", limit = DEFAULT_LIMIT, matchBy }: EmojiSuggestionsOptions) => ({ query: matchBy === \"search\" ? query : \"\", limit });\n const search = createSearch(store, searchOf(options));\n const opts = () => input.getSnapshot().options;\n const id = (suffix: string) => `${opts().id ?? \"emoji-suggestions\"}-${suffix}`;\n const update = (next: EmojiSuggestionsOptions) => {\n const { query, limit } = searchOf(next);\n search.setQuery(query, limit);\n input.dispatch(next);\n };\n const suggestionsOf = memo((status: string, q: string, n: number, matchBy: string | undefined, order: CompletionOrder | undefined, searched: readonly EmojiRecord[]) =>\n status !== \"ready\" || !q ? NONE : matchBy === \"search\" ? searched.map(completionOf) : store.completeShortcode(q, n, order),\n );\n const keep = stable<EmojiSuggestionsSnapshot>();\n const getSnapshot = memo((current: Input, state: Ui, status: string, searched: readonly EmojiRecord[]): EmojiSuggestionsSnapshot => {\n const { query: q = \"\", limit = DEFAULT_LIMIT, matchBy, order } = current.options;\n const suggestions = suggestionsOf(status, q, limit, matchBy, order, searched);\n const index = Math.min(highlightOf(current, state), Math.max(suggestions.length - 1, 0));\n const open = suggestions.length > 0 && state.dismissed !== current.resetKey;\n return keep({ query: q, open, suggestions, highlightedIndex: index, activeDescendant: open ? id(`option-${index}`) : undefined });\n });\n const snapshot = () => getSnapshot(input.getSnapshot(), ui.getSnapshot(), store.status(), search.getSnapshot().results);\n\n const highlight = (index: number) => {\n const current = input.getSnapshot();\n if (index === highlightOf(current, ui.getSnapshot())) return;\n ui.dispatch({ type: \"highlight\", index, generation: current.generation });\n };\n\n const self: EmojiSuggestionsStore = {\n getSnapshot: snapshot,\n subscribe: subscribeAll(input.subscribe, ui.subscribe, store.subscribe, search.subscribe),\n setOptions: update,\n setQuery: (q, key = q) => update({ ...opts(), query: q, resetKey: key }),\n highlight,\n move(direction) {\n const { open, suggestions, highlightedIndex: i } = snapshot();\n const n = suggestions.length;\n const columns = opts().columns;\n const step = columns ? { left: -1, right: 1, up: -columns, down: columns }[direction] : { up: -1, down: 1, left: 0, right: 0 }[direction];\n if (!open || !step) return false;\n highlight(columns ? Math.max(0, Math.min(n - 1, i + step)) : (i + step + n) % n);\n return true;\n },\n choose(index = snapshot().highlightedIndex) {\n const suggestion = snapshot().suggestions[index];\n if (!suggestion) return;\n const selection = store.select(store.withSkinTone(suggestion.record.unified));\n opts().onChoose?.(suggestion);\n if (selection) opts().onSelect?.(selection);\n },\n insert(selection) {\n const record = store.get(selection.baseUnified);\n if (!record) return;\n opts().onChoose?.(completionOf(record));\n opts().onSelect?.(selection);\n },\n dismiss() {\n ui.dispatch({ type: \"dismiss\", key: input.getSnapshot().resetKey });\n opts().onDismiss?.();\n },\n keydown(event) {\n if (!snapshot().open || isComposingKey(event)) return false;\n const move = KEY_MOVES[event.key];\n const direction = move === \"up\" || move === \"down\" || move === \"left\" || move === \"right\" ? move : undefined;\n if (direction) {\n if (!self.move(direction)) return false;\n } else if ((event.key === \"Enter\" && !event.shiftKey) || event.key === \"Tab\") self.choose();\n else if (event.key === \"Escape\") self.dismiss();\n else return false;\n event.preventDefault();\n return true;\n },\n textFor: ({ record, shortcode }, insert = \"emoji\") => (record.imageUrl || insert === \"shortcode\" ? `:${shortcode}:` : unifiedToNative(store.withSkinTone(record.unified))),\n getListProps: () => ({ id: id(\"listbox\"), role: \"listbox\" }),\n getOptionProps(index) {\n const selected = index === snapshot().highlightedIndex;\n return { id: id(`option-${index}`), role: \"option\", \"aria-selected\": selected, \"data-highlighted\": selected ? \"\" : undefined };\n },\n getInputProps() {\n const { open, activeDescendant } = snapshot();\n return { role: \"combobox\", \"aria-autocomplete\": \"list\", \"aria-expanded\": open, \"aria-controls\": open ? id(\"listbox\") : undefined, \"aria-activedescendant\": activeDescendant };\n },\n syncCombobox: (el) => syncAriaAttributes(el, () => (snapshot().open ? self.getInputProps() : null), self.subscribe),\n };\n return self;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/suggestions.ts"],"sourcesContent":["import { unifiedToNative, type EmojiRecord } from \"./dataset\";\nimport { KEY_MOVES } from \"./keys\";\nimport { createStore, memo, shallowEqual, stable, subscribeAll, type Subscribe } from \"./reactive\";\nimport { createSearch } from \"./search/query\";\nimport type { CompletionOrder, ShortcodeCompletion } from \"./search/search-index\";\nimport { isComposingKey } from \"./shortcodes\";\nimport type { EmojiSelection, EmojiStore } from \"./store\";\nimport { syncAriaAttributes } from \"./sync-attributes\";\n\nexport interface KeyLike {\n key: string;\n shiftKey?: boolean;\n metaKey?: boolean;\n ctrlKey?: boolean;\n altKey?: boolean;\n isComposing?: boolean;\n keyCode?: number;\n preventDefault(): void;\n}\n\nexport interface EmojiSuggestionsOptions {\n query?: string;\n resetKey?: string;\n id?: string;\n limit?: number;\n matchBy?: \"shortcode\" | \"search\";\n order?: CompletionOrder;\n columns?: number;\n onChoose?: (suggestion: ShortcodeCompletion) => void;\n onSelect?: (selection: EmojiSelection) => void;\n onDismiss?: () => void;\n}\n\nexport interface EmojiSuggestionsSnapshot {\n query: string;\n open: boolean;\n suggestions: readonly ShortcodeCompletion[];\n highlightedIndex: number;\n activeDescendant: string | undefined;\n}\n\nexport type SuggestionsMove = \"up\" | \"down\" | \"left\" | \"right\";\n\nexport interface EmojiTrigger {\n query: string;\n anchor: () => DOMRect | null;\n replace: (text: string, suggestion: ShortcodeCompletion) => void;\n}\n\nexport type SuggestionsInputProps = {\n role: \"combobox\";\n \"aria-autocomplete\": \"list\";\n \"aria-expanded\": boolean;\n \"aria-controls\": string | undefined;\n \"aria-activedescendant\": string | undefined;\n};\n\nexport type SuggestionsListProps = {\n id: string;\n role: \"listbox\";\n};\n\nexport type SuggestionsOptionProps = {\n id: string;\n role: \"option\";\n \"aria-selected\": boolean;\n \"data-highlighted\": \"\" | undefined;\n};\n\nexport interface EmojiSuggestionsStore {\n getSnapshot(): EmojiSuggestionsSnapshot;\n subscribe: Subscribe;\n setOptions(options: EmojiSuggestionsOptions): void;\n setQuery(query: string, resetKey?: string): void;\n highlight(index: number): void;\n move(direction: SuggestionsMove): boolean;\n choose(index?: number): void;\n // For a selection another UI made and already recorded, such as a picker in the menu\n insert(selection: EmojiSelection): void;\n dismiss(): void;\n keydown(event: KeyLike): boolean;\n textFor(suggestion: ShortcodeCompletion, insert?: \"emoji\" | \"shortcode\"): string;\n getListProps(): SuggestionsListProps;\n getOptionProps(index: number): SuggestionsOptionProps;\n getInputProps(): SuggestionsInputProps;\n // For an editor element the app doesn't render: writes getInputProps() while open and restores the element after\n syncCombobox(el: Element): () => void;\n}\n\nconst NONE: readonly ShortcodeCompletion[] = [];\nconst DEFAULT_LIMIT = 8;\n\ntype Input = { options: EmojiSuggestionsOptions; resetKey: string; generation: number };\ntype Ui = { highlight: { index: number; generation: number }; dismissed: string | null };\ntype UiAction = { type: \"highlight\"; index: number; generation: number } | { type: \"dismiss\"; key: string };\n\nconst resetKeyOf = (options: EmojiSuggestionsOptions) => options.resetKey ?? options.query ?? \"\";\n\nconst reduceInput = (input: Input, options: EmojiSuggestionsOptions): Input => {\n const resetKey = resetKeyOf(options);\n if (resetKey !== input.resetKey) return { options, resetKey, generation: input.generation + 1 };\n return shallowEqual(input.options, options) ? input : { ...input, options };\n};\n\nconst reduceUi = (ui: Ui, action: UiAction): Ui => {\n switch (action.type) {\n case \"highlight\":\n return { ...ui, highlight: { index: action.index, generation: action.generation } };\n case \"dismiss\":\n return ui.dismissed === action.key ? ui : { ...ui, dismissed: action.key };\n default:\n return action satisfies never;\n }\n};\n\nconst completionOf = (record: EmojiRecord): ShortcodeCompletion => ({ record, shortcode: record.shortcodes[0] ?? record.name.replace(/\\s+/g, \"_\"), match: null });\n\nconst highlightOf = (input: Input, ui: Ui) => (ui.highlight.generation === input.generation ? ui.highlight.index : 0);\n\nexport function createEmojiSuggestions(store: EmojiStore, options: EmojiSuggestionsOptions = {}): EmojiSuggestionsStore {\n const input = createStore<Input, EmojiSuggestionsOptions>({ options, resetKey: resetKeyOf(options), generation: 0 }, reduceInput);\n const ui = createStore<Ui, UiAction>({ highlight: { index: 0, generation: 0 }, dismissed: null }, reduceUi);\n const searchOf = ({ query = \"\", limit = DEFAULT_LIMIT, matchBy }: EmojiSuggestionsOptions) => ({ query: matchBy === \"search\" ? query : \"\", limit });\n const search = createSearch(store, searchOf(options));\n const opts = () => input.getSnapshot().options;\n const id = (suffix: string) => `${opts().id ?? \"emoji-suggestions\"}-${suffix}`;\n const update = (next: EmojiSuggestionsOptions) => {\n const { query, limit } = searchOf(next);\n search.setQuery(query, limit);\n input.dispatch(next);\n };\n const suggestionsOf = memo((status: string, q: string, n: number, matchBy: string | undefined, order: CompletionOrder | undefined, searched: readonly EmojiRecord[]) =>\n status !== \"ready\" || !q ? NONE : matchBy === \"search\" ? searched.map(completionOf) : store.completeShortcode(q, n, order),\n );\n const keep = stable<EmojiSuggestionsSnapshot>();\n const getSnapshot = memo((current: Input, state: Ui, status: string, searched: readonly EmojiRecord[]): EmojiSuggestionsSnapshot => {\n const { query: q = \"\", limit = DEFAULT_LIMIT, matchBy, order } = current.options;\n const suggestions = suggestionsOf(status, q, limit, matchBy, order, searched);\n const index = Math.min(highlightOf(current, state), Math.max(suggestions.length - 1, 0));\n const open = suggestions.length > 0 && state.dismissed !== current.resetKey;\n return keep({ query: q, open, suggestions, highlightedIndex: index, activeDescendant: open ? id(`option-${index}`) : undefined });\n });\n const snapshot = () => getSnapshot(input.getSnapshot(), ui.getSnapshot(), store.status(), search.getSnapshot().results);\n\n const highlight = (index: number) => {\n const current = input.getSnapshot();\n if (index === highlightOf(current, ui.getSnapshot())) return;\n ui.dispatch({ type: \"highlight\", index, generation: current.generation });\n };\n\n const self: EmojiSuggestionsStore = {\n getSnapshot: snapshot,\n subscribe: subscribeAll(input.subscribe, ui.subscribe, store.subscribe, search.subscribe),\n setOptions: update,\n setQuery: (q, key = q) => update({ ...opts(), query: q, resetKey: key }),\n highlight,\n move(direction) {\n const { open, suggestions, highlightedIndex: i } = snapshot();\n const n = suggestions.length;\n const columns = opts().columns;\n const step = columns ? { left: -1, right: 1, up: -columns, down: columns }[direction] : { up: -1, down: 1, left: 0, right: 0 }[direction];\n if (!open || !step) return false;\n highlight(columns ? Math.max(0, Math.min(n - 1, i + step)) : (i + step + n) % n);\n return true;\n },\n choose(index = snapshot().highlightedIndex) {\n const suggestion = snapshot().suggestions[index];\n if (!suggestion) return;\n const selection = store.select(store.withSkinTone(suggestion.record.unified));\n opts().onChoose?.(suggestion);\n if (selection) opts().onSelect?.(selection);\n },\n insert(selection) {\n const record = store.get(selection.baseUnified);\n if (!record) return;\n opts().onChoose?.(completionOf(record));\n opts().onSelect?.(selection);\n },\n dismiss() {\n ui.dispatch({ type: \"dismiss\", key: input.getSnapshot().resetKey });\n opts().onDismiss?.();\n },\n keydown(event) {\n // Modified keys, such as Cmd+Enter to send or Shift+ArrowDown to select, belong to the field\n if (!snapshot().open || isComposingKey(event) || event.metaKey || event.ctrlKey || event.altKey || (event.shiftKey && event.key !== \"Escape\")) return false;\n const move = KEY_MOVES[event.key];\n const direction = move === \"up\" || move === \"down\" || move === \"left\" || move === \"right\" ? move : undefined;\n if (direction) {\n if (!self.move(direction)) return false;\n } else if (event.key === \"Enter\" || event.key === \"Tab\") self.choose();\n else if (event.key === \"Escape\") self.dismiss();\n else return false;\n event.preventDefault();\n return true;\n },\n textFor: ({ record, shortcode }, insert = \"emoji\") => (record.imageUrl || insert === \"shortcode\" ? `:${shortcode}:` : unifiedToNative(store.withSkinTone(record.unified))),\n getListProps: () => ({ id: id(\"listbox\"), role: \"listbox\" }),\n getOptionProps(index) {\n const selected = index === snapshot().highlightedIndex;\n return { id: id(`option-${index}`), role: \"option\", \"aria-selected\": selected, \"data-highlighted\": selected ? \"\" : undefined };\n },\n getInputProps() {\n const { open, activeDescendant } = snapshot();\n return { role: \"combobox\", \"aria-autocomplete\": \"list\", \"aria-expanded\": open, \"aria-controls\": open ? id(\"listbox\") : undefined, \"aria-activedescendant\": activeDescendant };\n },\n syncCombobox: (el) => syncAriaAttributes(el, () => (snapshot().open ? self.getInputProps() : null), self.subscribe),\n };\n return self;\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@emoji-components/core",
3
- "version": "1.0.0-alpha.1",
3
+ "version": "1.0.0-alpha.2",
4
4
  "description": "Framework-neutral emoji data, search, frequently used emoji, skin tones, picker and typeahead state.",
5
5
  "keywords": [
6
6
  "emoji",
@@ -75,7 +75,7 @@
75
75
  }
76
76
  },
77
77
  "devDependencies": {
78
- "@emoji-components/twemoji-font": "^1.0.0-alpha.1",
78
+ "@emoji-components/twemoji-font": "^1.0.0-alpha.2",
79
79
  "@types/node": "^22.20.5",
80
80
  "emojibase": "^18.0.0",
81
81
  "emojibase-data": "^18.0.0",