@lovett/ui 0.0.11 → 0.2.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.
Files changed (142) hide show
  1. package/dist/chunk-RBYWGBQ2.js +2752 -0
  2. package/dist/chunk-RBYWGBQ2.js.map +1 -0
  3. package/dist/index.d.ts +5574 -57
  4. package/dist/index.js +21650 -11206
  5. package/dist/index.js.map +1 -1
  6. package/dist/rich-composer-impl-5NO443A6.js +1859 -0
  7. package/dist/rich-composer-impl-5NO443A6.js.map +1 -0
  8. package/dist/styles.css +1570 -0
  9. package/dist/tokens.css +112 -0
  10. package/package.json +8 -1
  11. package/src/__tests__/avatar.test.tsx +272 -0
  12. package/src/__tests__/bar-chart.test.tsx +809 -0
  13. package/src/__tests__/board.test.tsx +420 -0
  14. package/src/__tests__/chart-math.test.ts +922 -0
  15. package/src/__tests__/chart-series.test.ts +339 -0
  16. package/src/__tests__/code-block.test.tsx +134 -0
  17. package/src/__tests__/display-popover.test.tsx +195 -0
  18. package/src/__tests__/display-store.test.tsx +307 -0
  19. package/src/__tests__/donut-chart.test.tsx +397 -0
  20. package/src/__tests__/dropdown-menu.test.tsx +156 -2
  21. package/src/__tests__/filter-menu.test.tsx +175 -0
  22. package/src/__tests__/gauge-ring.test.tsx +233 -0
  23. package/src/__tests__/line-chart.test.tsx +612 -0
  24. package/src/__tests__/ranked-bars.test.tsx +343 -0
  25. package/src/__tests__/remark-underline.test.ts +194 -0
  26. package/src/__tests__/sortable.test.tsx +394 -0
  27. package/src/__tests__/sparkline.test.tsx +368 -0
  28. package/src/__tests__/stat-layer.test.tsx +271 -0
  29. package/src/__tests__/stat-strip.test.tsx +175 -0
  30. package/src/__tests__/status.test.tsx +293 -0
  31. package/src/__tests__/tabs.test.tsx +303 -0
  32. package/src/__tests__/token-shape.test.ts +132 -2
  33. package/src/avatar.tsx +352 -0
  34. package/src/bar-chart.tsx +1214 -0
  35. package/src/board.tsx +658 -0
  36. package/src/chart-frame.tsx +960 -0
  37. package/src/chart-legend.tsx +304 -0
  38. package/src/chart-tooltip.tsx +267 -0
  39. package/src/code-block.tsx +62 -8
  40. package/src/delta-chip.tsx +263 -0
  41. package/src/detail/__tests__/activity-pane.test.tsx +369 -0
  42. package/src/detail/__tests__/detail-chrome.test.tsx +134 -0
  43. package/src/detail/__tests__/detail-surface.test.tsx +529 -0
  44. package/src/detail/__tests__/field-row.test.tsx +357 -0
  45. package/src/detail/activity-pane.tsx +611 -0
  46. package/src/detail/calendar.tsx +355 -0
  47. package/src/detail/detail-divider.tsx +261 -0
  48. package/src/detail/detail-header.tsx +287 -0
  49. package/src/detail/detail-menu.tsx +254 -0
  50. package/src/detail/detail-surface.tsx +1110 -0
  51. package/src/detail/field-list.tsx +196 -0
  52. package/src/detail/field-row.tsx +1131 -0
  53. package/src/detail/index.ts +58 -0
  54. package/src/detail/segmented-choice.tsx +94 -0
  55. package/src/detail/types.ts +129 -0
  56. package/src/display-popover.tsx +487 -0
  57. package/src/display-store.tsx +301 -0
  58. package/src/donut-chart.tsx +988 -0
  59. package/src/dropdown-menu.tsx +290 -19
  60. package/src/filter-core/EXPORTS.md +85 -0
  61. package/src/filter-core/__tests__/columns.test.ts +159 -0
  62. package/src/filter-core/__tests__/faceting.test.ts +193 -0
  63. package/src/filter-core/__tests__/filter-fns.test.ts +519 -0
  64. package/src/filter-core/__tests__/operators.test.ts +235 -0
  65. package/src/filter-core/__tests__/state.test.ts +268 -0
  66. package/src/filter-core/__tests__/url.test.ts +350 -0
  67. package/src/filter-core/columns.ts +134 -0
  68. package/src/filter-core/date-utils.ts +38 -0
  69. package/src/filter-core/examples/task-filter-columns.ts +121 -0
  70. package/src/filter-core/faceting.ts +120 -0
  71. package/src/filter-core/filter-fns.ts +335 -0
  72. package/src/filter-core/index.ts +105 -0
  73. package/src/filter-core/operators.ts +433 -0
  74. package/src/filter-core/state.ts +280 -0
  75. package/src/filter-core/types.ts +247 -0
  76. package/src/filter-core/url.ts +261 -0
  77. package/src/filter-dropdown.tsx +12 -0
  78. package/src/filter-menu.tsx +649 -0
  79. package/src/floating-drawer.tsx +19 -1
  80. package/src/gauge-ring.tsx +435 -0
  81. package/src/hue.ts +52 -0
  82. package/src/index.ts +303 -0
  83. package/src/kbd.tsx +27 -4
  84. package/src/lib/chart.ts +866 -0
  85. package/src/lib/focus.ts +43 -1
  86. package/src/lib/layer-stack.ts +30 -3
  87. package/src/lib/remark-underline.ts +443 -0
  88. package/src/lib/series.ts +169 -0
  89. package/src/line-chart.tsx +1176 -0
  90. package/src/markdown.tsx +26 -7
  91. package/src/modal.tsx +42 -18
  92. package/src/progress-ledger.tsx +304 -0
  93. package/src/ranked-bars.tsx +386 -0
  94. package/src/segmented-pill.tsx +32 -9
  95. package/src/sortable.tsx +520 -1
  96. package/src/sparkline.tsx +416 -0
  97. package/src/stat-card.tsx +376 -0
  98. package/src/stat-strip.tsx +327 -0
  99. package/src/status.tsx +215 -0
  100. package/src/styles.css +1570 -0
  101. package/src/tabs.tsx +206 -25
  102. package/src/task-card.tsx +610 -0
  103. package/src/thread/__tests__/comment-body-hostile.test.tsx +331 -0
  104. package/src/thread/__tests__/comment-tree.test.ts +151 -0
  105. package/src/thread/__tests__/emoji.test.ts +187 -0
  106. package/src/thread/__tests__/fixtures/thread-fixture.ts +252 -0
  107. package/src/thread/__tests__/link-preview-source.test.ts +120 -0
  108. package/src/thread/__tests__/link-preview.test.tsx +600 -0
  109. package/src/thread/__tests__/markdown-format.test.ts +82 -0
  110. package/src/thread/__tests__/markdown-spec.test.ts +469 -0
  111. package/src/thread/__tests__/relative-time.test.ts +71 -0
  112. package/src/thread/__tests__/rich-composer.test.tsx +799 -0
  113. package/src/thread/__tests__/scroll-caret.test.ts +58 -0
  114. package/src/thread/__tests__/suggestion-list.test.tsx +648 -0
  115. package/src/thread/__tests__/thread-scroll-ownership.test.tsx +88 -0
  116. package/src/thread/__tests__/thread.test.tsx +742 -0
  117. package/src/thread/__tests__/use-attachments.test.tsx +679 -0
  118. package/src/thread/actions.tsx +196 -0
  119. package/src/thread/attachments.tsx +1071 -0
  120. package/src/thread/comment-body.tsx +148 -0
  121. package/src/thread/comment-tree.ts +182 -0
  122. package/src/thread/comment.tsx +967 -0
  123. package/src/thread/composer-footer.tsx +125 -0
  124. package/src/thread/composer.tsx +319 -0
  125. package/src/thread/emoji.ts +283 -0
  126. package/src/thread/index.ts +153 -0
  127. package/src/thread/link-preview.tsx +341 -0
  128. package/src/thread/markdown-format.ts +155 -0
  129. package/src/thread/markdown-spec.ts +754 -0
  130. package/src/thread/rail.tsx +372 -0
  131. package/src/thread/reactions.tsx +415 -0
  132. package/src/thread/relative-time.tsx +94 -0
  133. package/src/thread/rich-composer-impl.tsx +1601 -0
  134. package/src/thread/rich-composer.tsx +195 -0
  135. package/src/thread/scroll-caret.ts +37 -0
  136. package/src/thread/suggestion-list.tsx +182 -0
  137. package/src/thread/thread.tsx +718 -0
  138. package/src/thread/types.ts +232 -0
  139. package/src/thread/use-attachments.ts +598 -0
  140. package/src/thread/use-now.ts +73 -0
  141. package/src/thread/use-thread.ts +316 -0
  142. package/src/tokens.css +112 -0
@@ -0,0 +1,283 @@
1
+ /**
2
+ * emoji — the curated shortcode table behind the `:` picker (ADR-149 D3).
3
+ *
4
+ * A HAND-MAINTAINED table, not a dataset. `emojibase-data` and friends are
5
+ * ~1 MB and would load with the composer chunk for a feature whose whole job
6
+ * is to save four keystrokes. This is roughly 150 entries covering what this
7
+ * product's people actually reach for; adding one is a one-line PR against
8
+ * `EMOJI_ROWS` below, and that is the intended maintenance model.
9
+ *
10
+ * EVERY GLYPH IS WRITTEN AS A `\u{...}` ESCAPE, NEVER A RAW BYTE. A raw high
11
+ * byte makes a source file grep as binary and blinds the CLAUDE.md §8 gates —
12
+ * the lesson from ADR-147 D8, restated as ADR-149 AC#9. This file must stay
13
+ * `US-ASCII`/`UTF-8 text` under `file(1)` with zero bytes >= 0x80, and so must
14
+ * every test that names a glyph.
15
+ *
16
+ * DIALECT: GitHub shortcodes, the most widely known. Where Slack, Teams or a
17
+ * person's muscle memory spells one differently, the difference is an ALIAS on
18
+ * the same entry, never a second entry — the owner's `:side-eye:` is an alias
19
+ * of GitHub's `roll_eyes`. `-` and `_` are interchangeable everywhere (the
20
+ * query and the table are both normalized), so `:side-eye:` and `:side_eye:`
21
+ * are the same lookup.
22
+ *
23
+ * OUT OF SCOPE for v1 (D3): skin-tone modifiers and ZWJ sequences. They
24
+ * multiply the table and need a per-user preference to be anything but a
25
+ * gimmick.
26
+ */
27
+
28
+ export interface EmojiEntry {
29
+ /** The canonical GitHub shortcode, without the surrounding colons. */
30
+ readonly name: string
31
+ /** The character itself. Written as `\u{...}` at the source level. */
32
+ readonly glyph: string
33
+ /** Other spellings that resolve to this entry (Slack/Teams/plain English). */
34
+ readonly aliases: readonly string[]
35
+ /** Search terms that are not names — `:party:` has to find `tada`. */
36
+ readonly keywords: readonly string[]
37
+ }
38
+
39
+ /**
40
+ * One row per emoji: `[name, glyph, aliases, keywords]`, the two lists
41
+ * pipe-joined so a row stays one line. Expanded once, below.
42
+ */
43
+ type EmojiRow = readonly [name: string, glyph: string, aliases: string, keywords: string]
44
+
45
+ const EMOJI_ROWS: readonly EmojiRow[] = [
46
+ /* ── Faces ───────────────────────────────────────────────────────────── */
47
+ ['grinning', '\u{1F600}', '', 'smile|happy|grin'],
48
+ ['smiley', '\u{1F603}', '', 'happy|joy|cheerful'],
49
+ ['smile', '\u{1F604}', '', 'happy|joy|laugh'],
50
+ ['grin', '\u{1F601}', '', 'happy|beam|pleased'],
51
+ ['laughing', '\u{1F606}', 'satisfied', 'haha|lol|funny'],
52
+ ['sweat_smile', '\u{1F605}', '', 'phew|relief|nervous'],
53
+ ['joy', '\u{1F602}', 'lol', 'laugh|tears|haha|funny'],
54
+ ['rofl', '\u{1F923}', 'rolling_on_the_floor_laughing', 'lol|haha|hilarious'],
55
+ ['slightly_smiling_face', '\u{1F642}', 'slight_smile', 'smile|fine|ok'],
56
+ ['upside_down_face', '\u{1F643}', 'upside_down', 'irony|sarcasm|silly'],
57
+ ['wink', '\u{1F609}', '', 'flirt|joke|kidding'],
58
+ ['blush', '\u{1F60A}', '', 'happy|shy|smile'],
59
+ ['innocent', '\u{1F607}', '', 'angel|halo|good'],
60
+ ['heart_eyes', '\u{1F60D}', '', 'love|adore|crush'],
61
+ ['star_struck', '\u{1F929}', '', 'amazed|wow|excited'],
62
+ ['yum', '\u{1F60B}', '', 'delicious|tasty|tongue'],
63
+ ['raised_eyebrow', '\u{1F928}', 'face_with_raised_eyebrow', 'skeptical|doubt|suspicious'],
64
+ ['neutral_face', '\u{1F610}', '', 'meh|blank|straight'],
65
+ ['expressionless', '\u{1F611}', '', 'blank|meh|deadpan'],
66
+ ['smirk', '\u{1F60F}', '', 'smug|sly|knowing'],
67
+ ['unamused', '\u{1F612}', '', 'meh|annoyed|unimpressed'],
68
+ ['roll_eyes', '\u{1F644}', 'face_with_rolling_eyes|side_eye|sideeye', 'whatever|annoyed|sarcasm|really'],
69
+ ['grimacing', '\u{1F62C}', '', 'awkward|eek|yikes'],
70
+ ['relieved', '\u{1F60C}', '', 'phew|calm|content'],
71
+ ['pensive', '\u{1F614}', '', 'sad|thoughtful|wistful'],
72
+ ['sleeping', '\u{1F634}', '', 'zzz|asleep|bored'],
73
+ ['exploding_head', '\u{1F92F}', 'mind_blown', 'mindblown|whoa|shocked'],
74
+ ['partying_face', '\u{1F973}', '', 'birthday|hooray|horn'],
75
+ ['sunglasses', '\u{1F60E}', '', 'cool|deal|smooth'],
76
+ ['nerd_face', '\u{1F913}', 'nerd', 'geek|glasses|smart'],
77
+ ['monocle_face', '\u{1F9D0}', 'monocle', 'inspect|scrutinize|examine'],
78
+ ['confused', '\u{1F615}', '', 'unsure|puzzled'],
79
+ ['slightly_frowning_face', '\u{1F641}', 'slight_frown', 'sad|frown'],
80
+ ['open_mouth', '\u{1F62E}', '', 'wow|surprise|shock'],
81
+ ['flushed', '\u{1F633}', '', 'blush|embarrassed|shy'],
82
+ ['pleading_face', '\u{1F97A}', 'pleading', 'puppy|beg|please'],
83
+ ['fearful', '\u{1F628}', '', 'scared|afraid|yikes'],
84
+ ['cry', '\u{1F622}', '', 'sad|tear|upset'],
85
+ ['sob', '\u{1F62D}', 'crying', 'bawl|sad|tears'],
86
+ ['scream', '\u{1F631}', '', 'horror|shock|panic'],
87
+ ['disappointed', '\u{1F61E}', '', 'sad|letdown'],
88
+ ['weary', '\u{1F629}', '', 'exhausted|tired|done'],
89
+ ['tired_face', '\u{1F62B}', '', 'exhausted|fed_up'],
90
+ ['yawning_face', '\u{1F971}', 'yawn', 'bored|sleepy|tired'],
91
+ ['rage', '\u{1F621}', 'enraged', 'angry|mad|furious'],
92
+ ['angry', '\u{1F620}', '', 'mad|annoyed|cross'],
93
+ ['thinking', '\u{1F914}', 'thinking_face|hmm', 'ponder|consider|unsure'],
94
+ ['shushing_face', '\u{1F92B}', 'shush', 'quiet|secret|hush'],
95
+ ['zipper_mouth_face', '\u{1F910}', 'zipper_mouth', 'secret|quiet|sealed'],
96
+ ['saluting_face', '\u{1FAE1}', 'salute', 'respect|acknowledge|yessir'],
97
+ ['face_holding_back_tears', '\u{1F979}', 'holding_back_tears', 'emotional|touched|proud'],
98
+ ['clown_face', '\u{1F921}', 'clown', 'joke|silly|circus'],
99
+ ['ghost', '\u{1F47B}', '', 'boo|spooky|halloween'],
100
+ ['skull', '\u{1F480}', '', 'dead|rip|died'],
101
+ ['robot', '\u{1F916}', 'robot_face', 'bot|automation|machine'],
102
+ ['see_no_evil', '\u{1F648}', '', 'monkey|hide|oops'],
103
+ ['poop', '\u{1F4A9}', 'hankey', 'bad|garbage|rubbish'],
104
+
105
+ /* ── Hands and body ──────────────────────────────────────────────────── */
106
+ ['+1', '\u{1F44D}', 'thumbsup|thumbs_up|like', 'yes|agree|approve|ok'],
107
+ ['-1', '\u{1F44E}', 'thumbsdown|thumbs_down|dislike', 'no|disagree|reject'],
108
+ ['ok_hand', '\u{1F44C}', '', 'perfect|fine|good'],
109
+ ['clap', '\u{1F44F}', '', 'applause|bravo|well_done'],
110
+ ['raised_hands', '\u{1F64C}', '', 'praise|hooray|celebrate'],
111
+ ['pray', '\u{1F64F}', 'folded_hands', 'please|thanks|thank_you|hope'],
112
+ ['muscle', '\u{1F4AA}', 'flex', 'strong|strength|power'],
113
+ ['wave', '\u{1F44B}', '', 'hi|hello|bye|greeting'],
114
+ ['point_right', '\u{1F449}', '', 'this|direction|there'],
115
+ ['point_left', '\u{1F448}', '', 'that|direction|back'],
116
+ ['crossed_fingers', '\u{1F91E}', 'fingers_crossed', 'luck|hope|wish'],
117
+ ['handshake', '\u{1F91D}', '', 'deal|agree|partner'],
118
+ ['writing_hand', '\u{270D}\u{FE0F}', 'writing', 'note|sign|draft'],
119
+ ['eyes', '\u{1F440}', '', 'look|watch|reviewing|see'],
120
+ ['brain', '\u{1F9E0}', '', 'smart|idea|think'],
121
+ ['facepalm', '\u{1F926}', '', 'oops|ugh|smh'],
122
+ ['shrug', '\u{1F937}', '', 'dunno|idk|whatever'],
123
+ ['raising_hand', '\u{1F64B}', 'raise_hand', 'question|volunteer|me'],
124
+
125
+ /* ── Celebration and energy ──────────────────────────────────────────── */
126
+ ['tada', '\u{1F389}', 'party|hooray|celebrate', 'confetti|launch|ship|shipped|done'],
127
+ ['sparkles', '\u{2728}', '', 'shiny|magic|new|clean'],
128
+ ['fire', '\u{1F525}', 'flame|lit', 'hot|burning|great'],
129
+ ['rocket', '\u{1F680}', '', 'launch|ship|deploy|fast'],
130
+ ['boom', '\u{1F4A5}', 'collision', 'explode|bang|impact'],
131
+ ['zap', '\u{26A1}', 'lightning', 'fast|power|energy'],
132
+ ['star', '\u{2B50}', '', 'favourite|rating|good'],
133
+ ['100', '\u{1F4AF}', 'hundred|perfect', 'score|agree|exactly'],
134
+ ['trophy', '\u{1F3C6}', '', 'win|award|champion'],
135
+ ['dart', '\u{1F3AF}', 'bullseye|target', 'goal|aim|precise'],
136
+ ['crown', '\u{1F451}', '', 'king|queen|best'],
137
+ ['gem', '\u{1F48E}', 'diamond', 'jewel|valuable|premium'],
138
+ ['bulb', '\u{1F4A1}', 'idea|light_bulb', 'insight|bright|suggestion'],
139
+
140
+ /* ── Work, status and objects ────────────────────────────────────────── */
141
+ ['moneybag', '\u{1F4B0}', 'money', 'budget|cash|spend'],
142
+ ['chart_with_upwards_trend', '\u{1F4C8}', 'chart_up|trending_up', 'growth|increase|up'],
143
+ ['chart_with_downwards_trend', '\u{1F4C9}', 'chart_down|trending_down', 'decline|decrease|down'],
144
+ ['bar_chart', '\u{1F4CA}', 'chart', 'data|report|stats'],
145
+ ['clipboard', '\u{1F4CB}', '', 'list|notes|tasks'],
146
+ ['calendar', '\u{1F4C5}', 'date', 'schedule|day|meeting'],
147
+ ['pushpin', '\u{1F4CC}', 'pin', 'pinned|note|important'],
148
+ ['paperclip', '\u{1F4CE}', 'attachment', 'attach|file|clip'],
149
+ ['mag', '\u{1F50D}', 'search|magnifying_glass', 'find|look|zoom'],
150
+ ['lock', '\u{1F512}', '', 'secure|private|closed'],
151
+ ['unlock', '\u{1F513}', '', 'open|access'],
152
+ ['key', '\u{1F511}', '', 'access|password|secret'],
153
+ ['bell', '\u{1F514}', 'notification', 'alert|ring|remind'],
154
+ ['no_bell', '\u{1F515}', 'mute', 'silence|quiet|off'],
155
+ ['hourglass', '\u{231B}', 'waiting', 'time|wait|pending'],
156
+ ['alarm_clock', '\u{23F0}', 'alarm', 'time|remind|deadline'],
157
+ ['stopwatch', '\u{23F1}\u{FE0F}', 'timer', 'time|measure|speed'],
158
+ ['warning', '\u{26A0}\u{FE0F}', 'caution', 'careful|alert|risk'],
159
+ ['no_entry', '\u{26D4}', 'blocked', 'stop|forbidden|blocker'],
160
+ ['white_check_mark', '\u{2705}', 'check|done', 'complete|yes|approved|shipped'],
161
+ ['heavy_check_mark', '\u{2714}\u{FE0F}', 'checkmark|tick', 'done|yes|verified'],
162
+ ['x', '\u{274C}', 'cross_mark', 'no|wrong|cancel|failed'],
163
+ ['question', '\u{2753}', '', 'help|ask|unknown'],
164
+ ['exclamation', '\u{2757}', 'heavy_exclamation_mark', 'important|alert'],
165
+ ['construction', '\u{1F6A7}', 'wip', 'work_in_progress|building|roadworks'],
166
+ ['hammer', '\u{1F528}', '', 'build|fix|tool'],
167
+ ['wrench', '\u{1F527}', '', 'fix|tool|maintenance'],
168
+ ['gear', '\u{2699}\u{FE0F}', 'settings', 'config|cog|options'],
169
+ ['bug', '\u{1F41B}', '', 'defect|issue|error'],
170
+ ['computer', '\u{1F4BB}', 'laptop', 'code|work|dev'],
171
+ ['email', '\u{2709}\u{FE0F}', 'envelope', 'mail|message|send'],
172
+ ['package', '\u{1F4E6}', '', 'box|release|ship|delivery'],
173
+ ['memo', '\u{1F4DD}', 'note|pencil', 'write|notes|edit'],
174
+ ['art', '\u{1F3A8}', 'palette', 'creative|paint|design'],
175
+ ['speech_balloon', '\u{1F4AC}', 'comment', 'talk|message|chat'],
176
+ ['thought_balloon', '\u{1F4AD}', 'thought', 'idea|think|dream'],
177
+ ['mega', '\u{1F4E3}', 'megaphone|announce', 'announcement|shout|promote'],
178
+ ['link', '\u{1F517}', '', 'url|chain|connect'],
179
+ ['globe_with_meridians', '\u{1F310}', 'globe', 'internet|web|world'],
180
+
181
+ /* ── Hearts ──────────────────────────────────────────────────────────── */
182
+ ['heart', '\u{2764}\u{FE0F}', 'red_heart|love', 'like|adore'],
183
+ ['broken_heart', '\u{1F494}', '', 'sad|heartbreak'],
184
+ ['sparkling_heart', '\u{1F496}', '', 'love|sparkle'],
185
+ ['two_hearts', '\u{1F495}', '', 'love|cute'],
186
+
187
+ /* ── Nature ──────────────────────────────────────────────────────────── */
188
+ ['sunny', '\u{2600}\u{FE0F}', 'sun', 'weather|clear|bright'],
189
+ ['rainbow', '\u{1F308}', '', 'colour|pride|hope'],
190
+ ['seedling', '\u{1F331}', 'sprout', 'grow|new|plant'],
191
+ ['four_leaf_clover', '\u{1F340}', 'clover', 'luck|lucky'],
192
+ ['unicorn', '\u{1F984}', '', 'magic|rare|special'],
193
+
194
+ /* ── Food and drink ──────────────────────────────────────────────────── */
195
+ ['coffee', '\u{2615}', 'cafe', 'caffeine|morning|espresso'],
196
+ ['beer', '\u{1F37A}', '', 'drink|cheers|friday'],
197
+ ['champagne', '\u{1F37E}', '', 'celebrate|launch|cheers'],
198
+ ['clinking_glasses', '\u{1F942}', 'cheers', 'toast|celebrate'],
199
+ ['pizza', '\u{1F355}', '', 'food|slice'],
200
+ ['birthday', '\u{1F382}', 'birthday_cake', 'cake|celebrate'],
201
+
202
+ /* ── Play ────────────────────────────────────────────────────────────── */
203
+ ['video_game', '\u{1F3AE}', 'gamepad', 'play|gaming'],
204
+ ['gift', '\u{1F381}', 'present', 'birthday|surprise'],
205
+ ]
206
+
207
+ function splitList(value: string): readonly string[] {
208
+ return value === '' ? [] : value.split('|')
209
+ }
210
+
211
+ /** The table, expanded. Order is the tie-break in {@link searchEmoji}. */
212
+ export const THREAD_EMOJI: readonly EmojiEntry[] = EMOJI_ROWS.map(
213
+ ([name, glyph, aliases, keywords]) => ({
214
+ name,
215
+ glyph,
216
+ aliases: splitList(aliases),
217
+ keywords: splitList(keywords),
218
+ }),
219
+ )
220
+
221
+ /**
222
+ * One spelling rule for both sides of the lookup: case-insensitive, `-` and
223
+ * `_` interchangeable, surrounding colons ignored. That is what makes the
224
+ * owner's `:side-eye:` and GitHub's `:side_eye:` the same key without a second
225
+ * table row.
226
+ */
227
+ export function normalizeShortcode(value: string): string {
228
+ return value.trim().toLowerCase().replace(/^:+|:+$/g, '').replaceAll('-', '_')
229
+ }
230
+
231
+ const BY_SHORTCODE: ReadonlyMap<string, EmojiEntry> = (() => {
232
+ const map = new Map<string, EmojiEntry>()
233
+ for (const entry of THREAD_EMOJI) {
234
+ for (const code of [entry.name, ...entry.aliases]) {
235
+ const key = normalizeShortcode(code)
236
+ // First writer wins, so a canonical name can never be shadowed by an
237
+ // alias added later in the table.
238
+ if (!map.has(key)) map.set(key, entry)
239
+ }
240
+ }
241
+ return map
242
+ })()
243
+
244
+ /** Resolve one shortcode — `tada`, `:tada:`, `side-eye` — to its entry. */
245
+ export function emojiByShortcode(code: string): EmojiEntry | undefined {
246
+ return BY_SHORTCODE.get(normalizeShortcode(code))
247
+ }
248
+
249
+ /**
250
+ * Rank, most specific first: an exact name, an exact alias, an exact keyword,
251
+ * then the prefix forms of each. `:party:` therefore finds `tada` (alias,
252
+ * exact) ahead of `partying_face` (name, prefix) — D3's worked example.
253
+ * Anything that matches nothing scores `null` and is dropped.
254
+ */
255
+ function score(entry: EmojiEntry, query: string): number | null {
256
+ if (entry.name === query) return 0
257
+ if (entry.aliases.includes(query)) return 1
258
+ if (entry.keywords.includes(query)) return 2
259
+ if (entry.name.startsWith(query)) return 3
260
+ if (entry.aliases.some((alias) => alias.startsWith(query))) return 4
261
+ if (entry.keywords.some((keyword) => keyword.startsWith(query))) return 5
262
+ return null
263
+ }
264
+
265
+ /** How many options the popup shows at once. */
266
+ export const EMOJI_RESULT_LIMIT = 8
267
+
268
+ /**
269
+ * Prefix search over names, aliases and keywords (D4). An empty query returns
270
+ * nothing on purpose — the `:` trigger has a two-character threshold, and a
271
+ * bare colon must stay punctuation.
272
+ */
273
+ export function searchEmoji(query: string, limit: number = EMOJI_RESULT_LIMIT): readonly EmojiEntry[] {
274
+ const normalized = normalizeShortcode(query)
275
+ if (normalized === '') return []
276
+ const ranked: { readonly entry: EmojiEntry; readonly rank: number; readonly order: number }[] = []
277
+ for (const [order, entry] of THREAD_EMOJI.entries()) {
278
+ const rank = score(entry, normalized)
279
+ if (rank !== null) ranked.push({ entry, rank, order })
280
+ }
281
+ ranked.sort((a, b) => (a.rank === b.rank ? a.order - b.order : a.rank - b.rank))
282
+ return ranked.slice(0, limit).map((match) => match.entry)
283
+ }
@@ -0,0 +1,153 @@
1
+ /**
2
+ * @lovett/ui — thread primitive (ADR-147 T3).
3
+ *
4
+ * A headless core plus one styled skin. Entity-agnostic by construction: it
5
+ * takes already-shaped data and reports intent through callbacks. It owns no
6
+ * fetch client and no base URL — T4 wires it to the API.
7
+ *
8
+ * Re-exported from packages/ui/src/index.ts. The `.ds-thread` radius
9
+ * declaration lives in styles.css (see the THREAD block there), not in a
10
+ * per-component stylesheet — the package ships two consumer stylesheets and
11
+ * has no CSS loader in its tsup build.
12
+ */
13
+
14
+ export { Thread } from './thread'
15
+ export type { ThreadProps, ThreadParticipant } from './thread'
16
+
17
+ export { useThread, DEFAULT_MAX_DEPTH, DEFAULT_REPLIES_VISIBLE } from './use-thread'
18
+ export type { UseThreadOptions, UseThreadResult, VisibleReplies } from './use-thread'
19
+
20
+ export { CommentItem } from './comment'
21
+ export type { CommentItemProps, ThreadRenderContext } from './comment'
22
+
23
+ export { CommentActions } from './actions'
24
+ export type { CommentActionsProps } from './actions'
25
+
26
+ export {
27
+ CollapsedReplies,
28
+ DEFAULT_INDENT_CLAMP_DEPTH,
29
+ LineIcon,
30
+ ThreadConnector,
31
+ ThreadRail,
32
+ ThreadTrunkSegment,
33
+ } from './rail'
34
+ export type { CollapsedRepliesProps, ThreadConnectorProps, ThreadRailProps } from './rail'
35
+
36
+ export { ThreadComposer } from './composer'
37
+ export type { ThreadComposerProps } from './composer'
38
+
39
+ // The rich composer's SHELL only (ADR-148 D5). `rich-composer-impl.tsx` and
40
+ // `markdown-spec.ts` import TipTap statically and are reached exclusively
41
+ // through the shell's lazy boundary — re-exporting either here would pull
42
+ // ProseMirror into every consumer of the barrel.
43
+ export { RichThreadComposer } from './rich-composer'
44
+ export type { RichComposerAttachmentProps, RichThreadComposerProps } from './rich-composer'
45
+
46
+ // ADR-149 — the `@` and `:` picker.
47
+ //
48
+ // TYPES ONLY, and that is a decision rather than an oversight. `emoji.ts` and
49
+ // `suggestion-list.tsx` are both TipTap-free, so exporting their VALUES here
50
+ // would not pull in ProseMirror — but tsup would still hoist them into the
51
+ // shared chunk that `dist/index.js` imports eagerly, and AC#10 says the
52
+ // shortcode table loads with the COMPOSER, never with a thread list. Measured:
53
+ // with the value exports in place the table landed in the eager chunk; without
54
+ // them it sits in `rich-composer-impl-*.js` behind the lazy boundary.
55
+ //
56
+ // A host needs no value from either: it supplies `mentionItems` and the
57
+ // composer does the rest. Both modules stay reachable by direct path for the
58
+ // tests, exactly as `markdown-spec.ts` is.
59
+ export type { SuggestionListProps, SuggestionOption } from './suggestion-list'
60
+ export type { EmojiEntry } from './emoji'
61
+
62
+ // Attachments, GIFs and paste-to-file (ADR-151). Safe in the barrel: no TipTap
63
+ // and no ProseMirror — the rich composer's shell is still the only lazy seam.
64
+ export {
65
+ ATTACHMENT_ACCEPT,
66
+ AttachmentButton,
67
+ AttachmentDropZone,
68
+ AttachmentGif,
69
+ AttachmentImage,
70
+ AttachmentLightbox,
71
+ AttachmentPlaceholder,
72
+ AttachmentTray,
73
+ GifPicker,
74
+ formatAttachmentSize,
75
+ usePrefersReducedMotion,
76
+ } from './attachments'
77
+ export type {
78
+ AttachmentButtonProps,
79
+ AttachmentDropZoneProps,
80
+ AttachmentGifProps,
81
+ AttachmentImageProps,
82
+ AttachmentLightboxProps,
83
+ AttachmentTrayProps,
84
+ GifPickerProps,
85
+ GifPickerResult,
86
+ } from './attachments'
87
+
88
+ export {
89
+ ATTACHMENT_LIMITS,
90
+ GIF_SEARCH_DEBOUNCE_MS,
91
+ PASTE_ATTACHMENT_THRESHOLD,
92
+ pastedTextFilename,
93
+ shouldAttachPaste,
94
+ useAttachmentUrl,
95
+ useAttachments,
96
+ } from './use-attachments'
97
+ export type {
98
+ AttachmentTicketLike,
99
+ ComposerAttachment,
100
+ ComposerAttachmentKind,
101
+ ComposerAttachmentState,
102
+ UseAttachmentsOptions,
103
+ UseAttachmentsResult,
104
+ } from './use-attachments'
105
+
106
+ // Link previews (ADR-150 D8). No TipTap, no parser, no fetch client — the
107
+ // worker does every dangerous part and this renders the strings it stored.
108
+ export { LinkPreview, LINK_PREVIEW_DEFAULT_ASPECT } from './link-preview'
109
+ export type { LinkPreviewProps } from './link-preview'
110
+
111
+ export { applyMarkdownFormat } from './markdown-format'
112
+ export type { MarkdownFormat, MarkdownEdit } from './markdown-format'
113
+
114
+ export { CommentBody, parseMentionHref } from './comment-body'
115
+ export type { CommentBodyProps, MentionTarget, MentionTargetType } from './comment-body'
116
+
117
+ export { ReactionBar, CommentVote, REACTION_META } from './reactions'
118
+ export type { ReactionBarProps, CommentVoteProps } from './reactions'
119
+
120
+ export {
121
+ RelativeTime,
122
+ formatRelativeTime,
123
+ formatAbsoluteTime,
124
+ toIsoTimestamp,
125
+ } from './relative-time'
126
+ export type { RelativeTimeProps } from './relative-time'
127
+
128
+ export {
129
+ buildCommentTree,
130
+ countCommentTree,
131
+ countDescendants,
132
+ engagementScore,
133
+ sortCommentTree,
134
+ } from './comment-tree'
135
+
136
+ export { useNowTick } from './use-now'
137
+
138
+ export { THREAD_REACTION_KEYS } from './types'
139
+ export type {
140
+ CommentDeliveryState,
141
+ CommentNode,
142
+ MentionSuggestionItem,
143
+ ThreadAttachment,
144
+ ThreadAttachmentKind,
145
+ ThreadAuthor,
146
+ ThreadComment,
147
+ ThreadEngagement,
148
+ ThreadLinkPreview,
149
+ ThreadReactionCount,
150
+ ThreadReactionKey,
151
+ ThreadSort,
152
+ ThreadVote,
153
+ } from './types'