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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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 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"]}
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 { localeData } 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 ?? []));\n// Without data, the locale's dataset\nconst dataOf = ({ data, locale }: Source) => data ?? localeData(locale ?? \"en\");\nconst sameData = (a: Source, b: Source) => dataOf(a) === dataOf(b) || (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(dataOf(config), 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, (u) => frequents.score(u)) : []),\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"]}
@@ -2,7 +2,7 @@
2
2
  import { unifiedToNative } from "./dataset.js";
3
3
  import { KEY_MOVES } from "./keys.js";
4
4
  import { createStore, memo, shallowEqual, stable, subscribeAll } from "./reactive.js";
5
- import { createSearch } from "./search/query.js";
5
+ import { createEmojiSearch } from "./search/query.js";
6
6
  import { isComposingKey } from "./shortcodes.js";
7
7
  import { syncAriaAttributes } from "./sync-attributes.js";
8
8
  var NONE = [];
@@ -29,7 +29,7 @@ function createEmojiSuggestions(store, options = {}) {
29
29
  const input = createStore({ options, resetKey: resetKeyOf(options), generation: 0 }, reduceInput);
30
30
  const ui = createStore({ highlight: { index: 0, generation: 0 }, dismissed: null }, reduceUi);
31
31
  const searchOf = ({ query = "", limit = DEFAULT_LIMIT, matchBy }) => ({ query: matchBy === "search" ? query : "", limit });
32
- const search = createSearch(store, searchOf(options));
32
+ const search = createEmojiSearch(store, searchOf(options));
33
33
  const opts = () => input.getSnapshot().options;
34
34
  const id = (suffix) => `${opts().id ?? "emoji-suggestions"}-${suffix}`;
35
35
  const update = (next) => {
@@ -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 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":[]}
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 { createEmojiSearch } 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 = createEmojiSearch(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,7 +1,7 @@
1
1
  {
2
2
  "name": "@emoji-components/core",
3
- "version": "1.0.0-alpha.2",
4
- "description": "Framework-neutral emoji data, search, frequently used emoji, skin tones, picker and typeahead state.",
3
+ "version": "1.0.0-alpha.3",
4
+ "description": "Framework-neutral emoji data, search, frequently used emoji, skin tones, picker and suggestions state.",
5
5
  "keywords": [
6
6
  "emoji",
7
7
  "emoji-picker",
@@ -75,10 +75,11 @@
75
75
  }
76
76
  },
77
77
  "devDependencies": {
78
- "@emoji-components/twemoji-font": "^1.0.0-alpha.2",
78
+ "@emoji-components/twemoji-font": "^1.0.0-alpha.3",
79
79
  "@types/node": "^22.20.5",
80
80
  "emojibase": "^18.0.0",
81
81
  "emojibase-data": "^18.0.0",
82
+ "fast-check": "^4.10.2",
82
83
  "jsdom": "^26.1.0",
83
84
  "tsup": "^9.0.0",
84
85
  "tsx": "^4.23.15",