You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/scripts/generate-emoji-data.ts

151 lines
5.1 KiB

uix(chat): v2 · emoji-picker (picker completo) + escaparate rico del bloque Nuevo componente emoji-picker y rediseño del escaparate de mensajeria. emoji-picker (ruta 9 fases): - Compone Popover (open/close/foco/portal) + Command (busqueda + grid + nav de teclado + a11y combobox/listbox, su "grid mode" documentado) + dos ToggleGroup (categorias + tono de piel). Delegated: sin eventos propios (D.1); el grid y las celdas doble-registran Command.List/Item, conservando la a11y pero con contrato visual propio (gana especificidad con selectores de 2 niveles, sin tocar attrs [data-command-*] ajenos — eidos-lint limpio). - Datos: $libs/emoji, dataset vendorizado de emojibase (MIT) horneado por scripts/generate-emoji-data.ts (precedente lucide: JSON crudo en static/ + script generador). ~1906 emoji con nombre CLDR + keywords, 316 con tono de piel single-person. Grid acotado (categoria activa o resultados de busqueda), sin virtualizacion (refinamiento v2). Helpers puros + test (12/12). - morfo delegated (7 partes + header/footer de layout); soma (root + trigger + content + grid + option + preview, provider-test 4/4); eidos (panel empaquetado, receta con tokens --emoji-picker-*); langs es/en; demo 5 tabs (excepcion delegated documentada); READMEs dobles. Escaparate del bloque (web/routes/uix/demos/chat) — rehecho rico y correcto: - Avatares por-autor con color solido (Avatar del ecosistema): antes gris neutro indefinido, ahora teal/ambar/verde/indigo/primary bien contrastados. - Dos columnas: chat-list (sidebar con badges + typing-en-lista) + sala. - Conversacion rica: separador de dia, run agrupado, reaccion, reply citado, recibos de lectura, typing con avatar, y el emoji-picker integrado en el composer (inserta en el draft). - Solape de burbujas (transitorio, estimateSize=48 con mensajes ~90-126px) mitigado subiendo el estimate a 76. Auditoria de tamanos/espacios: recipes del bloque limpios (todo con tokens --space-*/--size-*/--color-*); unico literal (punto tono-capable) tokenizado. Verificacion: check baseline 76 (0 nuevos) · component:audit emoji-picker PASS · eidos-lint invalid=0 · recipe-contract 30/30 · morfo:vocabulary 0 · emoji lib 12/12 · provider-test 4/4 · Playwright PASS (busqueda 'rocket'->rocket+ astronautas, click->onSelect+cierra, teclado, tono oscuro 👋->👋🏿, picker abre en el composer). Capturas claro miradas. next-features §7 marca emoji-picker SHIPPED. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
/**
* Bake the vendored emojibase snapshot into a compact, statically-imported
* TS module for `$libs/emoji`. Mirrors the lucide-icon precedent
* (`scripts/generate-icon-categories.js`): a third-party dataset lives raw in
* `static/` and a generator prunes it into a baked module — no runtime dep.
*
* Source : static/emoji/compact.json + messages.json (emojibase-data@16, MIT).
* Output : src/libs/emoji/data.ts (DO NOT EDIT — regenerate via this script).
* Run : npm run generate:emoji-data
*
* Pruning: keep pickable emoji only (drop the `component` group — skin-tone /
* hair modifiers are not pickable glyphs). Per entry we keep the glyph, the
* CLDR name, the group index, a space-joined keyword string (search), and —
* for single-person tone-capable emoji — the five Fitzpatrick tone glyphs.
*/
import { readFileSync, writeFileSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import { dirname, resolve } from 'node:path'
const here = dirname(fileURLToPath(import.meta.url))
const root = resolve(here, '..')
interface CompactSkin {
hexcode: string
unicode: string
label: string
}
interface CompactEmoji {
hexcode: string
label: string
unicode: string
group?: number
order?: number
tags?: string[]
skins?: CompactSkin[]
}
interface Messages {
groups: { order: number; key: string; message: string }[]
skinTones: { key: string; message: string }[]
}
const COMPONENT_GROUP = 2 // skin-tone + hair modifiers — never pickable.
// Fitzpatrick modifiers in light→dark order (the tone-selector order).
const TONE_MODIFIERS = [
{ slot: 1, key: 'light', mod: '1F3FB' },
{ slot: 2, key: 'medium-light', mod: '1F3FC' },
{ slot: 3, key: 'medium', mod: '1F3FD' },
{ slot: 4, key: 'medium-dark', mod: '1F3FE' },
{ slot: 5, key: 'dark', mod: '1F3FF' }
] as const
const stripVS = (hex: string) => hex.replace(/-FE0F/g, '')
const isMod = (codepoint: string) => /^1F3F[B-F]$/.test(codepoint)
const compact: CompactEmoji[] = JSON.parse(
readFileSync(resolve(root, 'static/emoji/compact.json'), 'utf8')
)
const messages: Messages = JSON.parse(
readFileSync(resolve(root, 'static/emoji/messages.json'), 'utf8')
)
// Groups (excl. component), in canonical order.
const groups = messages.groups
.filter((g) => g.order !== COMPONENT_GROUP)
.sort((a, b) => a.order - b.order)
.map((g) => ({ index: g.order, key: g.key, label: g.message }))
// Skin tones in light→dark order, labels from messages.
const toneLabel = (key: string) =>
messages.skinTones.find((t) => t.key === key)?.message ?? key
const skinTones = TONE_MODIFIERS.map((t) => ({
slot: t.slot,
key: t.key,
label: toneLabel(t.key)
}))
/**
* Five uniform-tone glyphs for a single-person tone-capable emoji, else null.
* A variant qualifies when it carries EXACTLY ONE Fitzpatrick modifier and
* removing it yields the base sequence — this matches both suffix tones
* (`👋` → `👋🏻`) and infix ZWJ tones (`man: beard` → tone before the ZWJ),
* while rejecting multi-person combos (couples/kiss/holding-hands) whose skins
* carry two modifiers. Those 7 are deferred to a v2 refinement.
*/
function toneGlyphs(e: CompactEmoji): string[] | null {
if (!e.skins || e.skins.length === 0) return null
const base = stripVS(e.hexcode)
const out: string[] = []
for (const { mod } of TONE_MODIFIERS) {
const skin = e.skins.find((s) => {
const mods = s.hexcode.split('-').filter(isMod)
return mods.length === 1 && mods[0] === mod && stripVS(s.hexcode.replace(`-${mod}`, '')) === base
})
if (!skin) return null
out.push(skin.unicode)
}
return out
}
interface Entry {
u: string
n: string
g: number
k?: string
s?: string[]
}
const entries: Entry[] = compact
.filter((e) => e.group !== undefined && e.group !== COMPONENT_GROUP)
.sort((a, b) => a.group! - b.group! || (a.order ?? 0) - (b.order ?? 0))
.map((e) => {
const entry: Entry = { u: e.unicode, n: e.label, g: e.group! }
const kw = (e.tags ?? []).filter((t) => t && t !== e.label).join(' ')
if (kw) entry.k = kw
const tones = toneGlyphs(e)
if (tones) entry.s = tones
return entry
})
const toneCount = entries.filter((e) => e.s).length
// Emit — one entry per line, compact object literals, minimal quoting.
const q = (s: string) => JSON.stringify(s)
const line = (e: Entry) => {
const parts = [`u:${q(e.u)}`, `n:${q(e.n)}`, `g:${e.g}`]
if (e.k) parts.push(`k:${q(e.k)}`)
if (e.s) parts.push(`s:[${e.s.map(q).join(',')}]`)
return `\t{${parts.join(',')}}`
}
const body = `// AUTO-GENERATED by scripts/generate-emoji-data.ts — DO NOT EDIT.
// Source: emojibase-data@16 (MIT), pruned from static/emoji/{compact,messages}.json.
// Regenerate: npm run generate:emoji-data
import type { EmojiEntry, EmojiGroup, SkinTone } from './types'
export const EMOJI_GROUPS: readonly EmojiGroup[] = ${JSON.stringify(groups)}
export const SKIN_TONES: readonly SkinTone[] = ${JSON.stringify(skinTones)}
export const EMOJI: readonly EmojiEntry[] = [
${entries.map(line).join(',\n')}
]
`
writeFileSync(resolve(root, 'src/libs/emoji/data.ts'), body, 'utf8')
console.log(
`emoji-data: ${entries.length} pickable · ${toneCount} tone-capable · ${groups.length} groups → src/libs/emoji/data.ts`
)

Powered by TurnKey Linux.