goemoji 0.3.0
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/CHANGELOG.md +45 -0
- package/LICENSE +21 -0
- package/README.md +263 -0
- package/README.ru.md +258 -0
- package/THIRD_PARTY_NOTICES.md +18 -0
- package/data/en.json +1 -0
- package/data/ru.json +1 -0
- package/dist/discord.d.ts +15 -0
- package/dist/discord.js +2 -0
- package/dist/discord.js.map +1 -0
- package/dist/index.d.ts +67 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +1 -0
- package/package.json +90 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//#region src/discord.d.ts
|
|
2
|
+
export type CustomEmoji = {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
animated: boolean;
|
|
6
|
+
};
|
|
7
|
+
export declare function parseCustomEmoji(value: string): CustomEmoji | null;
|
|
8
|
+
export declare function customEmojiToString(emoji: Omit<CustomEmoji, "animated"> & {
|
|
9
|
+
animated?: boolean;
|
|
10
|
+
}): string;
|
|
11
|
+
export declare function customEmojiUrl(id: string, animated?: boolean, size?: number): string;
|
|
12
|
+
export declare function normalizeEmojiText(value: string): string;
|
|
13
|
+
export declare function sameEmojiValue(saved: string, name: string | null, identifier: string): boolean;
|
|
14
|
+
//#endregion
|
|
15
|
+
//# sourceMappingURL=discord.d.ts.map
|
package/dist/discord.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=/^<(a?):([\w~]+):(\d+)>$/;function t(t){let n=e.exec(t);return n?{name:n[2],id:n[3],animated:n[1]===`a`}:null}function n(e){return`<${e.animated?`a`:``}:${e.name}:${e.id}>`}function r(e,t=!1,n=48){return`https://cdn.discordapp.com/emojis/${e}.${t?`gif`:`png`}?size=${Number.isFinite(n)?Math.min(4096,Math.max(16,Math.trunc(n))):48}`}function i(e){return e.replace(/[\uFE0E\uFE0F\u200D]/gu,``)}function a(e,n,r){let a=t(e);return a?r.endsWith(`:${a.id}`):i(e)===i(n??``)}export{n as customEmojiToString,r as customEmojiUrl,i as normalizeEmojiText,t as parseCustomEmoji,a as sameEmojiValue};
|
|
2
|
+
//# sourceMappingURL=discord.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discord.js","names":[],"sources":["../src/discord.ts"],"sourcesContent":["export type CustomEmoji = { id: string; name: string; animated: boolean };\n\nconst CUSTOM_EMOJI = /^<(a?):([\\w~]+):(\\d+)>$/;\n\nexport function parseCustomEmoji(value: string): CustomEmoji | null {\n const match = CUSTOM_EMOJI.exec(value);\n if (!match) return null;\n return { name: match[2]!, id: match[3]!, animated: match[1] === \"a\" };\n}\n\nexport function customEmojiToString(emoji: Omit<CustomEmoji, \"animated\"> & { animated?: boolean }): string {\n return `<${emoji.animated ? \"a\" : \"\"}:${emoji.name}:${emoji.id}>`;\n}\n\nexport function customEmojiUrl(id: string, animated = false, size = 48): string {\n const pixels = Number.isFinite(size) ? Math.min(4096, Math.max(16, Math.trunc(size))) : 48;\n return `https://cdn.discordapp.com/emojis/${id}.${animated ? \"gif\" : \"png\"}?size=${pixels}`;\n}\n\nexport function normalizeEmojiText(value: string): string {\n return value.replace(/[\\uFE0E\\uFE0F\\u200D]/gu, \"\");\n}\n\nexport function sameEmojiValue(saved: string, name: string | null, identifier: string): boolean {\n const custom = parseCustomEmoji(saved);\n if (custom) return identifier.endsWith(`:${custom.id}`);\n return normalizeEmojiText(saved) === normalizeEmojiText(name ?? \"\");\n}\n"],"mappings":"AAEA,MAAM,EAAe,0BAErB,SAAgB,EAAiB,EAAmC,CAClE,IAAM,EAAQ,EAAa,KAAK,CAAK,EAErC,OADK,EACE,CAAE,KAAM,EAAM,GAAK,GAAI,EAAM,GAAK,SAAU,EAAM,KAAO,GAAI,EADjD,IAErB,CAEA,SAAgB,EAAoB,EAAuE,CACzG,MAAO,IAAI,EAAM,SAAW,IAAM,GAAG,GAAG,EAAM,KAAK,GAAG,EAAM,GAAG,EACjE,CAEA,SAAgB,EAAe,EAAY,EAAW,GAAO,EAAO,GAAY,CAE9E,MAAO,qCAAqC,EAAG,GAAG,EAAW,MAAQ,MAAM,QAD5D,OAAO,SAAS,CAAI,EAAI,KAAK,IAAI,KAAM,KAAK,IAAI,GAAI,KAAK,MAAM,CAAI,CAAC,CAAC,EAAI,IAE1F,CAEA,SAAgB,EAAmB,EAAuB,CACxD,OAAO,EAAM,QAAQ,yBAA0B,EAAE,CACnD,CAEA,SAAgB,EAAe,EAAe,EAAqB,EAA6B,CAC9F,IAAM,EAAS,EAAiB,CAAK,EAErC,OADI,EAAe,EAAW,SAAS,IAAI,EAAO,IAAI,EAC/C,EAAmB,CAAK,IAAM,EAAmB,GAAQ,EAAE,CACpE"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
//#region src/data.d.ts
|
|
2
|
+
type SkinTone = "none" | "light" | "medium-light" | "medium" | "medium-dark" | "dark";
|
|
3
|
+
type Category = {
|
|
4
|
+
key: string;
|
|
5
|
+
label: string;
|
|
6
|
+
};
|
|
7
|
+
type SlimEmoji = [string, string, string, number];
|
|
8
|
+
type ServerEmoji = {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
animated: boolean;
|
|
12
|
+
};
|
|
13
|
+
type Emoji = {
|
|
14
|
+
value: string;
|
|
15
|
+
label: string;
|
|
16
|
+
tags: string;
|
|
17
|
+
category: number;
|
|
18
|
+
server?: {
|
|
19
|
+
id: string;
|
|
20
|
+
animated: boolean;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
type EmojiData = {
|
|
24
|
+
locale: string;
|
|
25
|
+
categories: Category[];
|
|
26
|
+
emojis: Emoji[];
|
|
27
|
+
};
|
|
28
|
+
export declare function parseEmojiData(input: unknown): EmojiData;
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/labels.d.ts
|
|
31
|
+
type Labels = {
|
|
32
|
+
search: string;
|
|
33
|
+
empty: string;
|
|
34
|
+
recent: string;
|
|
35
|
+
server: string;
|
|
36
|
+
skinTone: string;
|
|
37
|
+
skinTones: Record<Exclude<SkinTone, "none">, string>;
|
|
38
|
+
};
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/EmojiPicker.d.ts
|
|
41
|
+
type EmojiPickerProps = {
|
|
42
|
+
data: EmojiData;
|
|
43
|
+
serverEmojis?: readonly ServerEmoji[] | undefined;
|
|
44
|
+
columns?: number | undefined;
|
|
45
|
+
cellSize?: number | undefined;
|
|
46
|
+
locale?: string | undefined;
|
|
47
|
+
labels?: Partial<Labels> | undefined;
|
|
48
|
+
skinTone?: SkinTone | undefined;
|
|
49
|
+
onSkinToneChange?: ((tone: SkinTone) => void) | undefined;
|
|
50
|
+
recentKey?: string | false | undefined;
|
|
51
|
+
onSelect: (emoji: Emoji) => void;
|
|
52
|
+
onEscape?: (() => void) | undefined;
|
|
53
|
+
serverIconUrl?: string | undefined;
|
|
54
|
+
className?: string | undefined;
|
|
55
|
+
};
|
|
56
|
+
export declare function EmojiPicker({ data, serverEmojis, columns, cellSize, locale, labels: labelOverrides, skinTone, onSkinToneChange, recentKey, onSelect, onEscape, serverIconUrl, className }: EmojiPickerProps): import("react").JSX.Element;
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/useEmojiData.d.ts
|
|
59
|
+
type UseEmojiDataResult = {
|
|
60
|
+
data: EmojiData | null;
|
|
61
|
+
error: Error | null;
|
|
62
|
+
loading: boolean;
|
|
63
|
+
};
|
|
64
|
+
export declare function useEmojiData(load: () => Promise<unknown>, deps?: readonly unknown[]): UseEmojiDataResult;
|
|
65
|
+
//#endregion
|
|
66
|
+
export type { Category, Emoji, EmojiData, EmojiPickerProps, Labels, ServerEmoji, SkinTone, SlimEmoji, UseEmojiDataResult };
|
|
67
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use client";import{customEmojiUrl as e}from"./discord.js";import{useCallback as t,useEffect as n,useId as r,useMemo as i,useRef as a,useState as o}from"react";import{jsx as s,jsxs as c}from"react/jsx-runtime";const l=[`light`,`medium-light`,`medium`,`medium-dark`,`dark`],u={light:`🏻`,"medium-light":`🏼`,medium:`🏽`,"medium-dark":`🏾`,dark:`🏿`};function d(e){return typeof e==`object`&&!!e}function f(e){if(d(e)&&e.v===void 0&&`default`in e){let t=e.default;if(d(t)&&t.v===1)return t}return e}function p(e){let t=f(e);if(!d(t)||t.v!==1)throw Error(`goemoji: неизвестный формат данных (ожидается v: 1)`);if(!Array.isArray(t.categories)||!Array.isArray(t.emojis))throw Error(`goemoji: в данных нет categories/emojis`);let n=t.categories.map((e,t)=>{if(!d(e)||typeof e.key!=`string`||typeof e.label!=`string`)throw Error(`goemoji: категория #${t} повреждена`);return{key:e.key,label:e.label}}),r=t.emojis.map((e,t)=>{if(!Array.isArray(e)||typeof e[0]!=`string`||typeof e[1]!=`string`||typeof e[2]!=`string`||typeof e[3]!=`number`)throw Error(`goemoji: эмодзи #${t} повреждён`);let r=e[3];if(r<0||r>=n.length)throw Error(`goemoji: эмодзи #${t} ссылается на неизвестную категорию`);if(!e[0])throw Error(`goemoji: эмодзи #${t} пустой`);return{value:e[0],label:e[1],tags:e[2],category:r}});return{locale:typeof t.locale==`string`?t.locale:`en`,categories:n,emojis:r}}function ee(e){let{id:t,name:n,animated:r}=e;return{value:`<${r?`a`:``}:${n}:${t}>`,label:n,tags:`${n.replace(/[_-]+/g,` `)} ${n}`.toLowerCase(),category:-1,server:{id:t,animated:r}}}const m=/\p{Emoji_Modifier_Base}/u,h=/\uFE0F$/,g=/\u{1F3FB}|\u{1F3FC}|\u{1F3FD}|\u{1F3FE}|\u{1F3FF}/gu;function _(e){return e.split(``).some(e=>m.test(e))}function v(e){return e.split(``).map(e=>e.replace(g,``)).join(``)}function y(e,t){let n=v(e);if(t===`none`||!_(n))return n;let r=u[t];return n.split(``).map((e,t,n)=>!m.test(e)||n.length>1&&e===`🤝`?e:e.replace(h,``)+r).join(``)}function te(e,t){let n=e.label.toLowerCase();if(n===t)return 100;if(n.startsWith(t))return 60;if(n.includes(t))return 40;for(let n of e.tags.split(` `))if(n){if(n===t)return 25;if(n.startsWith(t))return 15;if(n.includes(t))return 8}return 0}function ne(e,t){let n=t.trim().toLowerCase();if(!n)return[...e];let r=[];for(let t of e){let e=te(t,n);e>0&&r.push({emoji:t,score:e})}return r.sort((e,t)=>t.score-e.score),r.map(e=>e.emoji)}function re(e,t,n,r){let i=Number.isFinite(t)?Math.max(1,Math.trunc(t)):1,a=[],o=[],s=[],c=0,l=0;return e.forEach((e,t)=>{if(e.emojis.length===0){s[t]=-1;return}s[t]=a.length,e.label!==void 0&&(a.push({kind:`header`,label:e.label,section:t}),o.push(c),c+=r);for(let r=0;r<e.emojis.length;r+=i){let s=e.emojis.slice(r,r+i);a.push({kind:`emojis`,section:t,emojis:s,start:l}),o.push(c),c+=n,l+=s.length}}),{rows:a,offsets:o,sectionRows:s,rowHeight:n,headerHeight:r,totalHeight:c}}function b(e,t){return e.rows[t]?.kind===`header`?e.headerHeight:e.rowHeight}function x(e,t,n,r=2){let i=r*e.rowHeight,a=t-i,o=t+n+i,s=0;for(;s<e.rows.length&&e.offsets[s]+b(e,s)<a;)s++;let c=s;for(;c<e.rows.length&&e.offsets[c]<=o;)c++;return[s,c]}function S(e,t,n=1/0){if(t>=n-1&&n>0){for(let t=e.sectionRows.length-1;t>=0;t--)if((e.sectionRows[t]??-1)>=0)return t}let r=-1;for(let n=0;n<e.sectionRows.length;n++){let i=e.sectionRows[n];if(!(i<0)){if(e.offsets[i]<=t+1)r=n;else break}}return r}function ie(e,t){let n=e.sectionRows[t];return n===void 0||n<0?0:e.offsets[n]}function ae(e,t){let n=e.rows[t.row];return n?.kind===`emojis`?n.start+t.col:-1}function C(e,t,n){let r=e.rows[t];return r?.kind===`emojis`?Math.max(0,Math.min(n,r.emojis.length-1)):0}function w(e,t,n){for(let r=t+n;r>=0&&r<e.rows.length;r+=n)if(e.rows[r]?.kind===`emojis`)return r;return null}function oe(e,t){let n=e.rows[t.row];return n?.kind===`emojis`?n.emojis[t.col]??null:null}function se(e){let t=w(e,-1,1);return t===null?null:{row:t,col:0}}function ce(e){let t=w(e,e.rows.length,-1);return t===null?null:{row:t,col:C(e,t,2**53-1)}}function T(e,t,n,r){if(r!==0){let n=t.row;for(let t=0;t<Math.abs(r);t++){let t=w(e,n,Math.sign(r));if(t===null)break;n=t}return{row:n,col:C(e,n,t.col)}}if(n===0)return t;let i=e.rows[t.row];if(i?.kind!==`emojis`)return t;let a=t.col+n;if(a>=0&&a<i.emojis.length)return{row:t.row,col:a};let o=w(e,t.row,Math.sign(n));return o===null?t:{row:o,col:n>0?0:C(e,o,2**53-1)}}function le(e,t,n,r,i=0){let a=e.offsets[t];if(a===void 0)return n;let o=a+b(e,t);return a-i<n?Math.max(0,a-i):o>n+r?Math.max(0,o-r):n}function ue({layout:e,start:t,end:n,active:r,tone:i,listId:a,label:o,emptyLabel:l,stickyLabel:u,scrollRef:d,onScroll:f,onInterrupt:p,onSelect:ee,onHover:m,onLeave:h}){let g=e.rows.slice(t,n),_=e.offsets[t]??0;return/* @__PURE__ */ c(`div`,{className:`ge-list`,id:a,ref:d,role:`listbox`,"aria-label":o,onScroll:f,onWheel:p,onTouchStart:p,onPointerDown:p,onMouseLeave:h,children:[/* @__PURE__ */ s(`div`,{className:`ge-sizer`,style:{height:e.totalHeight},children:/* @__PURE__ */ s(`div`,{className:`ge-window`,style:{transform:`translateY(${_}px)`},children:g.map((e,n)=>{let o=t+n;return e.kind===`header`?/* @__PURE__ */ s(`div`,{className:`ge-header`,children:e.label},`h${o}`):/* @__PURE__ */ s(`div`,{className:`ge-row`,children:e.emojis.map((t,n)=>/* @__PURE__ */ s(E,{id:`${a}-${e.start+n}`,emoji:t,tone:i,active:r?.row===o&&r.col===n,onSelect:ee,onHover:m,row:o,col:n},`${e.start+n}`))},`r${o}`)})})}),e.rows.length===0&&/* @__PURE__ */ s(`p`,{className:`ge-empty`,children:l}),u!==null&&/* @__PURE__ */ s(`div`,{className:`ge-sticky`,children:u})]})}function E({id:t,emoji:n,tone:r,active:i,row:a,col:o,onSelect:c,onHover:l}){let u=n.server;return/* @__PURE__ */ s(`div`,{id:t,role:`option`,"aria-selected":i,className:`ge-cell`,"data-active":i?``:void 0,title:n.label,onMouseEnter:()=>l({row:a,col:o}),onClick:()=>c(n),children:u?/* @__PURE__ */ s(`img`,{src:e(u.id,u.animated),alt:``,loading:`lazy`,draggable:!1}):y(n.value,r)})}const de={search:`Поиск эмодзи`,empty:`Ничего не найдено`,recent:`Недавние`,server:`Сервер`,skinTone:`Тон эмодзи`,skinTones:{light:`Светлый тон`,"medium-light":`Средне-светлый тон`,medium:`Средний тон`,"medium-dark":`Средне-тёмный тон`,dark:`Тёмный тон`}},D={search:`Search emoji`,empty:`No emoji found`,recent:`Recent`,server:`Server`,skinTone:`Emoji skin tone`,skinTones:{light:`Light skin tone`,"medium-light":`Medium-light skin tone`,medium:`Medium skin tone`,"medium-dark":`Medium-dark skin tone`,dark:`Dark skin tone`}};function fe(e){return e.toLowerCase().startsWith(`ru`)?de:D}const O={"smileys-emotion":`😀`,"people-body":`🧑`,"animals-nature":`🐻`,"food-drink":`🍔`,"travel-places":`🚗`,activities:`⚽`,objects:`💡`,symbols:`🔣`,flags:`🏳️`};function pe(e){return O[e]??`•`}const k=`goemoji:tone`;function me({layout:e,sections:t,searching:n,scrollTop:r,viewportHeight:i,maxScroll:a,pinned:o}){let[s,c]=x(e,r,i,3),l=o??(n?-1:S(e,r,a)),u=l>=0?e.sectionRows[l]:void 0;return{start:s,end:c,section:l,sticky:!n&&u!==void 0&&u>=0&&(e.offsets[u]??0)+e.headerHeight<=r+1?t[l]?.label??null:null}}function he(e,t,n){if(typeof window<`u`&&typeof window.matchMedia==`function`&&window.matchMedia(`(prefers-reduced-motion: reduce)`).matches)return e.scrollTop=t,n(),()=>{};let r=e.scrollTop,i=t-r,a=Math.min(260,90+Math.abs(i)*.25),o=performance.now(),s=0,c=t=>{let l=Math.min(1,(t-o)/a),u=1-(1-l)**3;e.scrollTop=r+i*u,n(),l<1&&(s=requestAnimationFrame(c))};return s=requestAnimationFrame(c),()=>cancelAnimationFrame(s)}function ge({data:e,serverEmojis:u,columns:d=8,cellSize:f=34,locale:p=`ru`,labels:m,skinTone:h,onSkinToneChange:g,recentKey:_=`goemoji:recent`,onSelect:v,onEscape:te,serverIconUrl:b,className:x}){let S=Number.isFinite(d)?Math.max(1,Math.trunc(d)):8,C=Number.isFinite(f)?Math.max(1,Math.trunc(f)):34,w=i(()=>({...fe(p),...m}),[p,m]),[E,de]=o(``),[D,O]=o(null),[k,ge]=o(320),A=a(null),j=a(null),[M,N]=o(!1),[xe,Se]=o(()=>_===!1?[]:_e(_)),[Ce,we]=o(ye),P=h??Ce;n(()=>{Se(_===!1?[]:_e(_))},[_]);let F=`ge-list-${r().replace(/[^\w-]/g,``)}`,Te=a(null),I=a(null),L=a(null),R=a(null),z=a(null),B=a(null),V=t(e=>{A.current=e},[]),H=i(()=>(u??[]).map(ee),[u]),U=i(()=>[...e.emojis,...H],[e.emojis,H]),Ee=i(()=>new Map(U.map(e=>[e.value,e])),[U]),W=i(()=>xe.map(e=>Ee.get(e)).filter(e=>e!==void 0),[xe,Ee]),{sections:G,tabs:De}=i(()=>{let t=E.trim();if(t){let e=ne(U,t),n=e.filter(e=>e.server);return{sections:[{emojis:e.filter(e=>!e.server)},...n.length?[{label:w.server,emojis:n}]:[]],tabs:[]}}let n=/* @__PURE__ */ new Map;for(let t of e.emojis){let e=n.get(t.category);e?e.push(t):n.set(t.category,[t])}let r=[],i=[];return H.length&&(r.push({label:w.server,emojis:H}),i.push({icon:`🧩`,title:w.server,...b?{img:b}:{}})),W.length&&(r.push({label:w.recent,emojis:W}),i.push({icon:`🕘`,title:w.recent})),e.categories.forEach((e,t)=>{let a=n.get(t);a?.length&&(r.push({label:e.label,emojis:a}),i.push({icon:pe(e.key),title:e.label}))}),{sections:r,tabs:i}},[E,U,W,e.categories,e.emojis,H,w,b]),K=C+2,q=i(()=>re(G,S,K,26),[G,S,K]),J=E.trim().length>0,[Y,Oe]=o(()=>me({layout:q,sections:G,searching:J,scrollTop:0,viewportHeight:k,maxScroll:0,pinned:null})),X=t(()=>{let e=I.current,t=e?.scrollTop??0,n=e?Math.max(0,e.scrollHeight-e.clientHeight):0,r=A.current;if(r!==null){let e=ie(q,r);(Math.abs(t-e)<=1||t>=n-1)&&(V(null),r=null)}let i=me({layout:q,sections:G,searching:J,scrollTop:t,viewportHeight:k,maxScroll:n,pinned:r});Oe(e=>e.start===i.start&&e.end===i.end&&e.section===i.section&&e.sticky===i.sticky?e:i)},[q,V,J,G,k]),ke=D?ae(q,D):-1,Ae=ke>=0?`${F}-${ke}`:null,je=t(()=>{L.current===null&&(L.current=requestAnimationFrame(()=>{L.current=null,X()}))},[X]);n(()=>()=>{L.current!==null&&cancelAnimationFrame(L.current),j.current?.()},[]),n(()=>{X()},[X]),n(()=>{O(null)},[q]),n(()=>{let e=I.current;if(!e)return;let t=()=>ge(e.clientHeight);t();let n=new ResizeObserver(t);return n.observe(e),()=>n.disconnect()},[]);let Me=t((e=!0)=>{N(!1),e&&R.current?.focus()},[]),Z=t(e=>{let t=z.current?.querySelectorAll(`[role=menuitemradio]`);t?.length&&t[(e+t.length)%t.length]?.focus()},[]);n(()=>{M&&B.current!==null&&(Z(B.current===`first`?0:-1),B.current=null)},[M,Z]),n(()=>{if(!M)return;let e=e=>{Te.current?.contains(e.target)||N(!1)};return document.addEventListener(`pointerdown`,e),()=>document.removeEventListener(`pointerdown`,e)},[M]);let Ne=e=>{let t=Array.from(z.current?.querySelectorAll(`[role=menuitemradio]`)??[]),n=t.indexOf(document.activeElement);switch(e.key){case`ArrowDown`:case`ArrowRight`:e.preventDefault(),e.stopPropagation(),Z(n+1);break;case`ArrowUp`:case`ArrowLeft`:e.preventDefault(),e.stopPropagation(),Z(n-1);break;case`Home`:e.preventDefault(),e.stopPropagation(),Z(0);break;case`End`:e.preventDefault(),e.stopPropagation(),Z(t.length-1);break;case`Escape`:e.preventDefault(),e.stopPropagation(),Me()}},Pe=e=>{if(e.key!==`ArrowDown`&&e.key!==`ArrowUp`)return;e.preventDefault(),e.stopPropagation();let t=e.key===`ArrowDown`?0:-1;if(M){Z(t);return}B.current=e.key===`ArrowDown`?`first`:`last`,N(!0)},Q=t(e=>{let t=I.current;t&&(t.scrollTop=e),X()},[X]),Fe=t(e=>{V(e),X(),j.current?.(),j.current=null;let t=I.current;t&&(j.current=he(t,ie(q,e),X))},[q,V,X]),$=t(()=>{j.current?.(),j.current=null,V(null)},[V]),Ie=t(e=>{v(e),_!==!1&&Se(t=>{let n=[e.value,...t.filter(t=>t!==e.value)].slice(0,24);return ve(_,n),n})},[v,_]),Le=t(e=>{V(null),de(e),O(null),Q(0)},[Q,V]),Re=t(e=>{N(!1),R.current?.focus(),h===void 0&&(we(e),be(e)),g?.(e)},[g,h]);return/* @__PURE__ */ c(`div`,{ref:Te,className:x?`ge-root ${x}`:`ge-root`,style:{"--ge-cell":`${C}px`,"--ge-gap":`2px`,"--ge-row-height":`${K}px`,"--ge-header-height":`26px`,"--ge-columns":S},onKeyDown:e=>{if(e.key===`Escape`){if(M){e.preventDefault(),Me();return}te?.();return}let t=D??se(q);if(!t)return;let n;switch(e.key){case`ArrowLeft`:n=T(q,t,-1,0);break;case`ArrowRight`:n=T(q,t,1,0);break;case`ArrowUp`:n=T(q,t,0,-1);break;case`ArrowDown`:n=T(q,t,0,1);break;case`PageUp`:n=T(q,t,0,-5);break;case`PageDown`:n=T(q,t,0,5);break;case`Home`:n=se(q)??t;break;case`End`:n=ce(q)??t;break;case`Enter`:{let n=oe(q,t);if(!n)return;e.preventDefault(),Ie(n);return}default:return}e.preventDefault(),$(),O(n);let r=I.current?.scrollTop??0,i=le(q,n.row,r,k,q.headerHeight);Math.abs(i-r)>1&&Q(i)},children:[/* @__PURE__ */ c(`div`,{className:`ge-top`,children:[/* @__PURE__ */ s(`input`,{className:`ge-search`,type:`text`,value:E,placeholder:w.search,"aria-label":w.search,role:`combobox`,"aria-expanded":!0,"aria-controls":F,"aria-activedescendant":Ae??void 0,autoComplete:`off`,spellCheck:!1,autoFocus:!0,onChange:e=>Le(e.target.value)}),/* @__PURE__ */ c(`div`,{className:`ge-tone-wrap`,onBlur:e=>{let t=e.relatedTarget;t&&!e.currentTarget.contains(t)&&N(!1)},children:[/* @__PURE__ */ s(`button`,{ref:R,type:`button`,className:`ge-tone`,title:w.skinTone,"aria-label":w.skinTone,"aria-haspopup":`menu`,"aria-expanded":M,onClick:()=>N(e=>!e),onKeyDown:Pe,children:y(`✋`,P)}),M&&/* @__PURE__ */ c(`div`,{ref:z,className:`ge-tone-pop`,role:`menu`,"aria-label":w.skinTone,onKeyDown:Ne,children:[/* @__PURE__ */ s(`button`,{type:`button`,role:`menuitemradio`,"aria-checked":P===`none`,className:`ge-tone-item`,"data-active":P===`none`?``:void 0,onClick:()=>Re(`none`),children:`✋`}),l.map(e=>/* @__PURE__ */ s(`button`,{type:`button`,role:`menuitemradio`,"aria-checked":P===e,className:`ge-tone-item`,"data-active":P===e?``:void 0,title:w.skinTones[e],"aria-label":w.skinTones[e],onClick:()=>Re(e),children:y(`✋`,e)},e))]})]})]}),De.length>0&&/* @__PURE__ */ s(`div`,{className:`ge-tabs`,role:`tablist`,"aria-label":w.search,children:De.map((e,t)=>/* @__PURE__ */ s(`button`,{type:`button`,role:`tab`,"aria-selected":t===Y.section,className:`ge-tab`,"data-active":t===Y.section?``:void 0,title:e.title,onClick:()=>Fe(t),children:e.img?/* @__PURE__ */ s(`img`,{src:e.img,alt:``,draggable:!1}):e.icon},`${e.title}-${t}`))}),/* @__PURE__ */ s(ue,{layout:q,start:Y.start,end:Y.end,active:D,tone:P,listId:F,label:w.search,emptyLabel:w.empty,stickyLabel:Y.sticky,scrollRef:I,onScroll:je,onInterrupt:$,onSelect:Ie,onHover:O,onLeave:()=>O(null)})]})}function A(){if(typeof window>`u`)return!1;try{return window.localStorage!==void 0}catch{return!1}}function _e(e){if(!A())return[];try{let t=localStorage.getItem(e);if(!t)return[];let n=JSON.parse(t);return Array.isArray(n)?n.filter(e=>typeof e==`string`).slice(0,24):[]}catch{return[]}}function ve(e,t){if(A())try{localStorage.setItem(e,JSON.stringify(t))}catch{}}function ye(){if(!A())return`none`;try{let e=localStorage.getItem(k);return e!==null&&j(e)?e:`none`}catch{return`none`}}function be(e){if(A())try{localStorage.setItem(k,e)}catch{}}function j(e){return e===`none`||l.includes(e)}function M(e,t=[]){let[r,i]=o({data:null,error:null,loading:!0}),s=a(e);return n(()=>{s.current=e}),n(()=>{let e=!0;return i({data:null,error:null,loading:!0}),Promise.resolve().then(()=>s.current()).then(t=>{e&&i({data:p(t),error:null,loading:!1})}).catch(t=>{e&&i({data:null,loading:!1,error:t instanceof Error?t:Error(`goemoji: не удалось загрузить данные эмодзи`,{cause:t})})}),()=>{e=!1}},t),r}export{ge as EmojiPicker,p as parseEmojiData,M as useEmojiData};
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/data.ts","../src/EmojiList.tsx","../src/labels.ts","../src/EmojiPicker.tsx","../src/useEmojiData.ts"],"sourcesContent":["export type SkinTone =\n | \"none\"\n | \"light\"\n | \"medium-light\"\n | \"medium\"\n | \"medium-dark\"\n | \"dark\";\n\nexport const SKIN_TONES = [\n \"light\",\n \"medium-light\",\n \"medium\",\n \"medium-dark\",\n \"dark\",\n] as const satisfies readonly Exclude<SkinTone, \"none\">[];\n\nexport const SKIN_TONE_MODIFIERS: Record<Exclude<SkinTone, \"none\">, string> = {\n light: \"\\u{1F3FB}\",\n \"medium-light\": \"\\u{1F3FC}\",\n medium: \"\\u{1F3FD}\",\n \"medium-dark\": \"\\u{1F3FE}\",\n dark: \"\\u{1F3FF}\",\n};\n\nexport type Category = { key: string; label: string };\n\nexport type SlimEmoji = [string, string, string, number];\n\nexport type ServerEmoji = { id: string; name: string; animated: boolean };\n\nexport type Emoji = {\n value: string;\n label: string;\n tags: string;\n category: number;\n server?: { id: string; animated: boolean };\n};\n\nexport type EmojiData = {\n locale: string;\n categories: Category[];\n emojis: Emoji[];\n};\n\nexport const SERVER_CATEGORY = -1;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nfunction unwrapDefault(raw: unknown): unknown {\n if (isRecord(raw) && raw.v === undefined && \"default\" in raw) {\n const inner = raw.default;\n if (isRecord(inner) && inner.v === 1) return inner;\n }\n return raw;\n}\n\nexport function parseEmojiData(input: unknown): EmojiData {\n const raw = unwrapDefault(input);\n if (!isRecord(raw) || raw.v !== 1) {\n throw new Error(\"goemoji: неизвестный формат данных (ожидается v: 1)\");\n }\n if (!Array.isArray(raw.categories) || !Array.isArray(raw.emojis)) {\n throw new Error(\"goemoji: в данных нет categories/emojis\");\n }\n\n const categories: Category[] = raw.categories.map((item, index) => {\n if (!isRecord(item) || typeof item.key !== \"string\" || typeof item.label !== \"string\") {\n throw new Error(`goemoji: категория #${index} повреждена`);\n }\n return { key: item.key, label: item.label };\n });\n\n const emojis: Emoji[] = raw.emojis.map((item, index) => {\n if (\n !Array.isArray(item) ||\n typeof item[0] !== \"string\" ||\n typeof item[1] !== \"string\" ||\n typeof item[2] !== \"string\" ||\n typeof item[3] !== \"number\"\n ) {\n throw new Error(`goemoji: эмодзи #${index} повреждён`);\n }\n const category = item[3];\n if (category < 0 || category >= categories.length) {\n throw new Error(`goemoji: эмодзи #${index} ссылается на неизвестную категорию`);\n }\n if (!item[0]) {\n throw new Error(`goemoji: эмодзи #${index} пустой`);\n }\n return { value: item[0], label: item[1], tags: item[2], category };\n });\n\n return { locale: typeof raw.locale === \"string\" ? raw.locale : \"en\", categories, emojis };\n}\n\nexport function serverEmojiToEmoji(server: ServerEmoji): Emoji {\n const { id, name, animated } = server;\n return {\n value: `<${animated ? \"a\" : \"\"}:${name}:${id}>`,\n label: name,\n tags: `${name.replace(/[_-]+/g, \" \")} ${name}`.toLowerCase(),\n category: SERVER_CATEGORY,\n server: { id, animated },\n };\n}\n\nconst ZWJ = \"\\u200D\";\nconst MODIFIER_BASE = /\\p{Emoji_Modifier_Base}/u;\nconst TRAILING_VS16 = /\\uFE0F$/;\nconst TONE_MODIFIER = /\\u{1F3FB}|\\u{1F3FC}|\\u{1F3FD}|\\u{1F3FE}|\\u{1F3FF}/gu;\n\nexport function supportsSkinTone(emoji: string): boolean {\n return emoji.split(ZWJ).some((segment) => MODIFIER_BASE.test(segment));\n}\n\nexport function stripSkinTone(emoji: string): string {\n return emoji\n .split(ZWJ)\n .map((segment) => segment.replace(TONE_MODIFIER, \"\"))\n .join(ZWJ);\n}\n\nexport function skinToneVariation(emoji: string, tone: SkinTone): string {\n const base = stripSkinTone(emoji);\n if (tone === \"none\" || !supportsSkinTone(base)) {\n return base;\n }\n const modifier = SKIN_TONE_MODIFIERS[tone];\n return base\n .split(ZWJ)\n .map((segment, _index, segments) => {\n if (!MODIFIER_BASE.test(segment) || (segments.length > 1 && segment === \"🤝\")) {\n return segment;\n }\n return segment.replace(TRAILING_VS16, \"\") + modifier;\n })\n .join(ZWJ);\n}\n\nfunction scoreEmoji(emoji: Emoji, needle: string): number {\n const label = emoji.label.toLowerCase();\n if (label === needle) return 100;\n if (label.startsWith(needle)) return 60;\n if (label.includes(needle)) return 40;\n\n for (const tag of emoji.tags.split(\" \")) {\n if (!tag) continue;\n if (tag === needle) return 25;\n if (tag.startsWith(needle)) return 15;\n if (tag.includes(needle)) return 8;\n }\n return 0;\n}\n\nexport function searchEmojis(emojis: readonly Emoji[], query: string): Emoji[] {\n const needle = query.trim().toLowerCase();\n if (!needle) return [...emojis];\n\n const found: { emoji: Emoji; score: number }[] = [];\n for (const emoji of emojis) {\n const score = scoreEmoji(emoji, needle);\n if (score > 0) found.push({ emoji, score });\n }\n found.sort((a, b) => b.score - a.score);\n return found.map((item) => item.emoji);\n}\n\nexport type Section = {\n label?: string;\n emojis: readonly Emoji[];\n};\n\nexport type LayoutRow =\n | { kind: \"header\"; label: string; section: number }\n | { kind: \"emojis\"; section: number; emojis: readonly Emoji[]; start: number };\n\nexport type Layout = {\n rows: LayoutRow[];\n offsets: number[];\n sectionRows: number[];\n rowHeight: number;\n headerHeight: number;\n totalHeight: number;\n};\n\nexport function buildLayout(\n sections: readonly Section[],\n columns: number,\n rowHeight: number,\n headerHeight: number,\n): Layout {\n const cols = Number.isFinite(columns) ? Math.max(1, Math.trunc(columns)) : 1;\n const rows: LayoutRow[] = [];\n const offsets: number[] = [];\n const sectionRows: number[] = [];\n let y = 0;\n let flat = 0;\n\n sections.forEach((section, sectionIndex) => {\n if (section.emojis.length === 0) {\n sectionRows[sectionIndex] = -1;\n return;\n }\n sectionRows[sectionIndex] = rows.length;\n if (section.label !== undefined) {\n rows.push({ kind: \"header\", label: section.label, section: sectionIndex });\n offsets.push(y);\n y += headerHeight;\n }\n for (let start = 0; start < section.emojis.length; start += cols) {\n const emojis = section.emojis.slice(start, start + cols);\n rows.push({\n kind: \"emojis\",\n section: sectionIndex,\n emojis,\n start: flat,\n });\n offsets.push(y);\n y += rowHeight;\n flat += emojis.length;\n }\n });\n\n return {\n rows,\n offsets,\n sectionRows,\n rowHeight,\n headerHeight,\n totalHeight: y,\n };\n}\n\nfunction rowHeight(layout: Layout, row: number): number {\n return layout.rows[row]?.kind === \"header\" ? layout.headerHeight : layout.rowHeight;\n}\n\nexport function visibleRange(\n layout: Layout,\n scrollTop: number,\n viewportHeight: number,\n overscan = 2,\n): [start: number, end: number] {\n const margin = overscan * layout.rowHeight;\n const from = scrollTop - margin;\n const to = scrollTop + viewportHeight + margin;\n\n let start = 0;\n while (start < layout.rows.length && layout.offsets[start]! + rowHeight(layout, start) < from) {\n start++;\n }\n let end = start;\n while (end < layout.rows.length && layout.offsets[end]! <= to) {\n end++;\n }\n return [start, end];\n}\n\nexport function sectionAt(\n layout: Layout,\n scrollTop: number,\n maxScroll = Number.POSITIVE_INFINITY,\n): number {\n if (scrollTop >= maxScroll - 1 && maxScroll > 0) {\n for (let index = layout.sectionRows.length - 1; index >= 0; index--) {\n if ((layout.sectionRows[index] ?? -1) >= 0) return index;\n }\n }\n\n let current = -1;\n for (let index = 0; index < layout.sectionRows.length; index++) {\n const row = layout.sectionRows[index]!;\n if (row < 0) continue;\n if (layout.offsets[row]! <= scrollTop + 1) current = index;\n else break;\n }\n return current;\n}\n\nexport function sectionOffset(layout: Layout, section: number): number {\n const row = layout.sectionRows[section];\n return row === undefined || row < 0 ? 0 : layout.offsets[row]!;\n}\n\nexport type Cell = { row: number; col: number };\n\nexport function flatIndexAt(layout: Layout, cell: Cell): number {\n const row = layout.rows[cell.row];\n return row?.kind === \"emojis\" ? row.start + cell.col : -1;\n}\n\nfunction clampCol(layout: Layout, row: number, col: number): number {\n const layoutRow = layout.rows[row];\n if (layoutRow?.kind !== \"emojis\") return 0;\n return Math.max(0, Math.min(col, layoutRow.emojis.length - 1));\n}\n\nfunction adjacentEmojiRow(layout: Layout, row: number, direction: number): number | null {\n for (let next = row + direction; next >= 0 && next < layout.rows.length; next += direction) {\n if (layout.rows[next]?.kind === \"emojis\") return next;\n }\n return null;\n}\n\nexport function emojiAt(layout: Layout, cell: Cell): Emoji | null {\n const row = layout.rows[cell.row];\n if (row?.kind !== \"emojis\") return null;\n return row.emojis[cell.col] ?? null;\n}\n\nexport function firstCell(layout: Layout): Cell | null {\n const row = adjacentEmojiRow(layout, -1, 1);\n return row === null ? null : { row, col: 0 };\n}\n\nexport function lastCell(layout: Layout): Cell | null {\n const row = adjacentEmojiRow(layout, layout.rows.length, -1);\n return row === null ? null : { row, col: clampCol(layout, row, Number.MAX_SAFE_INTEGER) };\n}\n\nexport function moveActive(layout: Layout, active: Cell, dx: number, dy: number): Cell {\n if (dy !== 0) {\n let row = active.row;\n for (let step = 0; step < Math.abs(dy); step++) {\n const next = adjacentEmojiRow(layout, row, Math.sign(dy));\n if (next === null) break;\n row = next;\n }\n return { row, col: clampCol(layout, row, active.col) };\n }\n if (dx === 0) return active;\n\n const row = layout.rows[active.row];\n if (row?.kind !== \"emojis\") return active;\n\n const next = active.col + dx;\n if (next >= 0 && next < row.emojis.length) {\n return { row: active.row, col: next };\n }\n const adjacent = adjacentEmojiRow(layout, active.row, Math.sign(dx));\n if (adjacent === null) return active;\n return { row: adjacent, col: dx > 0 ? 0 : clampCol(layout, adjacent, Number.MAX_SAFE_INTEGER) };\n}\n\nexport function scrollToShowRow(\n layout: Layout,\n row: number,\n scrollTop: number,\n viewportHeight: number,\n stickyHeight = 0,\n): number {\n const top = layout.offsets[row];\n if (top === undefined) return scrollTop;\n const bottom = top + rowHeight(layout, row);\n if (top - stickyHeight < scrollTop) return Math.max(0, top - stickyHeight);\n if (bottom > scrollTop + viewportHeight) return Math.max(0, bottom - viewportHeight);\n return scrollTop;\n}\n","\"use client\";\n\nimport type { RefObject } from \"react\";\nimport { customEmojiUrl } from \"./discord.ts\";\nimport { skinToneVariation, type Cell, type Emoji, type Layout, type SkinTone } from \"./data.ts\";\n\ntype EmojiListProps = {\n layout: Layout;\n start: number;\n end: number;\n active: Cell | null;\n tone: SkinTone;\n listId: string;\n label: string;\n emptyLabel: string;\n stickyLabel: string | null;\n scrollRef: RefObject<HTMLDivElement | null>;\n onScroll: () => void;\n onInterrupt: () => void;\n onSelect: (emoji: Emoji) => void;\n onHover: (cell: Cell) => void;\n onLeave: () => void;\n};\n\nexport function EmojiList({\n layout,\n start,\n end,\n active,\n tone,\n listId,\n label,\n emptyLabel,\n stickyLabel,\n scrollRef,\n onScroll,\n onInterrupt,\n onSelect,\n onHover,\n onLeave,\n}: EmojiListProps) {\n const rows = layout.rows.slice(start, end);\n const windowTop = layout.offsets[start] ?? 0;\n\n return (\n <div\n className=\"ge-list\"\n id={listId}\n ref={scrollRef}\n role=\"listbox\"\n aria-label={label}\n onScroll={onScroll}\n onWheel={onInterrupt}\n onTouchStart={onInterrupt}\n onPointerDown={onInterrupt}\n onMouseLeave={onLeave}\n >\n <div className=\"ge-sizer\" style={{ height: layout.totalHeight }}>\n <div className=\"ge-window\" style={{ transform: `translateY(${windowTop}px)` }}>\n {rows.map((row, index) => {\n const rowIndex = start + index;\n if (row.kind === \"header\") {\n return (\n <div className=\"ge-header\" key={`h${rowIndex}`}>\n {row.label}\n </div>\n );\n }\n return (\n <div className=\"ge-row\" key={`r${rowIndex}`}>\n {row.emojis.map((emoji, col) => (\n <EmojiCell\n key={`${row.start + col}`}\n id={`${listId}-${row.start + col}`}\n emoji={emoji}\n tone={tone}\n active={active?.row === rowIndex && active.col === col}\n onSelect={onSelect}\n onHover={onHover}\n row={rowIndex}\n col={col}\n />\n ))}\n </div>\n );\n })}\n </div>\n </div>\n {layout.rows.length === 0 && <p className=\"ge-empty\">{emptyLabel}</p>}\n {stickyLabel !== null && <div className=\"ge-sticky\">{stickyLabel}</div>}\n </div>\n );\n}\n\ntype EmojiCellProps = {\n id: string;\n emoji: Emoji;\n tone: SkinTone;\n active: boolean;\n row: number;\n col: number;\n onSelect: (emoji: Emoji) => void;\n onHover: (cell: Cell) => void;\n};\n\nfunction EmojiCell({ id, emoji, tone, active, row, col, onSelect, onHover }: EmojiCellProps) {\n const server = emoji.server;\n return (\n <div\n id={id}\n role=\"option\"\n aria-selected={active}\n className=\"ge-cell\"\n data-active={active ? \"\" : undefined}\n title={emoji.label}\n onMouseEnter={() => onHover({ row, col })}\n onClick={() => onSelect(emoji)}\n >\n {server ? (\n <img src={customEmojiUrl(server.id, server.animated)} alt=\"\" loading=\"lazy\" draggable={false} />\n ) : (\n skinToneVariation(emoji.value, tone)\n )}\n </div>\n );\n}\n","import type { SkinTone } from \"./data.ts\";\n\nexport type Labels = {\n search: string;\n empty: string;\n recent: string;\n server: string;\n skinTone: string;\n skinTones: Record<Exclude<SkinTone, \"none\">, string>;\n};\n\nconst RU: Labels = {\n search: \"Поиск эмодзи\",\n empty: \"Ничего не найдено\",\n recent: \"Недавние\",\n server: \"Сервер\",\n skinTone: \"Тон эмодзи\",\n skinTones: {\n light: \"Светлый тон\",\n \"medium-light\": \"Средне-светлый тон\",\n medium: \"Средний тон\",\n \"medium-dark\": \"Средне-тёмный тон\",\n dark: \"Тёмный тон\",\n },\n};\n\nconst EN: Labels = {\n search: \"Search emoji\",\n empty: \"No emoji found\",\n recent: \"Recent\",\n server: \"Server\",\n skinTone: \"Emoji skin tone\",\n skinTones: {\n light: \"Light skin tone\",\n \"medium-light\": \"Medium-light skin tone\",\n medium: \"Medium skin tone\",\n \"medium-dark\": \"Medium-dark skin tone\",\n dark: \"Dark skin tone\",\n },\n};\n\nexport function labelsFor(locale: string): Labels {\n return locale.toLowerCase().startsWith(\"ru\") ? RU : EN;\n}\n\nconst CATEGORY_ICONS: Record<string, string> = {\n \"smileys-emotion\": \"😀\",\n \"people-body\": \"🧑\",\n \"animals-nature\": \"🐻\",\n \"food-drink\": \"🍔\",\n \"travel-places\": \"🚗\",\n activities: \"⚽\",\n objects: \"💡\",\n symbols: \"🔣\",\n flags: \"🏳️\",\n};\n\nexport function categoryIcon(key: string): string {\n return CATEGORY_ICONS[key] ?? \"•\";\n}\n\nexport const SERVER_TAB_ICON = \"🧩\";\n\nexport const RECENT_TAB_ICON = \"🕘\";\n\nexport const GRID_GAP = 2;\n\nexport const SKIN_TONE_BASE = \"✋\";\n","\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n type CSSProperties,\n type KeyboardEvent as ReactKeyboardEvent,\n} from \"react\";\nimport {\n buildLayout,\n emojiAt,\n firstCell,\n flatIndexAt,\n lastCell,\n moveActive,\n scrollToShowRow,\n searchEmojis,\n sectionAt,\n sectionOffset,\n serverEmojiToEmoji,\n skinToneVariation,\n SKIN_TONES,\n visibleRange,\n type Cell,\n type Emoji,\n type EmojiData,\n type Layout,\n type Section,\n type ServerEmoji,\n type SkinTone,\n} from \"./data.ts\";\nimport { EmojiList } from \"./EmojiList.tsx\";\nimport {\n GRID_GAP,\n RECENT_TAB_ICON,\n SERVER_TAB_ICON,\n SKIN_TONE_BASE,\n categoryIcon,\n labelsFor,\n type Labels,\n} from \"./labels.ts\";\n\nconst TONE_STORAGE_KEY = \"goemoji:tone\";\nconst DEFAULT_RECENT_KEY = \"goemoji:recent\";\nconst DEFAULT_COLUMNS = 8;\nconst DEFAULT_CELL_SIZE = 34;\nconst HEADER_HEIGHT = 26;\nconst MAX_RECENT = 24;\nconst PAGE_ROWS = 5;\nconst OVERSCAN = 3;\n\ntype ViewState = {\n start: number;\n end: number;\n section: number;\n sticky: string | null;\n};\n\ntype ViewInput = {\n layout: Layout;\n sections: readonly Section[];\n searching: boolean;\n scrollTop: number;\n viewportHeight: number;\n maxScroll: number;\n pinned: number | null;\n};\n\nfunction computeView({\n layout,\n sections,\n searching,\n scrollTop,\n viewportHeight,\n maxScroll,\n pinned,\n}: ViewInput): ViewState {\n const [start, end] = visibleRange(layout, scrollTop, viewportHeight, OVERSCAN);\n const section = pinned ?? (searching ? -1 : sectionAt(layout, scrollTop, maxScroll));\n const headerRow = section >= 0 ? layout.sectionRows[section] : undefined;\n const sticky =\n !searching &&\n headerRow !== undefined &&\n headerRow >= 0 &&\n (layout.offsets[headerRow] ?? 0) + layout.headerHeight <= scrollTop + 1\n ? (sections[section]?.label ?? null)\n : null;\n return { start, end, section, sticky };\n}\n\nfunction glideTo(element: HTMLElement, to: number, onFrame: () => void): () => void {\n const reduceMotion =\n typeof window !== \"undefined\" &&\n typeof window.matchMedia === \"function\" &&\n window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n if (reduceMotion) {\n element.scrollTop = to;\n onFrame();\n return () => {};\n }\n\n const from = element.scrollTop;\n const distance = to - from;\n const duration = Math.min(260, 90 + Math.abs(distance) * 0.25);\n const start = performance.now();\n let frame = 0;\n\n const step = (now: number): void => {\n const progress = Math.min(1, (now - start) / duration);\n const eased = 1 - (1 - progress) ** 3;\n element.scrollTop = from + distance * eased;\n onFrame();\n if (progress < 1) frame = requestAnimationFrame(step);\n };\n\n frame = requestAnimationFrame(step);\n return () => cancelAnimationFrame(frame);\n}\n\ntype TabMeta = { icon: string; title: string; img?: string | undefined };\n\nexport type EmojiPickerProps = {\n data: EmojiData;\n serverEmojis?: readonly ServerEmoji[] | undefined;\n columns?: number | undefined;\n cellSize?: number | undefined;\n locale?: string | undefined;\n labels?: Partial<Labels> | undefined;\n skinTone?: SkinTone | undefined;\n onSkinToneChange?: ((tone: SkinTone) => void) | undefined;\n recentKey?: string | false | undefined;\n onSelect: (emoji: Emoji) => void;\n onEscape?: (() => void) | undefined;\n serverIconUrl?: string | undefined;\n className?: string | undefined;\n};\n\nexport function EmojiPicker({\n data,\n serverEmojis,\n columns = DEFAULT_COLUMNS,\n cellSize = DEFAULT_CELL_SIZE,\n locale = \"ru\",\n labels: labelOverrides,\n skinTone,\n onSkinToneChange,\n recentKey = DEFAULT_RECENT_KEY,\n onSelect,\n onEscape,\n serverIconUrl,\n className,\n}: EmojiPickerProps) {\n const gridColumns = Number.isFinite(columns) ? Math.max(1, Math.trunc(columns)) : DEFAULT_COLUMNS;\n const gridCellSize = Number.isFinite(cellSize)\n ? Math.max(1, Math.trunc(cellSize))\n : DEFAULT_CELL_SIZE;\n\n const labels = useMemo<Labels>(\n () => ({ ...labelsFor(locale), ...labelOverrides }),\n [locale, labelOverrides],\n );\n\n const [query, setQuery] = useState(\"\");\n const [active, setActive] = useState<Cell | null>(null);\n const [viewportHeight, setViewportHeight] = useState(320);\n const pinnedRef = useRef<number | null>(null);\n const glideRef = useRef<(() => void) | null>(null);\n const [toneOpen, setToneOpen] = useState(false);\n const [recent, setRecent] = useState<string[]>(() =>\n recentKey === false ? [] : readRecent(recentKey),\n );\n const [internalTone, setInternalTone] = useState<SkinTone>(readTone);\n const tone = skinTone ?? internalTone;\n\n useEffect(() => {\n setRecent(recentKey === false ? [] : readRecent(recentKey));\n }, [recentKey]);\n\n const generatedId = useId().replace(/[^\\w-]/g, \"\");\n const listId = `ge-list-${generatedId}`;\n const rootRef = useRef<HTMLDivElement>(null);\n const scrollRef = useRef<HTMLDivElement>(null);\n const frameRef = useRef<number | null>(null);\n const toneButtonRef = useRef<HTMLButtonElement>(null);\n const tonePopRef = useRef<HTMLDivElement>(null);\n const toneFocusRef = useRef<\"first\" | \"last\" | null>(null);\n\n const pinSection = useCallback((index: number | null) => {\n pinnedRef.current = index;\n }, []);\n\n const serverEntries = useMemo(\n () => (serverEmojis ?? []).map(serverEmojiToEmoji),\n [serverEmojis],\n );\n const merged = useMemo(\n () => [...data.emojis, ...serverEntries],\n [data.emojis, serverEntries],\n );\n const emojiByValue = useMemo(\n () => new Map(merged.map((emoji) => [emoji.value, emoji])),\n [merged],\n );\n const recentEmojis = useMemo(\n () =>\n recent\n .map((value) => emojiByValue.get(value))\n .filter((emoji): emoji is Emoji => emoji !== undefined),\n [recent, emojiByValue],\n );\n\n const { sections, tabs } = useMemo(() => {\n const needle = query.trim();\n if (needle) {\n const hits = searchEmojis(merged, needle);\n const server = hits.filter((emoji) => emoji.server);\n const unicode = hits.filter((emoji) => !emoji.server);\n return {\n sections: [\n { emojis: unicode },\n ...(server.length ? [{ label: labels.server, emojis: server }] : []),\n ] as Section[],\n tabs: [] as TabMeta[],\n };\n }\n\n const byCategory = new Map<number, Emoji[]>();\n for (const emoji of data.emojis) {\n const list = byCategory.get(emoji.category);\n if (list) list.push(emoji);\n else byCategory.set(emoji.category, [emoji]);\n }\n\n const nextSections: Section[] = [];\n const nextTabs: TabMeta[] = [];\n if (serverEntries.length) {\n nextSections.push({ label: labels.server, emojis: serverEntries });\n nextTabs.push({\n icon: SERVER_TAB_ICON,\n title: labels.server,\n ...(serverIconUrl ? { img: serverIconUrl } : {}),\n });\n }\n if (recentEmojis.length) {\n nextSections.push({ label: labels.recent, emojis: recentEmojis });\n nextTabs.push({ icon: RECENT_TAB_ICON, title: labels.recent });\n }\n data.categories.forEach((category, index) => {\n const list = byCategory.get(index);\n if (!list?.length) return;\n nextSections.push({ label: category.label, emojis: list });\n nextTabs.push({ icon: categoryIcon(category.key), title: category.label });\n });\n return { sections: nextSections, tabs: nextTabs };\n }, [query, merged, recentEmojis, data.categories, data.emojis, serverEntries, labels, serverIconUrl]);\n\n const rowHeight = gridCellSize + GRID_GAP;\n const layout = useMemo(\n () => buildLayout(sections, gridColumns, rowHeight, HEADER_HEIGHT),\n [sections, gridColumns, rowHeight],\n );\n\n const searching = query.trim().length > 0;\n\n const [view, setView] = useState<ViewState>(() =>\n computeView({ layout, sections, searching, scrollTop: 0, viewportHeight, maxScroll: 0, pinned: null }),\n );\n\n const syncView = useCallback(() => {\n const element = scrollRef.current;\n const scrollTop = element?.scrollTop ?? 0;\n const maxScroll = element ? Math.max(0, element.scrollHeight - element.clientHeight) : 0;\n const current = pinnedRef.current;\n\n let active = current;\n if (active !== null) {\n const target = sectionOffset(layout, active);\n if (Math.abs(scrollTop - target) <= 1 || scrollTop >= maxScroll - 1) {\n pinSection(null);\n active = null;\n }\n }\n\n const next = computeView({\n layout,\n sections,\n searching,\n scrollTop,\n viewportHeight,\n maxScroll,\n pinned: active,\n });\n setView((previous) =>\n previous.start === next.start &&\n previous.end === next.end &&\n previous.section === next.section &&\n previous.sticky === next.sticky\n ? previous\n : next,\n );\n }, [layout, pinSection, searching, sections, viewportHeight]);\n\n const activeFlat = active ? flatIndexAt(layout, active) : -1;\n const activeId = activeFlat >= 0 ? `${listId}-${activeFlat}` : null;\n\n const onScroll = useCallback(() => {\n if (frameRef.current !== null) return;\n frameRef.current = requestAnimationFrame(() => {\n frameRef.current = null;\n syncView();\n });\n }, [syncView]);\n\n useEffect(() => () => {\n if (frameRef.current !== null) cancelAnimationFrame(frameRef.current);\n glideRef.current?.();\n }, []);\n\n useEffect(() => {\n syncView();\n }, [syncView]);\n\n useEffect(() => {\n setActive(null);\n }, [layout]);\n\n useEffect(() => {\n const element = scrollRef.current;\n if (!element) return;\n const measure = () => setViewportHeight(element.clientHeight);\n measure();\n const observer = new ResizeObserver(measure);\n observer.observe(element);\n return () => observer.disconnect();\n }, []);\n\n const closeTone = useCallback((restoreFocus = true) => {\n setToneOpen(false);\n if (restoreFocus) toneButtonRef.current?.focus();\n }, []);\n\n const focusToneItem = useCallback((index: number) => {\n const items = tonePopRef.current?.querySelectorAll<HTMLButtonElement>(\"[role=menuitemradio]\");\n if (!items?.length) return;\n items[(index + items.length) % items.length]?.focus();\n }, []);\n\n useEffect(() => {\n if (toneOpen && toneFocusRef.current !== null) {\n focusToneItem(toneFocusRef.current === \"first\" ? 0 : -1);\n toneFocusRef.current = null;\n }\n }, [toneOpen, focusToneItem]);\n\n useEffect(() => {\n if (!toneOpen) return;\n const onPointerDown = (event: PointerEvent) => {\n if (!rootRef.current?.contains(event.target as Node)) setToneOpen(false);\n };\n document.addEventListener(\"pointerdown\", onPointerDown);\n return () => document.removeEventListener(\"pointerdown\", onPointerDown);\n }, [toneOpen]);\n\n const onToneKeyDown = (event: ReactKeyboardEvent<HTMLDivElement>) => {\n const items = Array.from(\n tonePopRef.current?.querySelectorAll<HTMLButtonElement>(\"[role=menuitemradio]\") ?? [],\n );\n const current = items.indexOf(document.activeElement as HTMLButtonElement);\n switch (event.key) {\n case \"ArrowDown\":\n case \"ArrowRight\":\n event.preventDefault();\n event.stopPropagation();\n focusToneItem(current + 1);\n break;\n case \"ArrowUp\":\n case \"ArrowLeft\":\n event.preventDefault();\n event.stopPropagation();\n focusToneItem(current - 1);\n break;\n case \"Home\":\n event.preventDefault();\n event.stopPropagation();\n focusToneItem(0);\n break;\n case \"End\":\n event.preventDefault();\n event.stopPropagation();\n focusToneItem(items.length - 1);\n break;\n case \"Escape\":\n event.preventDefault();\n event.stopPropagation();\n closeTone();\n break;\n default:\n break;\n }\n };\n\n const onToneButtonKeyDown = (event: ReactKeyboardEvent<HTMLButtonElement>) => {\n if (event.key !== \"ArrowDown\" && event.key !== \"ArrowUp\") return;\n event.preventDefault();\n event.stopPropagation();\n const target = event.key === \"ArrowDown\" ? 0 : -1;\n if (toneOpen) {\n focusToneItem(target);\n return;\n }\n toneFocusRef.current = event.key === \"ArrowDown\" ? \"first\" : \"last\";\n setToneOpen(true);\n };\n\n const jumpTo = useCallback(\n (top: number) => {\n const element = scrollRef.current;\n if (element) element.scrollTop = top;\n syncView();\n },\n [syncView],\n );\n\n const selectSection = useCallback(\n (index: number) => {\n pinSection(index);\n syncView();\n glideRef.current?.();\n glideRef.current = null;\n const element = scrollRef.current;\n if (element) glideRef.current = glideTo(element, sectionOffset(layout, index), syncView);\n },\n [layout, pinSection, syncView],\n );\n\n const stopGlide = useCallback(() => {\n glideRef.current?.();\n glideRef.current = null;\n pinSection(null);\n }, [pinSection]);\n\n const select = useCallback(\n (emoji: Emoji) => {\n onSelect(emoji);\n if (recentKey === false) return;\n setRecent((previous) => {\n const next = [emoji.value, ...previous.filter((value) => value !== emoji.value)].slice(\n 0,\n MAX_RECENT,\n );\n writeRecent(recentKey, next);\n return next;\n });\n },\n [onSelect, recentKey],\n );\n\n const changeQuery = useCallback(\n (value: string) => {\n pinSection(null);\n setQuery(value);\n setActive(null);\n jumpTo(0);\n },\n [jumpTo, pinSection],\n );\n\n const changeTone = useCallback(\n (next: SkinTone) => {\n setToneOpen(false);\n toneButtonRef.current?.focus();\n if (skinTone === undefined) {\n setInternalTone(next);\n writeTone(next);\n }\n onSkinToneChange?.(next);\n },\n [onSkinToneChange, skinTone],\n );\n\n const onKeyDown = (event: ReactKeyboardEvent<HTMLDivElement>) => {\n if (event.key === \"Escape\") {\n if (toneOpen) {\n event.preventDefault();\n closeTone();\n return;\n }\n onEscape?.();\n return;\n }\n const current = active ?? firstCell(layout);\n if (!current) return;\n\n let next: Cell;\n switch (event.key) {\n case \"ArrowLeft\":\n next = moveActive(layout, current, -1, 0);\n break;\n case \"ArrowRight\":\n next = moveActive(layout, current, 1, 0);\n break;\n case \"ArrowUp\":\n next = moveActive(layout, current, 0, -1);\n break;\n case \"ArrowDown\":\n next = moveActive(layout, current, 0, 1);\n break;\n case \"PageUp\":\n next = moveActive(layout, current, 0, -PAGE_ROWS);\n break;\n case \"PageDown\":\n next = moveActive(layout, current, 0, PAGE_ROWS);\n break;\n case \"Home\":\n next = firstCell(layout) ?? current;\n break;\n case \"End\":\n next = lastCell(layout) ?? current;\n break;\n case \"Enter\": {\n const emoji = emojiAt(layout, current);\n if (!emoji) return;\n event.preventDefault();\n select(emoji);\n return;\n }\n default:\n return;\n }\n\n event.preventDefault();\n stopGlide();\n setActive(next);\n const from = scrollRef.current?.scrollTop ?? 0;\n const top = scrollToShowRow(layout, next.row, from, viewportHeight, layout.headerHeight);\n if (Math.abs(top - from) > 1) jumpTo(top);\n };\n\n return (\n <div\n ref={rootRef}\n className={className ? `ge-root ${className}` : \"ge-root\"}\n style={\n {\n \"--ge-cell\": `${gridCellSize}px`,\n \"--ge-gap\": `${GRID_GAP}px`,\n \"--ge-row-height\": `${rowHeight}px`,\n \"--ge-header-height\": `${HEADER_HEIGHT}px`,\n \"--ge-columns\": gridColumns,\n } as CSSProperties\n }\n onKeyDown={onKeyDown}\n >\n <div className=\"ge-top\">\n <input\n className=\"ge-search\"\n type=\"text\"\n value={query}\n placeholder={labels.search}\n aria-label={labels.search}\n role=\"combobox\"\n aria-expanded\n aria-controls={listId}\n aria-activedescendant={activeId ?? undefined}\n autoComplete=\"off\"\n spellCheck={false}\n autoFocus\n onChange={(event) => changeQuery(event.target.value)}\n />\n <div\n className=\"ge-tone-wrap\"\n onBlur={(event) => {\n const next = event.relatedTarget as Node | null;\n if (next && !event.currentTarget.contains(next)) setToneOpen(false);\n }}\n >\n <button\n ref={toneButtonRef}\n type=\"button\"\n className=\"ge-tone\"\n title={labels.skinTone}\n aria-label={labels.skinTone}\n aria-haspopup=\"menu\"\n aria-expanded={toneOpen}\n onClick={() => setToneOpen((open) => !open)}\n onKeyDown={onToneButtonKeyDown}\n >\n {skinToneVariation(SKIN_TONE_BASE, tone)}\n </button>\n {toneOpen && (\n <div\n ref={tonePopRef}\n className=\"ge-tone-pop\"\n role=\"menu\"\n aria-label={labels.skinTone}\n onKeyDown={onToneKeyDown}\n >\n <button\n type=\"button\"\n role=\"menuitemradio\"\n aria-checked={tone === \"none\"}\n className=\"ge-tone-item\"\n data-active={tone === \"none\" ? \"\" : undefined}\n onClick={() => changeTone(\"none\")}\n >\n {SKIN_TONE_BASE}\n </button>\n {SKIN_TONES.map((value) => (\n <button\n key={value}\n type=\"button\"\n role=\"menuitemradio\"\n aria-checked={tone === value}\n className=\"ge-tone-item\"\n data-active={tone === value ? \"\" : undefined}\n title={labels.skinTones[value]}\n aria-label={labels.skinTones[value]}\n onClick={() => changeTone(value)}\n >\n {skinToneVariation(SKIN_TONE_BASE, value)}\n </button>\n ))}\n </div>\n )}\n </div>\n </div>\n\n {tabs.length > 0 && (\n <div className=\"ge-tabs\" role=\"tablist\" aria-label={labels.search}>\n {tabs.map((tab, index) => (\n <button\n key={`${tab.title}-${index}`}\n type=\"button\"\n role=\"tab\"\n aria-selected={index === view.section}\n className=\"ge-tab\"\n data-active={index === view.section ? \"\" : undefined}\n title={tab.title}\n onClick={() => selectSection(index)}\n >\n {tab.img ? <img src={tab.img} alt=\"\" draggable={false} /> : tab.icon}\n </button>\n ))}\n </div>\n )}\n\n <EmojiList\n layout={layout}\n start={view.start}\n end={view.end}\n active={active}\n tone={tone}\n listId={listId}\n label={labels.search}\n emptyLabel={labels.empty}\n stickyLabel={view.sticky}\n scrollRef={scrollRef}\n onScroll={onScroll}\n onInterrupt={stopGlide}\n onSelect={select}\n onHover={setActive}\n onLeave={() => setActive(null)}\n />\n </div>\n );\n}\n\nfunction hasStorage(): boolean {\n if (typeof window === \"undefined\") return false;\n try {\n return typeof window.localStorage !== \"undefined\";\n } catch {\n return false;\n }\n}\n\nfunction readRecent(key: string): string[] {\n if (!hasStorage()) return [];\n try {\n const raw = localStorage.getItem(key);\n if (!raw) return [];\n const parsed: unknown = JSON.parse(raw);\n if (!Array.isArray(parsed)) return [];\n return parsed.filter((value): value is string => typeof value === \"string\").slice(0, MAX_RECENT);\n } catch {\n return [];\n }\n}\n\nfunction writeRecent(key: string, values: string[]): void {\n if (!hasStorage()) return;\n try {\n localStorage.setItem(key, JSON.stringify(values));\n } catch {\n }\n}\n\nfunction readTone(): SkinTone {\n if (!hasStorage()) return \"none\";\n try {\n const raw = localStorage.getItem(TONE_STORAGE_KEY);\n return raw !== null && isSkinTone(raw) ? raw : \"none\";\n } catch {\n return \"none\";\n }\n}\n\nfunction writeTone(tone: SkinTone): void {\n if (!hasStorage()) return;\n try {\n localStorage.setItem(TONE_STORAGE_KEY, tone);\n } catch {\n }\n}\n\nfunction isSkinTone(value: string): value is SkinTone {\n return value === \"none\" || (SKIN_TONES as readonly string[]).includes(value);\n}\n","\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport { parseEmojiData, type EmojiData } from \"./data.ts\";\n\nexport type UseEmojiDataResult = {\n data: EmojiData | null;\n error: Error | null;\n loading: boolean;\n};\n\nexport function useEmojiData(\n load: () => Promise<unknown>,\n deps: readonly unknown[] = [],\n): UseEmojiDataResult {\n const [result, setResult] = useState<UseEmojiDataResult>({\n data: null,\n error: null,\n loading: true,\n });\n const loadRef = useRef(load);\n\n useEffect(() => {\n loadRef.current = load;\n });\n\n useEffect(() => {\n let alive = true;\n setResult({ data: null, error: null, loading: true });\n Promise.resolve()\n .then(() => loadRef.current())\n .then((raw) => {\n if (alive) setResult({ data: parseEmojiData(raw), error: null, loading: false });\n })\n .catch((cause: unknown) => {\n if (!alive) return;\n setResult({\n data: null,\n loading: false,\n error:\n cause instanceof Error\n ? cause\n : new Error(\"goemoji: не удалось загрузить данные эмодзи\", { cause }),\n });\n });\n return () => {\n alive = false;\n };\n }, deps);\n\n return result;\n}\n"],"mappings":"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"}
|
package/dist/styles.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.ge-root{--ge-bg:#1e1f22;--ge-panel:#2b2d31;--ge-surface:#232428;--ge-border:#1a1b1e;--ge-text:#dbdee1;--ge-muted:#949ba4;--ge-hover:#ffffff0f;--ge-active:#5865f266;--ge-accent:#5865f2;--ge-radius:6px;--ge-list-height:320px;--ge-font:system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;--ge-font-emoji:"Apple Color Emoji", "Noto Color Emoji", "Twemoji Mozilla", "Android Emoji", "Segoe UI Emoji", "Segoe UI Symbol", EmojiSymbols, sans-serif;background:var(--ge-bg);border:1px solid var(--ge-border);border-radius:var(--ge-radius);width:100%;min-width:0;color:var(--ge-text);font-family:var(--ge-font);flex-direction:column;font-size:14px;display:flex;overflow:hidden}.ge-top{align-items:center;gap:6px;padding:8px;display:flex}.ge-search{min-width:0;color:var(--ge-text);background:var(--ge-panel);font:inherit;border:1px solid #0000;border-radius:4px;flex:1;padding:6px 9px;font-size:13px}.ge-search::placeholder{color:var(--ge-muted)}.ge-search:focus{border-color:var(--ge-accent);outline:none}.ge-tone-wrap{flex:none;position:relative}.ge-tone{background:var(--ge-panel);width:32px;height:32px;font-family:var(--ge-font-emoji);cursor:pointer;border:1px solid #0000;border-radius:4px;place-items:center;padding:0;font-size:17px;line-height:1;display:grid}.ge-tone:hover,.ge-tone[aria-expanded=true]{background:var(--ge-hover);border-color:var(--ge-accent)}.ge-tone-pop{z-index:5;background:var(--ge-surface);border:1px solid var(--ge-border);border-radius:var(--ge-radius);grid-template-columns:repeat(3,auto);gap:2px;padding:6px;display:grid;position:absolute;top:calc(100% + 6px);right:0;box-shadow:0 8px 24px #00000073}.ge-tone-item{width:30px;height:30px;font-family:var(--ge-font-emoji);cursor:pointer;background:0 0;border:0;border-radius:4px;place-items:center;padding:0;font-size:17px;line-height:1;display:grid}.ge-tone-item:hover,.ge-tone-item[data-active]{background:var(--ge-hover)}.ge-tone-item[data-active]{box-shadow:inset 0 0 0 1px var(--ge-accent)}.ge-tabs{gap:2px;padding:0 8px 6px;display:flex}.ge-tab{min-width:0;height:26px;color:var(--ge-muted);font-family:var(--ge-font-emoji);cursor:pointer;opacity:.7;background:0 0;border:0;border-radius:4px;flex:1 1 0;place-items:center;padding:0;font-size:16px;line-height:1;display:grid}.ge-tab img{object-fit:cover;border-radius:50%;width:18px;height:18px}.ge-tab:hover{background:var(--ge-hover);opacity:1}.ge-tab[data-active]{background:var(--ge-hover);box-shadow:inset 0 -2px 0 var(--ge-accent);opacity:1}.ge-list{height:var(--ge-list-height);overscroll-behavior:contain;scrollbar-gutter:stable;border-top:1px solid var(--ge-border);position:relative;overflow-y:auto}.ge-sizer{width:100%;position:relative}.ge-window{contain:layout paint;position:absolute;inset:0 0 auto}.ge-row{grid-template-columns:repeat(var(--ge-columns,8), minmax(0, 1fr));gap:var(--ge-gap,2px);height:var(--ge-row-height,36px);padding:0 8px;display:grid}.ge-header{height:var(--ge-header-height,26px);color:var(--ge-muted);letter-spacing:.04em;text-transform:uppercase;align-items:center;padding:0 8px;font-size:11px;font-weight:700;display:flex}.ge-cell{height:var(--ge-cell,34px);font-family:var(--ge-font-emoji);cursor:pointer;user-select:none;border-radius:4px;place-items:center;font-size:21px;line-height:1;display:grid}.ge-cell img{object-fit:contain;width:24px;height:24px}.ge-cell:hover{background:var(--ge-hover)}.ge-cell[data-active]{background:var(--ge-active)}.ge-sticky{z-index:2;height:var(--ge-header-height,26px);background:var(--ge-bg);color:var(--ge-muted);letter-spacing:.04em;text-transform:uppercase;pointer-events:none;align-items:center;padding:0 8px;font-size:11px;font-weight:700;display:flex;position:absolute;top:0;left:0;right:0}.ge-empty{color:var(--ge-muted);text-align:center;margin:0;padding:24px 12px;font-size:13px}
|
package/package.json
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "goemoji",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Лёгкий эмодзи-пикер для React: вкладки категорий, поиск, скины и серверные эмодзи Discord.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "VolRencs",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"sideEffects": [
|
|
9
|
+
"./dist/styles.css"
|
|
10
|
+
],
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=24"
|
|
16
|
+
},
|
|
17
|
+
"packageManager": "pnpm@12.6.0",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/VolRencs/goemoji.git"
|
|
21
|
+
},
|
|
22
|
+
"homepage": "https://github.com/VolRencs/goemoji",
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/VolRencs/goemoji/issues"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"emoji",
|
|
28
|
+
"emoji-picker",
|
|
29
|
+
"react",
|
|
30
|
+
"discord",
|
|
31
|
+
"discord-emoji",
|
|
32
|
+
"server-emojis"
|
|
33
|
+
],
|
|
34
|
+
"files": [
|
|
35
|
+
"dist",
|
|
36
|
+
"data",
|
|
37
|
+
"README.md",
|
|
38
|
+
"README.ru.md",
|
|
39
|
+
"CHANGELOG.md",
|
|
40
|
+
"LICENSE",
|
|
41
|
+
"THIRD_PARTY_NOTICES.md"
|
|
42
|
+
],
|
|
43
|
+
"exports": {
|
|
44
|
+
".": {
|
|
45
|
+
"types": "./dist/index.d.ts",
|
|
46
|
+
"import": "./dist/index.js",
|
|
47
|
+
"default": "./dist/index.js"
|
|
48
|
+
},
|
|
49
|
+
"./discord": {
|
|
50
|
+
"types": "./dist/discord.d.ts",
|
|
51
|
+
"import": "./dist/discord.js",
|
|
52
|
+
"default": "./dist/discord.js"
|
|
53
|
+
},
|
|
54
|
+
"./styles.css": {
|
|
55
|
+
"default": "./dist/styles.css"
|
|
56
|
+
},
|
|
57
|
+
"./data/ru.json": "./data/ru.json",
|
|
58
|
+
"./data/en.json": "./data/en.json",
|
|
59
|
+
"./package.json": "./package.json"
|
|
60
|
+
},
|
|
61
|
+
"scripts": {
|
|
62
|
+
"build": "tsdown",
|
|
63
|
+
"check": "tsc --noEmit",
|
|
64
|
+
"test": "node --test test/*.test.ts",
|
|
65
|
+
"data": "node scripts/build-data.ts",
|
|
66
|
+
"size": "node scripts/size-budget.ts",
|
|
67
|
+
"smoke": "node scripts/smoke.ts",
|
|
68
|
+
"lint:package": "LINT_PACKAGE=1 tsdown",
|
|
69
|
+
"dev": "vite --config demo/vite.config.ts",
|
|
70
|
+
"prepare": "tsdown",
|
|
71
|
+
"prepublishOnly": "pnpm lint:package && pnpm check && pnpm test && pnpm build && pnpm size && pnpm smoke"
|
|
72
|
+
},
|
|
73
|
+
"peerDependencies": {
|
|
74
|
+
"react": "^19"
|
|
75
|
+
},
|
|
76
|
+
"devDependencies": {
|
|
77
|
+
"@arethetypeswrong/core": "^0.18.5",
|
|
78
|
+
"@tsdown/css": "^0.23.0",
|
|
79
|
+
"@types/node": "^26.6.2",
|
|
80
|
+
"@types/react": "^19.3.0",
|
|
81
|
+
"@types/react-dom": "^19.3.0",
|
|
82
|
+
"emojibase-data": "^17.0.0",
|
|
83
|
+
"publint": "^0.3.24",
|
|
84
|
+
"react": "^19.3.0",
|
|
85
|
+
"react-dom": "^19.3.0",
|
|
86
|
+
"tsdown": "^0.23.0",
|
|
87
|
+
"typescript": "^7.0.2",
|
|
88
|
+
"vite": "^8.3.1"
|
|
89
|
+
}
|
|
90
|
+
}
|