@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/README.md +4 -4
- package/dist/build.js +1 -1
- package/dist/{chunk-UG4NPOXS.js → chunk-MJPQEHGK.js} +5 -2
- package/dist/cli.js +1 -1
- package/dist/frequents.js +1 -2
- package/dist/frequents.js.map +1 -1
- package/dist/index.d.ts +20 -8
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +2 -2
- package/dist/labels.js +3 -2
- package/dist/labels.js.map +1 -1
- package/dist/picker/picker.js +57 -19
- package/dist/picker/picker.js.map +1 -1
- package/dist/picker/sections.js +4 -4
- package/dist/picker/sections.js.map +1 -1
- package/dist/platform.js +20 -3
- package/dist/platform.js.map +1 -1
- package/dist/search/query.js +2 -2
- package/dist/search/query.js.map +1 -1
- package/dist/search/search-index.js +3 -3
- package/dist/search/search-index.js.map +1 -1
- package/dist/search/tokenize.js +4 -1
- package/dist/search/tokenize.js.map +1 -1
- package/dist/{sections-Bwe4sMgE.d.ts → sections-D7PEWAVf.d.ts} +3 -2
- package/dist/store.js +5 -4
- package/dist/store.js.map +1 -1
- package/dist/suggestions.js +2 -2
- package/dist/suggestions.js.map +1 -1
- package/package.json +4 -3
|
@@ -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 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"]}
|
|
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 { createEmojiSearch, 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\">;\nexport type ValueChangeDetails = ChangeDetails<\"item-press\" | \"imperative-action\">;\nexport type ReactionsOpenChangeDetails = ChangeDetails<\"trigger-press\" | \"item-press\" | \"escape-key\" | \"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 | 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 // Passing only onValueChange tracks the value too, as in Base UI's Select\n value?: string | null;\n defaultValue?: string | null;\n onValueChange?: (unified: string | null, details: ValueChangeDetails) => void;\n onSelect?: (selection: EmojiSelection, details: ValueChangeDetails) => void;\n disableVariations?: boolean;\n reactions?: readonly string[] | number;\n reactionsOpen?: boolean;\n defaultReactionsOpen?: boolean;\n onReactionsOpenChange?: (open: boolean, details: ReactionsOpenChangeDetails) => void;\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 reactionsOpen: boolean;\n // undefined while the picker doesn't track a value\n value: string | null | 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 // choose, then onSelect, the value, and the reaction row, which reopens after a pick from the full picker\n pick(id?: string | null, source?: ChangeSource<ValueChangeDetails>): EmojiSelection | undefined;\n // Opening clears the query, as a closed popover's search resets\n setReactionsOpen(open: boolean, source?: ChangeSource<ReactionsOpenChangeDetails>): ReactionsOpenChangeDetails | 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 frequent 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 = { frequent: 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 value: string | null;\n reactionsOpen: boolean;\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 value: options.defaultValue ?? null,\n reactionsOpen: options.defaultReactionsOpen ?? true,\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 = createEmojiSearch(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 const tracks = (current: EmojiPickerOptions<Icon>) => options.defaultValue !== undefined || current.value !== undefined || current.onValueChange !== undefined;\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 frequent: readonly string[],\n collapsed: readonly string[],\n emojiVersion: number | undefined,\n tone: SkinToneValue,\n item: ItemFactory,\n ) => (ready ? categorySections({ categories, records, frequent, 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 = () => {\n const { frequent = DEFAULT_FREQUENT } = opts();\n return typeof frequent === \"number\" ? frequent || 1 : 1;\n };\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 shownFrequent = Array.isArray(current.frequent) ? current.frequent : current.frequent === false ? NO_IDS : frequent;\n const collapsed = collapsedOf(state, current);\n const categorySections = sectionsOf(records, customGroups, ready, categories, shownFrequent, 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 const reactions = state.held && typeof current.reactions === \"number\" ? state.held.reactions : reactionsOf(current.reactions);\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,\n // An empty row, such as frequently used before any pick, shows the picker instead\n reactionsOpen: Boolean(reactions?.length) && (current.reactionsOpen ?? state.reactionsOpen),\n value: tracks(current) ? (current.value === undefined ? state.value : current.value) : undefined,\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?.frequent ?? 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 value = controllable<string | null, ValueChangeDetails>({\n controlled: () => opts().value,\n own: () => ui.getSnapshot().value,\n onChange: () => opts().onValueChange,\n write: (unified) => set({ value: unified }),\n });\n\n const reactionsOpen = controllable<boolean, ReactionsOpenChangeDetails>({\n controlled: () => opts().reactionsOpen,\n own: () => ui.getSnapshot().reactionsOpen,\n onChange: () => opts().onReactionsOpenChange,\n write: (open) => set({ reactionsOpen: open }),\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 const self: EmojiPickerStore<Icon> = {\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 pick(id, { reason = \"item-press\", event } = {}) {\n const selection = self.choose(id);\n if (!selection) return undefined;\n const details = createChangeDetails(reason, event);\n opts().onSelect?.(selection, details);\n if (details.isCanceled) return selection;\n if (tracks(opts())) value.set(selection.unified, createChangeDetails(reason, event));\n if (opts().reactions && !reactionsOpen.value()) self.setReactionsOpen(true, { reason: \"item-press\", event });\n return selection;\n },\n setReactionsOpen(open, { reason = \"none\", event } = {}) {\n const details = reactionsOpen.set(open, createChangeDetails(reason, event));\n if (open && details && !details.isCanceled) self.setQuery(\"\");\n return details;\n },\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 ? { frequent: store.frequent(frequentCount()), reactions: reactionsOf(opts().reactions) } : null });\n },\n };\n return self;\n}\n"],"mappings":"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","names":["query","collapsed","categorySections","highlighted","value"]}
|
package/dist/picker/sections.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
import { GROUPS } from "../dataset.js";
|
|
3
3
|
import { cached } from "../reactive.js";
|
|
4
4
|
import { nameWithTone } from "../shortcodes.js";
|
|
5
|
-
var DEFAULT_ORDER = ["
|
|
5
|
+
var DEFAULT_ORDER = ["frequent", "custom", ...GROUPS];
|
|
6
6
|
var RESULT_ORDER = [...GROUPS, "custom"];
|
|
7
7
|
var groupName = (groupNames, id) => groupNames[GROUPS.indexOf(id)] ?? id;
|
|
8
8
|
function categoryList(options, groupNames, labels, customGroups) {
|
|
9
|
-
const name = (id) => id === "
|
|
9
|
+
const name = (id) => id === "frequent" ? labels.frequent : id === "custom" ? labels.custom : id.startsWith("custom:") ? id.slice(7) : groupName(groupNames, id);
|
|
10
10
|
const chosen = options?.length ? options : DEFAULT_ORDER.map((id) => ({ id }));
|
|
11
11
|
const listed = chosen.map((o) => ({ id: o.id, name: o.name ?? name(o.id), icon: o.icon, nav: o.nav !== false }));
|
|
12
12
|
const ids = new Set(listed.map((c) => c.id));
|
|
@@ -32,14 +32,14 @@ var groupBy = (values, keyOf) => {
|
|
|
32
32
|
};
|
|
33
33
|
var toned = (record, tone, unified = record.unified) => tone && record.tones[tone - 1] || unified;
|
|
34
34
|
var shownIn = (emojiVersion) => (r) => emojiVersion == null || Boolean(r.imageUrl) || r.version <= emojiVersion;
|
|
35
|
-
function categorySections({ categories, records,
|
|
35
|
+
function categorySections({ categories, records, frequent, customGroup, collapsed, emojiVersion, get, tone, item }) {
|
|
36
36
|
const shown = shownIn(emojiVersion);
|
|
37
37
|
const byGroup = groupBy(records.filter(shown), (r) => {
|
|
38
38
|
if (r.group !== "custom") return r.group;
|
|
39
39
|
const group = customGroup(r.unified);
|
|
40
40
|
return group ? `custom:${group}` : "custom";
|
|
41
41
|
});
|
|
42
|
-
const itemsOf = (id) => id === "
|
|
42
|
+
const itemsOf = (id) => id === "frequent" ? frequent.flatMap((unified) => {
|
|
43
43
|
const record = get(unified);
|
|
44
44
|
return record && shown(record) ? [item(id, record, toned(record, tone, unified))] : [];
|
|
45
45
|
}) : (byGroup.get(id) ?? []).map((r) => item(id, r, toned(r, tone)));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/picker/sections.ts"],"sourcesContent":["import { GROUPS, type EmojiRecord, type Group, type SkinToneValue } from \"../dataset\";\nimport type { EmojiLabels } from \"../labels\";\nimport { cached } from \"../reactive\";\nimport { nameWithTone } from \"../shortcodes\";\n\nexport type CategoryId = Group | \"
|
|
1
|
+
{"version":3,"sources":["../../src/picker/sections.ts"],"sourcesContent":["import { GROUPS, type EmojiRecord, type Group, type SkinToneValue } from \"../dataset\";\nimport type { EmojiLabels } from \"../labels\";\nimport { cached } from \"../reactive\";\nimport { nameWithTone } from \"../shortcodes\";\n\nexport type CategoryId = Group | \"frequent\" | \"custom\" | `custom:${string}`;\n\nexport interface CategoryOption<Icon = unknown> {\n id: CategoryId;\n name?: string;\n icon?: Icon;\n nav?: boolean;\n}\n\nexport interface Category<Icon = unknown> {\n id: string;\n name: string;\n icon?: Icon;\n nav: boolean;\n}\n\nexport interface EmojiItem {\n readonly id: string;\n readonly section: string;\n readonly record: EmojiRecord;\n readonly unified: string;\n readonly label: string;\n}\n\nexport interface EmojiSection {\n readonly id: string;\n readonly category: string;\n readonly name: string;\n readonly items: readonly EmojiItem[];\n readonly header: boolean;\n readonly collapsed: boolean;\n}\n\nexport type SearchResultsMode = \"ranked\" | \"grouped\";\n\nexport type ItemFactory = (section: string, record: EmojiRecord, unified: string) => EmojiItem;\n\nconst DEFAULT_ORDER: readonly CategoryId[] = [\"frequent\", \"custom\", ...GROUPS];\nconst RESULT_ORDER: readonly string[] = [...GROUPS, \"custom\"];\n\nexport const groupName = (groupNames: readonly string[], id: string) => groupNames[(GROUPS as readonly string[]).indexOf(id)] ?? id;\n\nexport function categoryList<Icon>(options: readonly CategoryOption<Icon>[] | undefined, groupNames: readonly string[], labels: EmojiLabels, customGroups: Iterable<string>): Category<Icon>[] {\n const name = (id: string) => (id === \"frequent\" ? labels.frequent : id === \"custom\" ? labels.custom : id.startsWith(\"custom:\") ? id.slice(7) : groupName(groupNames, id));\n const chosen: readonly CategoryOption<Icon>[] = options?.length ? options : DEFAULT_ORDER.map((id) => ({ id }));\n const listed = chosen.map((o): Category<Icon> => ({ id: o.id, name: o.name ?? name(o.id), icon: o.icon, nav: o.nav !== false }));\n const ids = new Set(listed.map((c) => c.id));\n const unlisted = [\"custom\", ...[...customGroups].map((g) => `custom:${g}`)].filter((id) => !ids.has(id) && ids.add(id));\n return [...listed, ...unlisted.map((id): Category<Icon> => ({ id, name: name(id), nav: true }))];\n}\n\nconst caches = new WeakMap<EmojiLabels, Map<string, EmojiItem>>();\n\n// Items keep their identity while their record does, so a collection can reuse them across renders\nexport function itemFactory(labels: EmojiLabels): ItemFactory {\n const cache = cached(caches, labels, () => new Map<string, EmojiItem>());\n return (section, record, unified) => {\n const id = `${section}:${unified}`;\n const hit = cache.get(id);\n if (hit?.record === record) return hit;\n const item: EmojiItem = Object.freeze({ id, section, record, unified, label: nameWithTone(record.name, unified, labels.withSkinTone) });\n cache.set(id, item);\n return item;\n };\n}\n\nconst groupBy = <T>(values: Iterable<T>, keyOf: (value: T) => string) => {\n const groups = new Map<string, T[]>();\n for (const value of values) cached(groups, keyOf(value), (): T[] => []).push(value);\n return groups;\n};\n\nexport interface CategorySectionsInput {\n categories: readonly Category<unknown>[];\n records: readonly EmojiRecord[];\n frequent: readonly string[];\n customGroup: (unified: string) => string | undefined;\n collapsed: readonly string[];\n emojiVersion?: number;\n get: (unified: string) => EmojiRecord | undefined;\n tone: SkinToneValue;\n item: ItemFactory;\n}\n\nexport const toned = (record: EmojiRecord, tone: SkinToneValue, unified = record.unified): string => (tone && record.tones[tone - 1]) || unified;\n\nexport const shownIn = (emojiVersion: number | undefined) => (r: EmojiRecord) => emojiVersion == null || Boolean(r.imageUrl) || r.version <= emojiVersion;\n\nexport function categorySections({ categories, records, frequent, customGroup, collapsed, emojiVersion, get, tone, item }: CategorySectionsInput): EmojiSection[] {\n const shown = shownIn(emojiVersion);\n const byGroup = groupBy(records.filter(shown), (r) => {\n if (r.group !== \"custom\") return r.group;\n const group = customGroup(r.unified);\n return group ? `custom:${group}` : \"custom\";\n });\n const itemsOf = (id: string): EmojiItem[] =>\n id === \"frequent\"\n ? frequent.flatMap((unified) => {\n const record = get(unified);\n return record && shown(record) ? [item(id, record, toned(record, tone, unified))] : [];\n })\n : (byGroup.get(id) ?? []).map((r) => item(id, r, toned(r, tone)));\n return categories.flatMap(({ id, name }) => {\n const items = itemsOf(id);\n if (!items.length) return [];\n const isCollapsed = collapsed.includes(id);\n return [{ id, category: id, name, items: isCollapsed ? [] : items, header: true, collapsed: isCollapsed }];\n });\n}\n\nexport function resultSections(results: readonly EmojiRecord[], mode: SearchResultsMode, name: (group: string) => string, tone: SkinToneValue, item: ItemFactory, label: string): EmojiSection[] {\n const toItem = (section: string, r: EmojiRecord) => item(section, r, toned(r, tone));\n if (mode === \"ranked\") return results.length ? [{ id: \"results\", category: \"results\", name: label, items: results.map((r) => toItem(\"results\", r)), header: false, collapsed: false }] : [];\n const groups = groupBy(results, (r) => r.group);\n return [...groups]\n .sort(([a], [b]) => RESULT_ORDER.indexOf(a) - RESULT_ORDER.indexOf(b))\n .map(([group, records]) => ({ id: `results:${group}`, category: group, name: name(group), items: records.map((r) => toItem(`results:${group}`, r)), header: true, collapsed: false }));\n}\n\nexport function setCategoryCollapsed(ids: readonly string[], id: string, collapsed = !ids.includes(id)): readonly string[] {\n if (ids.includes(id) === collapsed) return ids;\n return collapsed ? [...ids, id] : ids.filter((x) => x !== id);\n}\n\nexport const findItem = (sections: readonly EmojiSection[], id: string | null | undefined): EmojiItem | null => {\n for (const s of sections) for (const item of s.items) if (item.id === id) return item;\n return null;\n};\n"],"mappings":"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","names":[]}
|
package/dist/platform.js
CHANGED
|
@@ -6,17 +6,34 @@ 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
|
|
10
|
-
var
|
|
9
|
+
var DATA_LOCALES = ["bn", "da", "de", "en", "en-gb", "es", "es-mx", "et", "fi", "fr", "hi", "hu", "it", "ja", "ko", "lt", "ms", "nb", "nl", "pl", "pt", "ru", "sv", "th", "uk", "vi", "zh", "zh-hant"];
|
|
10
|
+
var ALIASES = { "es-419": "es-mx", no: "nb", nn: "nb" };
|
|
11
|
+
function dataLocale(locale) {
|
|
12
|
+
let tag;
|
|
13
|
+
try {
|
|
14
|
+
tag = new Intl.Locale(locale);
|
|
15
|
+
} catch {
|
|
16
|
+
return "en";
|
|
17
|
+
}
|
|
18
|
+
const known = (code) => DATA_LOCALES.includes(code);
|
|
19
|
+
const full = tag.toString().toLowerCase();
|
|
20
|
+
if (known(full)) return full;
|
|
21
|
+
const alias = ALIASES[`${tag.language}-${tag.region?.toLowerCase()}`] ?? ALIASES[tag.language];
|
|
22
|
+
if (alias) return alias;
|
|
23
|
+
if (tag.language === "zh") return tag.maximize().script === "Hant" ? "zh-hant" : "zh";
|
|
24
|
+
return known(tag.language) ? tag.language : "en";
|
|
25
|
+
}
|
|
26
|
+
var localeData = (locale, { shortcodes = "iamcal", baseUrl = `${DATA_CDN}@${true ? "1.0.0-alpha.3" : "latest"}/data` } = {}) => `${baseUrl}/${dataLocale(locale)}/${shortcodes}.json`;
|
|
11
27
|
var TWEMOJI_BASE_URL = `https://cdn.jsdelivr.net/gh/jdecked/twemoji@${config.twemoji}/assets/svg/`;
|
|
12
28
|
var TWEMOJI_EMOJI_VERSION = 17;
|
|
13
29
|
function twemojiUrl(unified, baseUrl = TWEMOJI_BASE_URL) {
|
|
14
30
|
return `${baseUrl}${unified.includes("200d") ? unified : withoutFe0f(unified)}.svg`;
|
|
15
31
|
}
|
|
16
32
|
export {
|
|
17
|
-
|
|
33
|
+
DATA_LOCALES,
|
|
18
34
|
TWEMOJI_BASE_URL,
|
|
19
35
|
TWEMOJI_EMOJI_VERSION,
|
|
36
|
+
dataLocale,
|
|
20
37
|
localeData,
|
|
21
38
|
twemojiUrl
|
|
22
39
|
};
|
package/dist/platform.js.map
CHANGED
|
@@ -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
|
+
{"version":3,"sources":["../../twemoji-font/package.json","../src/platform.ts"],"sourcesContent":["{\n \"name\": \"@emoji-components/twemoji-font\",\n \"version\": \"1.0.0-alpha.3\",\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\n// The folders of @emoji-components/data\nexport const DATA_LOCALES = [\"bn\", \"da\", \"de\", \"en\", \"en-gb\", \"es\", \"es-mx\", \"et\", \"fi\", \"fr\", \"hi\", \"hu\", \"it\", \"ja\", \"ko\", \"lt\", \"ms\", \"nb\", \"nl\", \"pl\", \"pt\", \"ru\", \"sv\", \"th\", \"uk\", \"vi\", \"zh\", \"zh-hant\"] as const;\n\nconst ALIASES: Record<string, string> = { \"es-419\": \"es-mx\", no: \"nb\", nn: \"nb\" };\n\nexport function dataLocale(locale: string): string {\n let tag: Intl.Locale;\n try {\n tag = new Intl.Locale(locale);\n } catch {\n return \"en\";\n }\n const known = (code: string) => (DATA_LOCALES as readonly string[]).includes(code);\n const full = tag.toString().toLowerCase();\n if (known(full)) return full;\n const alias = ALIASES[`${tag.language}-${tag.region?.toLowerCase()}`] ?? ALIASES[tag.language];\n if (alias) return alias;\n if (tag.language === \"zh\") return tag.maximize().script === \"Hant\" ? \"zh-hant\" : \"zh\";\n return known(tag.language) ? tag.language : \"en\";\n}\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}/${dataLocale(locale)}/${shortcodes}.json`;\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;AAKV,IAAM,eAAe,CAAC,MAAM,MAAM,MAAM,MAAM,SAAS,MAAM,SAAS,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,SAAS;AAE9M,IAAM,UAAkC,EAAE,UAAU,SAAS,IAAI,MAAM,IAAI,KAAK;AAEzE,SAAS,WAAW,QAAwB;AACjD,MAAI;AACJ,MAAI;AACF,UAAM,IAAI,KAAK,OAAO,MAAM;AAAA,EAC9B,QAAQ;AACN,WAAO;AAAA,EACT;AACA,QAAM,QAAQ,CAAC,SAAkB,aAAmC,SAAS,IAAI;AACjF,QAAM,OAAO,IAAI,SAAS,EAAE,YAAY;AACxC,MAAI,MAAM,IAAI,EAAG,QAAO;AACxB,QAAM,QAAQ,QAAQ,GAAG,IAAI,QAAQ,IAAI,IAAI,QAAQ,YAAY,CAAC,EAAE,KAAK,QAAQ,IAAI,QAAQ;AAC7F,MAAI,MAAO,QAAO;AAClB,MAAI,IAAI,aAAa,KAAM,QAAO,IAAI,SAAS,EAAE,WAAW,SAAS,YAAY;AACjF,SAAO,MAAM,IAAI,QAAQ,IAAI,IAAI,WAAW;AAC9C;AAEO,IAAM,aAAa,CAAC,QAAgB,EAAE,aAAa,UAAU,UAAU,GAAG,QAAQ,IAAI,OAAuC,kBAAmB,QAAQ,QAAQ,IAAqD,CAAC,MAC3N,GAAG,OAAO,IAAI,WAAW,MAAM,CAAC,IAAI,UAAU;AAEzC,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/search/query.js
CHANGED
|
@@ -14,7 +14,7 @@ var reduce = (run, action) => {
|
|
|
14
14
|
return action;
|
|
15
15
|
}
|
|
16
16
|
};
|
|
17
|
-
function
|
|
17
|
+
function createEmojiSearch(store, initial = { query: "", limit: 0 }) {
|
|
18
18
|
const run = createStore({ key: "", version: -1, state: IDLE, controller: null, settled: NONE }, reduce);
|
|
19
19
|
let input = initial;
|
|
20
20
|
let watchers = 0;
|
|
@@ -64,6 +64,6 @@ function createSearch(store, initial = { query: "", limit: 0 }) {
|
|
|
64
64
|
};
|
|
65
65
|
}
|
|
66
66
|
export {
|
|
67
|
-
|
|
67
|
+
createEmojiSearch
|
|
68
68
|
};
|
|
69
69
|
//# sourceMappingURL=query.js.map
|
package/dist/search/query.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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
|
|
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 EmojiSearchStore {\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 createEmojiSearch(store: EmojiStore, initial: EmojiSearchQuery = { query: \"\", limit: 0 }): EmojiSearchStore {\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":[]}
|
|
@@ -57,7 +57,7 @@ function createSearchIndex(records, { locale, tokenize = tokenizerFor(locale) }
|
|
|
57
57
|
for (let j = lowerBound(keys, word); j < keys.length; j++) {
|
|
58
58
|
const key = keys[j];
|
|
59
59
|
if (prefix ? !key.startsWith(word) : key !== word) break;
|
|
60
|
-
const score = kinds[j]
|
|
60
|
+
const score = kinds[j] + (key === word ? 0 : 3);
|
|
61
61
|
const id = ids[j];
|
|
62
62
|
const prev = next.get(id);
|
|
63
63
|
if (prev === void 0 || score < prev) next.set(id, score);
|
|
@@ -72,7 +72,7 @@ function createSearchIndex(records, { locale, tokenize = tokenizerFor(locale) }
|
|
|
72
72
|
const id = shortcodes.get(normalize(code).replace(/^:|:$/g, ""));
|
|
73
73
|
return id === void 0 ? void 0 : records[id];
|
|
74
74
|
},
|
|
75
|
-
completeShortcode(prefix, limit = 8, order = "relevance") {
|
|
75
|
+
completeShortcode(prefix, limit = 8, order = "relevance", frequency = () => 0) {
|
|
76
76
|
const p = normalize(prefix);
|
|
77
77
|
const best = /* @__PURE__ */ new Map();
|
|
78
78
|
const offer = (id, shortcode, score, match) => {
|
|
@@ -98,7 +98,7 @@ function createSearchIndex(records, { locale, tokenize = tokenizerFor(locale) }
|
|
|
98
98
|
const id = idOf.get(record);
|
|
99
99
|
if (record.shortcodes[0]) offer(id, normalize(record.shortcodes[0]), 3, null);
|
|
100
100
|
}
|
|
101
|
-
const relevance = (a, b) => a.score - b.score || a.shortcode.length - b.shortcode.length || a.record.order - b.record.order;
|
|
101
|
+
const relevance = (a, b) => a.score - b.score || frequency(b.record.unified) - frequency(a.record.unified) || a.shortcode.length - b.shortcode.length || a.record.order - b.record.order;
|
|
102
102
|
const tier = (score) => score <= 1 ? score : 2;
|
|
103
103
|
const alphabetical = (a, b) => tier(a.score) - tier(b.score) || collator.compare(a.shortcode, b.shortcode);
|
|
104
104
|
return [...best.values()].sort(order === "alphabetical" ? alphabetical : relevance).slice(0, limit).map(({ score: _score, ...rest }) => rest);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/search/search-index.ts"],"sourcesContent":["import type { EmojiRecord } from \"../dataset\";\nimport { memo } from \"../reactive\";\nimport { canonicalLocale, normalizer, tokenizerFor, type Tokenizer } from \"./tokenize\";\n\nconst SHORTCODE = 0;\nconst WORD = 1;\nconst TAG = 2;\n\nexport type SearchIndexOptions = {\n locale?: string;\n tokenize?: Tokenizer;\n};\n\nexport type SearchIndex = {\n tokenize: Tokenizer;\n search(query: string, limit?: number): EmojiRecord[];\n vocabulary(): ReadonlyMap<string, number>;\n byShortcode(code: string): EmojiRecord | undefined;\n completeShortcode(prefix: string, limit?: number, order?: CompletionOrder): ShortcodeCompletion[];\n};\n\nexport type CompletionOrder = \"relevance\" | \"alphabetical\";\n\nexport type ShortcodeCompletion = {\n record: EmojiRecord;\n shortcode: string;\n match: [number, number] | null;\n};\n\nexport function createSearchIndex(records: readonly EmojiRecord[], { locale, tokenize = tokenizerFor(locale) }: SearchIndexOptions = {}): SearchIndex {\n const normalize = normalizer(locale);\n const collator = new Intl.Collator(canonicalLocale(locale));\n const entries: [token: string, id: number, kind: number][] = [];\n const shortcodes = new Map<string, number>();\n const shortcodeList: [code: string, id: number][] = [];\n\n records.forEach((record, id) => {\n const best = new Map<string, number>();\n const add = (token: string, kind: number) => {\n const prev = best.get(token);\n if (prev === undefined || kind < prev) best.set(token, kind);\n };\n for (const code of record.shortcodes) {\n const lower = normalize(code);\n if (!shortcodes.has(lower)) shortcodes.set(lower, id);\n shortcodeList.push([lower, id]);\n add(lower, SHORTCODE);\n for (const t of tokenize(lower)) add(t, WORD);\n }\n for (const t of tokenize(record.name)) add(t, WORD);\n for (const tag of record.tags) for (const t of tokenize(tag)) add(t, TAG);\n for (const [token, kind] of best) entries.push([token, id, kind]);\n });\n\n entries.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : a[2] - b[2]));\n const keys = entries.map((e) => e[0]);\n const ids = Int32Array.from(entries, (e) => e[1]);\n const kinds = Uint8Array.from(entries, (e) => e[2]);\n shortcodeList.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : records[a[1]]!.order - records[b[1]]!.order));\n const shortcodeKeys = shortcodeList.map((s) => s[0]);\n const codeWords: [word: string, listIndex: number, offset: number][] = [];\n shortcodeList.forEach(([code], listIndex) => {\n let offset = 0;\n for (const part of code.split(\"_\")) {\n if (part) codeWords.push([part, listIndex, offset]);\n offset += part.length + 1;\n }\n });\n codeWords.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));\n const codeWordKeys = codeWords.map((w) => w[0]);\n\n const idOf = new Map(records.map((r, i) => [r, i] as const));\n const vocabulary = memo(() => keys.reduce((counts, key) => counts.set(key, (counts.get(key) ?? 0) + 1), new Map<string, number>()));\n const api: SearchIndex = {\n tokenize,\n\n vocabulary,\n\n search(query, limit = Infinity) {\n const words = tokenize(query);\n if (!words.length) return [];\n const matches = words.map((word, n) => {\n const prefix = n === words.length - 1;\n const next = new Map<number, number>();\n for (let j = lowerBound(keys, word); j < keys.length; j++) {\n const key = keys[j]!;\n if (prefix ? !key.startsWith(word) : key !== word) break;\n const score = kinds[j]! * 2 + (key === word ? 0 : 1);\n const id = ids[j]!;\n const prev = next.get(id);\n if (prev === undefined || score < prev) next.set(id, score);\n }\n return next;\n });\n const scores = matches.reduce((all, next) => new Map([...next].flatMap(([id, s]) => (all.has(id) ? [[id, all.get(id)! + s] as const] : []))));\n const ranked = [...scores].sort((a, b) => a[1] - b[1] || records[a[0]]!.order - records[b[0]]!.order);\n return ranked.slice(0, limit).map(([id]) => records[id]!);\n },\n\n byShortcode(code) {\n const id = shortcodes.get(normalize(code).replace(/^:|:$/g, \"\"));\n return id === undefined ? undefined : records[id];\n },\n\n completeShortcode(prefix, limit = 8, order = \"relevance\") {\n const p = normalize(prefix);\n const best = new Map<number, ShortcodeCompletion & { score: number }>();\n const offer = (id: number, shortcode: string, score: number, match: [number, number] | null) => {\n const prev = best.get(id);\n if (!prev || score < prev.score || (score === prev.score && shortcode.length < prev.shortcode.length)) {\n best.set(id, { record: records[id]!, shortcode, match, score });\n }\n };\n for (let j = lowerBound(codeWordKeys, p); j < codeWordKeys.length; j++) {\n if (!codeWordKeys[j]!.startsWith(p)) break;\n const [, listIndex, offset] = codeWords[j]!;\n const [code, id] = shortcodeList[listIndex]!;\n offer(id, code, code === p ? 0 : offset === 0 ? 1 : 2, [offset, offset + p.length]);\n }\n if (/[_-]/.test(p)) {\n for (let j = lowerBound(shortcodeKeys, p); j < shortcodeKeys.length && shortcodeKeys[j]!.startsWith(p); j++) {\n const [code, id] = shortcodeList[j]!;\n offer(id, code, code === p ? 0 : 1, [0, p.length]);\n }\n }\n if (!/[_-]/.test(p))\n for (const record of api.search(p, limit * 2)) {\n const id = idOf.get(record)!;\n if (record.shortcodes[0]) offer(id, normalize(record.shortcodes[0]), 3, null);\n }\n const relevance = (a: { score: number; shortcode: string; record: EmojiRecord }, b: typeof a) => a.score - b.score || a.shortcode.length - b.shortcode.length || a.record.order - b.record.order;\n const tier = (score: number) => (score <= 1 ? score : 2);\n const alphabetical = (a: { score: number; shortcode: string }, b: typeof a) => tier(a.score) - tier(b.score) || collator.compare(a.shortcode, b.shortcode);\n return [...best.values()]\n .sort(order === \"alphabetical\" ? alphabetical : relevance)\n .slice(0, limit)\n .map(({ score: _score, ...rest }) => rest);\n },\n };\n return api;\n}\n\nfunction lowerBound(keys: readonly string[], target: string) {\n let lo = 0;\n let hi = keys.length;\n while (lo < hi) {\n const mid = (lo + hi) >> 1;\n if (keys[mid]! < target) lo = mid + 1;\n else hi = mid;\n }\n return lo;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/search/search-index.ts"],"sourcesContent":["import type { EmojiRecord } from \"../dataset\";\nimport { memo } from \"../reactive\";\nimport { canonicalLocale, normalizer, tokenizerFor, type Tokenizer } from \"./tokenize\";\n\nconst SHORTCODE = 0;\nconst WORD = 1;\nconst TAG = 2;\n\nexport type SearchIndexOptions = {\n locale?: string;\n tokenize?: Tokenizer;\n};\n\nexport type SearchIndex = {\n tokenize: Tokenizer;\n search(query: string, limit?: number): EmojiRecord[];\n vocabulary(): ReadonlyMap<string, number>;\n byShortcode(code: string): EmojiRecord | undefined;\n completeShortcode(prefix: string, limit?: number, order?: CompletionOrder, frequency?: (unified: string) => number): ShortcodeCompletion[];\n};\n\nexport type CompletionOrder = \"relevance\" | \"alphabetical\";\n\nexport type ShortcodeCompletion = {\n record: EmojiRecord;\n shortcode: string;\n match: [number, number] | null;\n};\n\nexport function createSearchIndex(records: readonly EmojiRecord[], { locale, tokenize = tokenizerFor(locale) }: SearchIndexOptions = {}): SearchIndex {\n const normalize = normalizer(locale);\n const collator = new Intl.Collator(canonicalLocale(locale));\n const entries: [token: string, id: number, kind: number][] = [];\n const shortcodes = new Map<string, number>();\n const shortcodeList: [code: string, id: number][] = [];\n\n records.forEach((record, id) => {\n const best = new Map<string, number>();\n const add = (token: string, kind: number) => {\n const prev = best.get(token);\n if (prev === undefined || kind < prev) best.set(token, kind);\n };\n for (const code of record.shortcodes) {\n const lower = normalize(code);\n if (!shortcodes.has(lower)) shortcodes.set(lower, id);\n shortcodeList.push([lower, id]);\n add(lower, SHORTCODE);\n for (const t of tokenize(lower)) add(t, WORD);\n }\n for (const t of tokenize(record.name)) add(t, WORD);\n for (const tag of record.tags) for (const t of tokenize(tag)) add(t, TAG);\n for (const [token, kind] of best) entries.push([token, id, kind]);\n });\n\n entries.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : a[2] - b[2]));\n const keys = entries.map((e) => e[0]);\n const ids = Int32Array.from(entries, (e) => e[1]);\n const kinds = Uint8Array.from(entries, (e) => e[2]);\n shortcodeList.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : records[a[1]]!.order - records[b[1]]!.order));\n const shortcodeKeys = shortcodeList.map((s) => s[0]);\n const codeWords: [word: string, listIndex: number, offset: number][] = [];\n shortcodeList.forEach(([code], listIndex) => {\n let offset = 0;\n for (const part of code.split(\"_\")) {\n if (part) codeWords.push([part, listIndex, offset]);\n offset += part.length + 1;\n }\n });\n codeWords.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));\n const codeWordKeys = codeWords.map((w) => w[0]);\n\n const idOf = new Map(records.map((r, i) => [r, i] as const));\n const vocabulary = memo(() => keys.reduce((counts, key) => counts.set(key, (counts.get(key) ?? 0) + 1), new Map<string, number>()));\n const api: SearchIndex = {\n tokenize,\n\n vocabulary,\n\n search(query, limit = Infinity) {\n const words = tokenize(query);\n if (!words.length) return [];\n const matches = words.map((word, n) => {\n const prefix = n === words.length - 1;\n const next = new Map<number, number>();\n for (let j = lowerBound(keys, word); j < keys.length; j++) {\n const key = keys[j]!;\n if (prefix ? !key.startsWith(word) : key !== word) break;\n const score = kinds[j]! + (key === word ? 0 : 3);\n const id = ids[j]!;\n const prev = next.get(id);\n if (prev === undefined || score < prev) next.set(id, score);\n }\n return next;\n });\n const scores = matches.reduce((all, next) => new Map([...next].flatMap(([id, s]) => (all.has(id) ? [[id, all.get(id)! + s] as const] : []))));\n const ranked = [...scores].sort((a, b) => a[1] - b[1] || records[a[0]]!.order - records[b[0]]!.order);\n return ranked.slice(0, limit).map(([id]) => records[id]!);\n },\n\n byShortcode(code) {\n const id = shortcodes.get(normalize(code).replace(/^:|:$/g, \"\"));\n return id === undefined ? undefined : records[id];\n },\n\n completeShortcode(prefix, limit = 8, order = \"relevance\", frequency = () => 0) {\n const p = normalize(prefix);\n const best = new Map<number, ShortcodeCompletion & { score: number }>();\n const offer = (id: number, shortcode: string, score: number, match: [number, number] | null) => {\n const prev = best.get(id);\n if (!prev || score < prev.score || (score === prev.score && shortcode.length < prev.shortcode.length)) {\n best.set(id, { record: records[id]!, shortcode, match, score });\n }\n };\n for (let j = lowerBound(codeWordKeys, p); j < codeWordKeys.length; j++) {\n if (!codeWordKeys[j]!.startsWith(p)) break;\n const [, listIndex, offset] = codeWords[j]!;\n const [code, id] = shortcodeList[listIndex]!;\n offer(id, code, code === p ? 0 : offset === 0 ? 1 : 2, [offset, offset + p.length]);\n }\n if (/[_-]/.test(p)) {\n for (let j = lowerBound(shortcodeKeys, p); j < shortcodeKeys.length && shortcodeKeys[j]!.startsWith(p); j++) {\n const [code, id] = shortcodeList[j]!;\n offer(id, code, code === p ? 0 : 1, [0, p.length]);\n }\n }\n if (!/[_-]/.test(p))\n for (const record of api.search(p, limit * 2)) {\n const id = idOf.get(record)!;\n if (record.shortcodes[0]) offer(id, normalize(record.shortcodes[0]), 3, null);\n }\n const relevance = (a: { score: number; shortcode: string; record: EmojiRecord }, b: typeof a) =>\n a.score - b.score || frequency(b.record.unified) - frequency(a.record.unified) || a.shortcode.length - b.shortcode.length || a.record.order - b.record.order;\n const tier = (score: number) => (score <= 1 ? score : 2);\n const alphabetical = (a: { score: number; shortcode: string }, b: typeof a) => tier(a.score) - tier(b.score) || collator.compare(a.shortcode, b.shortcode);\n return [...best.values()]\n .sort(order === \"alphabetical\" ? alphabetical : relevance)\n .slice(0, limit)\n .map(({ score: _score, ...rest }) => rest);\n },\n };\n return api;\n}\n\nfunction lowerBound(keys: readonly string[], target: string) {\n let lo = 0;\n let hi = keys.length;\n while (lo < hi) {\n const mid = (lo + hi) >> 1;\n if (keys[mid]! < target) lo = mid + 1;\n else hi = mid;\n }\n return lo;\n}\n"],"mappings":";AACA,SAAS,YAAY;AACrB,SAAS,iBAAiB,YAAY,oBAAoC;AAE1E,IAAM,YAAY;AAClB,IAAM,OAAO;AACb,IAAM,MAAM;AAuBL,SAAS,kBAAkB,SAAiC,EAAE,QAAQ,WAAW,aAAa,MAAM,EAAE,IAAwB,CAAC,GAAgB;AACpJ,QAAM,YAAY,WAAW,MAAM;AACnC,QAAM,WAAW,IAAI,KAAK,SAAS,gBAAgB,MAAM,CAAC;AAC1D,QAAM,UAAuD,CAAC;AAC9D,QAAM,aAAa,oBAAI,IAAoB;AAC3C,QAAM,gBAA8C,CAAC;AAErD,UAAQ,QAAQ,CAAC,QAAQ,OAAO;AAC9B,UAAM,OAAO,oBAAI,IAAoB;AACrC,UAAM,MAAM,CAAC,OAAe,SAAiB;AAC3C,YAAM,OAAO,KAAK,IAAI,KAAK;AAC3B,UAAI,SAAS,UAAa,OAAO,KAAM,MAAK,IAAI,OAAO,IAAI;AAAA,IAC7D;AACA,eAAW,QAAQ,OAAO,YAAY;AACpC,YAAM,QAAQ,UAAU,IAAI;AAC5B,UAAI,CAAC,WAAW,IAAI,KAAK,EAAG,YAAW,IAAI,OAAO,EAAE;AACpD,oBAAc,KAAK,CAAC,OAAO,EAAE,CAAC;AAC9B,UAAI,OAAO,SAAS;AACpB,iBAAW,KAAK,SAAS,KAAK,EAAG,KAAI,GAAG,IAAI;AAAA,IAC9C;AACA,eAAW,KAAK,SAAS,OAAO,IAAI,EAAG,KAAI,GAAG,IAAI;AAClD,eAAW,OAAO,OAAO,KAAM,YAAW,KAAK,SAAS,GAAG,EAAG,KAAI,GAAG,GAAG;AACxE,eAAW,CAAC,OAAO,IAAI,KAAK,KAAM,SAAQ,KAAK,CAAC,OAAO,IAAI,IAAI,CAAC;AAAA,EAClE,CAAC;AAED,UAAQ,KAAK,CAAC,GAAG,MAAO,EAAE,CAAC,IAAI,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,CAAE;AACzE,QAAM,OAAO,QAAQ,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;AACpC,QAAM,MAAM,WAAW,KAAK,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC;AAChD,QAAM,QAAQ,WAAW,KAAK,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC;AAClD,gBAAc,KAAK,CAAC,GAAG,MAAO,EAAE,CAAC,IAAI,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,IAAI,IAAI,QAAQ,EAAE,CAAC,CAAC,EAAG,QAAQ,QAAQ,EAAE,CAAC,CAAC,EAAG,KAAM;AAC/G,QAAM,gBAAgB,cAAc,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;AACnD,QAAM,YAAiE,CAAC;AACxE,gBAAc,QAAQ,CAAC,CAAC,IAAI,GAAG,cAAc;AAC3C,QAAI,SAAS;AACb,eAAW,QAAQ,KAAK,MAAM,GAAG,GAAG;AAClC,UAAI,KAAM,WAAU,KAAK,CAAC,MAAM,WAAW,MAAM,CAAC;AAClD,gBAAU,KAAK,SAAS;AAAA,IAC1B;AAAA,EACF,CAAC;AACD,YAAU,KAAK,CAAC,GAAG,MAAO,EAAE,CAAC,IAAI,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,IAAI,IAAI,CAAE;AACjE,QAAM,eAAe,UAAU,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;AAE9C,QAAM,OAAO,IAAI,IAAI,QAAQ,IAAI,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAU,CAAC;AAC3D,QAAM,aAAa,KAAK,MAAM,KAAK,OAAO,CAAC,QAAQ,QAAQ,OAAO,IAAI,MAAM,OAAO,IAAI,GAAG,KAAK,KAAK,CAAC,GAAG,oBAAI,IAAoB,CAAC,CAAC;AAClI,QAAM,MAAmB;AAAA,IACvB;AAAA,IAEA;AAAA,IAEA,OAAO,OAAO,QAAQ,UAAU;AAC9B,YAAM,QAAQ,SAAS,KAAK;AAC5B,UAAI,CAAC,MAAM,OAAQ,QAAO,CAAC;AAC3B,YAAM,UAAU,MAAM,IAAI,CAAC,MAAM,MAAM;AACrC,cAAM,SAAS,MAAM,MAAM,SAAS;AACpC,cAAM,OAAO,oBAAI,IAAoB;AACrC,iBAAS,IAAI,WAAW,MAAM,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACzD,gBAAM,MAAM,KAAK,CAAC;AAClB,cAAI,SAAS,CAAC,IAAI,WAAW,IAAI,IAAI,QAAQ,KAAM;AACnD,gBAAM,QAAQ,MAAM,CAAC,KAAM,QAAQ,OAAO,IAAI;AAC9C,gBAAM,KAAK,IAAI,CAAC;AAChB,gBAAM,OAAO,KAAK,IAAI,EAAE;AACxB,cAAI,SAAS,UAAa,QAAQ,KAAM,MAAK,IAAI,IAAI,KAAK;AAAA,QAC5D;AACA,eAAO;AAAA,MACT,CAAC;AACD,YAAM,SAAS,QAAQ,OAAO,CAAC,KAAK,SAAS,IAAI,IAAI,CAAC,GAAG,IAAI,EAAE,QAAQ,CAAC,CAAC,IAAI,CAAC,MAAO,IAAI,IAAI,EAAE,IAAI,CAAC,CAAC,IAAI,IAAI,IAAI,EAAE,IAAK,CAAC,CAAU,IAAI,CAAC,CAAE,CAAC,CAAC;AAC5I,YAAM,SAAS,CAAC,GAAG,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,QAAQ,EAAE,CAAC,CAAC,EAAG,QAAQ,QAAQ,EAAE,CAAC,CAAC,EAAG,KAAK;AACpG,aAAO,OAAO,MAAM,GAAG,KAAK,EAAE,IAAI,CAAC,CAAC,EAAE,MAAM,QAAQ,EAAE,CAAE;AAAA,IAC1D;AAAA,IAEA,YAAY,MAAM;AAChB,YAAM,KAAK,WAAW,IAAI,UAAU,IAAI,EAAE,QAAQ,UAAU,EAAE,CAAC;AAC/D,aAAO,OAAO,SAAY,SAAY,QAAQ,EAAE;AAAA,IAClD;AAAA,IAEA,kBAAkB,QAAQ,QAAQ,GAAG,QAAQ,aAAa,YAAY,MAAM,GAAG;AAC7E,YAAM,IAAI,UAAU,MAAM;AAC1B,YAAM,OAAO,oBAAI,IAAqD;AACtE,YAAM,QAAQ,CAAC,IAAY,WAAmB,OAAe,UAAmC;AAC9F,cAAM,OAAO,KAAK,IAAI,EAAE;AACxB,YAAI,CAAC,QAAQ,QAAQ,KAAK,SAAU,UAAU,KAAK,SAAS,UAAU,SAAS,KAAK,UAAU,QAAS;AACrG,eAAK,IAAI,IAAI,EAAE,QAAQ,QAAQ,EAAE,GAAI,WAAW,OAAO,MAAM,CAAC;AAAA,QAChE;AAAA,MACF;AACA,eAAS,IAAI,WAAW,cAAc,CAAC,GAAG,IAAI,aAAa,QAAQ,KAAK;AACtE,YAAI,CAAC,aAAa,CAAC,EAAG,WAAW,CAAC,EAAG;AACrC,cAAM,CAAC,EAAE,WAAW,MAAM,IAAI,UAAU,CAAC;AACzC,cAAM,CAAC,MAAM,EAAE,IAAI,cAAc,SAAS;AAC1C,cAAM,IAAI,MAAM,SAAS,IAAI,IAAI,WAAW,IAAI,IAAI,GAAG,CAAC,QAAQ,SAAS,EAAE,MAAM,CAAC;AAAA,MACpF;AACA,UAAI,OAAO,KAAK,CAAC,GAAG;AAClB,iBAAS,IAAI,WAAW,eAAe,CAAC,GAAG,IAAI,cAAc,UAAU,cAAc,CAAC,EAAG,WAAW,CAAC,GAAG,KAAK;AAC3G,gBAAM,CAAC,MAAM,EAAE,IAAI,cAAc,CAAC;AAClC,gBAAM,IAAI,MAAM,SAAS,IAAI,IAAI,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC;AAAA,QACnD;AAAA,MACF;AACA,UAAI,CAAC,OAAO,KAAK,CAAC;AAChB,mBAAW,UAAU,IAAI,OAAO,GAAG,QAAQ,CAAC,GAAG;AAC7C,gBAAM,KAAK,KAAK,IAAI,MAAM;AAC1B,cAAI,OAAO,WAAW,CAAC,EAAG,OAAM,IAAI,UAAU,OAAO,WAAW,CAAC,CAAC,GAAG,GAAG,IAAI;AAAA,QAC9E;AACF,YAAM,YAAY,CAAC,GAA8D,MAC/E,EAAE,QAAQ,EAAE,SAAS,UAAU,EAAE,OAAO,OAAO,IAAI,UAAU,EAAE,OAAO,OAAO,KAAK,EAAE,UAAU,SAAS,EAAE,UAAU,UAAU,EAAE,OAAO,QAAQ,EAAE,OAAO;AACzJ,YAAM,OAAO,CAAC,UAAmB,SAAS,IAAI,QAAQ;AACtD,YAAM,eAAe,CAAC,GAAyC,MAAgB,KAAK,EAAE,KAAK,IAAI,KAAK,EAAE,KAAK,KAAK,SAAS,QAAQ,EAAE,WAAW,EAAE,SAAS;AACzJ,aAAO,CAAC,GAAG,KAAK,OAAO,CAAC,EACrB,KAAK,UAAU,iBAAiB,eAAe,SAAS,EACxD,MAAM,GAAG,KAAK,EACd,IAAI,CAAC,EAAE,OAAO,QAAQ,GAAG,KAAK,MAAM,IAAI;AAAA,IAC7C;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,WAAW,MAAyB,QAAgB;AAC3D,MAAI,KAAK;AACT,MAAI,KAAK,KAAK;AACd,SAAO,KAAK,IAAI;AACd,UAAM,MAAO,KAAK,MAAO;AACzB,QAAI,KAAK,GAAG,IAAK,OAAQ,MAAK,MAAM;AAAA,QAC/B,MAAK;AAAA,EACZ;AACA,SAAO;AACT;","names":[]}
|
package/dist/search/tokenize.js
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
import { cached } from "../reactive.js";
|
|
3
3
|
var SEPARATORS = /[\s_\-:,.()]+/;
|
|
4
4
|
var NON_ASCII = /[^\x00-\x7f]/;
|
|
5
|
+
var HIRAGANA = /[\u3041-\u3096\u309d\u309e]/g;
|
|
6
|
+
var HIRAGANA_ONLY = /^[\p{sc=Hiragana}\u30fc]+$/u;
|
|
7
|
+
var toKatakana = (text) => text.replace(HIRAGANA, (c) => String.fromCharCode(c.charCodeAt(0) + 96));
|
|
5
8
|
function canonicalLocale(locale) {
|
|
6
9
|
if (!locale) return void 0;
|
|
7
10
|
try {
|
|
@@ -29,7 +32,7 @@ function createTokenizer({ locale, segmenter } = {}) {
|
|
|
29
32
|
tokens.push(piece);
|
|
30
33
|
continue;
|
|
31
34
|
}
|
|
32
|
-
for (const { segment, isWordLike } of words.segment(piece)) if (isWordLike ?? segment.trim()) tokens.push(segment);
|
|
35
|
+
for (const { segment, isWordLike } of words.segment(HIRAGANA_ONLY.test(piece) ? toKatakana(piece) : piece)) if (isWordLike ?? segment.trim()) tokens.push(toKatakana(segment));
|
|
33
36
|
}
|
|
34
37
|
return tokens;
|
|
35
38
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/search/tokenize.ts"],"sourcesContent":["import { cached } from \"../reactive\";\n\nexport type Tokenizer = (text: string) => string[];\n\nexport type WordSegmenter = { segment(text: string): Iterable<{ segment: string; isWordLike?: boolean }> };\n\nexport type TokenizerOptions = {\n locale?: string;\n segmenter?: WordSegmenter | false;\n};\n\nconst SEPARATORS = /[\\s_\\-:,.()]+/;\nconst NON_ASCII = /[^\\x00-\\x7f]/;\n\nexport function canonicalLocale(locale: string | undefined): string | undefined {\n if (!locale) return undefined;\n try {\n return Intl.getCanonicalLocales(locale)[0];\n } catch {\n return undefined;\n }\n}\n\nexport function normalizer(locale?: string): (text: string) => string {\n const tag = canonicalLocale(locale);\n return tag ? (text) => text.normalize(\"NFKC\").toLocaleLowerCase(tag) : (text) => text.normalize(\"NFKC\").toLowerCase();\n}\n\nfunction defaultSegmenter(locale: string | undefined): WordSegmenter | false {\n return typeof Intl.Segmenter === \"function\" ? new Intl.Segmenter(locale, { granularity: \"word\" }) : false;\n}\n\nexport function createTokenizer({ locale, segmenter }: TokenizerOptions = {}): Tokenizer {\n const tag = canonicalLocale(locale);\n const normalize = normalizer(tag);\n const words = segmenter ?? defaultSegmenter(tag);\n return (text) => {\n const tokens: string[] = [];\n for (const piece of normalize(text).split(SEPARATORS)) {\n if (!piece) continue;\n if (!words || !NON_ASCII.test(piece)) {\n tokens.push(piece);\n continue;\n }\n for (const { segment, isWordLike } of words.segment(piece)) if (isWordLike ?? segment.trim()) tokens.push(segment);\n }\n return tokens;\n };\n}\n\nconst shared = new Map<string, Tokenizer>();\n\nexport function tokenizerFor(locale?: string): Tokenizer {\n const key = canonicalLocale(locale) ?? \"\";\n return cached(shared, key, () => createTokenizer({ locale: key || undefined }));\n}\n"],"mappings":";AAAA,SAAS,cAAc;AAWvB,IAAM,aAAa;AACnB,IAAM,YAAY;
|
|
1
|
+
{"version":3,"sources":["../../src/search/tokenize.ts"],"sourcesContent":["import { cached } from \"../reactive\";\n\nexport type Tokenizer = (text: string) => string[];\n\nexport type WordSegmenter = { segment(text: string): Iterable<{ segment: string; isWordLike?: boolean }> };\n\nexport type TokenizerOptions = {\n locale?: string;\n segmenter?: WordSegmenter | false;\n};\n\nconst SEPARATORS = /[\\s_\\-:,.()]+/;\nconst NON_ASCII = /[^\\x00-\\x7f]/;\nconst HIRAGANA = /[\\u3041-\\u3096\\u309d\\u309e]/g;\nconst HIRAGANA_ONLY = /^[\\p{sc=Hiragana}\\u30fc]+$/u;\nconst toKatakana = (text: string) => text.replace(HIRAGANA, (c) => String.fromCharCode(c.charCodeAt(0) + 0x60));\n\nexport function canonicalLocale(locale: string | undefined): string | undefined {\n if (!locale) return undefined;\n try {\n return Intl.getCanonicalLocales(locale)[0];\n } catch {\n return undefined;\n }\n}\n\nexport function normalizer(locale?: string): (text: string) => string {\n const tag = canonicalLocale(locale);\n return tag ? (text) => text.normalize(\"NFKC\").toLocaleLowerCase(tag) : (text) => text.normalize(\"NFKC\").toLowerCase();\n}\n\nfunction defaultSegmenter(locale: string | undefined): WordSegmenter | false {\n return typeof Intl.Segmenter === \"function\" ? new Intl.Segmenter(locale, { granularity: \"word\" }) : false;\n}\n\nexport function createTokenizer({ locale, segmenter }: TokenizerOptions = {}): Tokenizer {\n const tag = canonicalLocale(locale);\n const normalize = normalizer(tag);\n const words = segmenter ?? defaultSegmenter(tag);\n return (text) => {\n const tokens: string[] = [];\n for (const piece of normalize(text).split(SEPARATORS)) {\n if (!piece) continue;\n if (!words || !NON_ASCII.test(piece)) {\n tokens.push(piece);\n continue;\n }\n // Kana folds to katakana, so ねこ finds ネコ; all-hiragana text folds before segmenting, which splits hiragana words it doesn't know (は|ー|と)\n for (const { segment, isWordLike } of words.segment(HIRAGANA_ONLY.test(piece) ? toKatakana(piece) : piece)) if (isWordLike ?? segment.trim()) tokens.push(toKatakana(segment));\n }\n return tokens;\n };\n}\n\nconst shared = new Map<string, Tokenizer>();\n\nexport function tokenizerFor(locale?: string): Tokenizer {\n const key = canonicalLocale(locale) ?? \"\";\n return cached(shared, key, () => createTokenizer({ locale: key || undefined }));\n}\n"],"mappings":";AAAA,SAAS,cAAc;AAWvB,IAAM,aAAa;AACnB,IAAM,YAAY;AAClB,IAAM,WAAW;AACjB,IAAM,gBAAgB;AACtB,IAAM,aAAa,CAAC,SAAiB,KAAK,QAAQ,UAAU,CAAC,MAAM,OAAO,aAAa,EAAE,WAAW,CAAC,IAAI,EAAI,CAAC;AAEvG,SAAS,gBAAgB,QAAgD;AAC9E,MAAI,CAAC,OAAQ,QAAO;AACpB,MAAI;AACF,WAAO,KAAK,oBAAoB,MAAM,EAAE,CAAC;AAAA,EAC3C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,WAAW,QAA2C;AACpE,QAAM,MAAM,gBAAgB,MAAM;AAClC,SAAO,MAAM,CAAC,SAAS,KAAK,UAAU,MAAM,EAAE,kBAAkB,GAAG,IAAI,CAAC,SAAS,KAAK,UAAU,MAAM,EAAE,YAAY;AACtH;AAEA,SAAS,iBAAiB,QAAmD;AAC3E,SAAO,OAAO,KAAK,cAAc,aAAa,IAAI,KAAK,UAAU,QAAQ,EAAE,aAAa,OAAO,CAAC,IAAI;AACtG;AAEO,SAAS,gBAAgB,EAAE,QAAQ,UAAU,IAAsB,CAAC,GAAc;AACvF,QAAM,MAAM,gBAAgB,MAAM;AAClC,QAAM,YAAY,WAAW,GAAG;AAChC,QAAM,QAAQ,aAAa,iBAAiB,GAAG;AAC/C,SAAO,CAAC,SAAS;AACf,UAAM,SAAmB,CAAC;AAC1B,eAAW,SAAS,UAAU,IAAI,EAAE,MAAM,UAAU,GAAG;AACrD,UAAI,CAAC,MAAO;AACZ,UAAI,CAAC,SAAS,CAAC,UAAU,KAAK,KAAK,GAAG;AACpC,eAAO,KAAK,KAAK;AACjB;AAAA,MACF;AAEA,iBAAW,EAAE,SAAS,WAAW,KAAK,MAAM,QAAQ,cAAc,KAAK,KAAK,IAAI,WAAW,KAAK,IAAI,KAAK,EAAG,KAAI,cAAc,QAAQ,KAAK,EAAG,QAAO,KAAK,WAAW,OAAO,CAAC;AAAA,IAC/K;AACA,WAAO;AAAA,EACT;AACF;AAEA,IAAM,SAAS,oBAAI,IAAuB;AAEnC,SAAS,aAAa,QAA4B;AACvD,QAAM,MAAM,gBAAgB,MAAM,KAAK;AACvC,SAAO,OAAO,QAAQ,KAAK,MAAM,gBAAgB,EAAE,QAAQ,OAAO,OAAU,CAAC,CAAC;AAChF;","names":[]}
|
|
@@ -61,8 +61,9 @@ type EmojiLabels = {
|
|
|
61
61
|
emojiList: string;
|
|
62
62
|
resultCount: (count: number) => string;
|
|
63
63
|
categoryNavigation: string;
|
|
64
|
-
|
|
64
|
+
frequent: string;
|
|
65
65
|
custom: string;
|
|
66
|
+
suggestions: string;
|
|
66
67
|
reactions: string;
|
|
67
68
|
expandReactions: string;
|
|
68
69
|
previewCaption: string;
|
|
@@ -218,7 +219,7 @@ declare const isComposingKey: (event: {
|
|
|
218
219
|
}) => boolean;
|
|
219
220
|
declare function nameWithTone(name: string, unified: string, format?: EmojiLabels["withSkinTone"]): string;
|
|
220
221
|
|
|
221
|
-
type CategoryId = Group | "
|
|
222
|
+
type CategoryId = Group | "frequent" | "custom" | `custom:${string}`;
|
|
222
223
|
interface CategoryOption<Icon = unknown> {
|
|
223
224
|
id: CategoryId;
|
|
224
225
|
name?: string;
|
package/dist/store.js
CHANGED
|
@@ -4,7 +4,7 @@ import { controllable, createChangeDetails } from "./change.js";
|
|
|
4
4
|
import { customRecords, loadCustomEmojis, uniqueById } from "./custom.js";
|
|
5
5
|
import { createFrequencyStore, persisted, readOnFirstSubscribe } from "./frequents.js";
|
|
6
6
|
import { localizeLabels } from "./labels.js";
|
|
7
|
-
import {
|
|
7
|
+
import { localeData } from "./platform.js";
|
|
8
8
|
import { cached, createStore, fetchJson, memo, toError } from "./reactive.js";
|
|
9
9
|
import { indexFor, isThenable, lexicalSearch } from "./search/search.js";
|
|
10
10
|
import { formatShortcode, replaceShortcodes, toneOf } from "./shortcodes.js";
|
|
@@ -29,7 +29,8 @@ var CODEPOINTS = /^[\da-f-]+$/i;
|
|
|
29
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
|
-
var
|
|
32
|
+
var dataOf = ({ data, locale }) => data ?? localeData(locale ?? "en");
|
|
33
|
+
var sameData = (a, b) => dataOf(a) === dataOf(b) || typeof a.data === "function" && typeof b.data === "function" && a.locale === b.locale;
|
|
33
34
|
var sameSource = (a, b) => sameData(a, b) && (a.customEmojis === b.customEmojis || customKey(a.customEmojis) === customKey(b.customEmojis));
|
|
34
35
|
var sameItems = (a, b) => a.length === b.length && a.every((x, i) => x === b[i]);
|
|
35
36
|
var lookup = (data, unified) => data.byUnified.get(unified.toLowerCase()) ?? data.byUnified.get(unified);
|
|
@@ -103,7 +104,7 @@ function createEmojiStore({
|
|
|
103
104
|
if (loading || raw && !raw[1].errors.length) return loading ?? Promise.resolve();
|
|
104
105
|
const current = controller = new AbortController();
|
|
105
106
|
const { signal } = current;
|
|
106
|
-
const dataset = raw ? Promise.resolve(raw[0]) : resolve(config
|
|
107
|
+
const dataset = raw ? Promise.resolve(raw[0]) : resolve(dataOf(config), signal).then(toDataset);
|
|
107
108
|
const pending = Promise.all([dataset, loadCustomEmojis(config.customEmojis, signal)]).then(
|
|
108
109
|
(next) => {
|
|
109
110
|
if (controller === current) publish(next);
|
|
@@ -159,7 +160,7 @@ function createEmojiStore({
|
|
|
159
160
|
const result = (config.search ?? defaultEngine)(text, { records: loaded().records, limit, signal, frequency: (u) => frequents.score(u), locale: loaded().locale });
|
|
160
161
|
return isThenable(result) ? Promise.resolve(result).then(resolve2) : resolve2(result);
|
|
161
162
|
},
|
|
162
|
-
completeShortcode: (prefix, limit, order) => ready() ? index().completeShortcode(prefix, limit, order) : [],
|
|
163
|
+
completeShortcode: (prefix, limit, order) => ready() ? index().completeShortcode(prefix, limit, order, (u) => frequents.score(u)) : [],
|
|
163
164
|
replaceShortcodes: (text) => ready() ? replaceShortcodes(text, index()) : text,
|
|
164
165
|
skinTone: () => clampTone(tone.value()),
|
|
165
166
|
setSkinTone(next, { reason = "none", event } = {}) {
|