@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,193 @@
1
+ /**
2
+ * Faceted counts.
3
+ *
4
+ * BUG 3 — the source computed counts inside a `useMemo` with an EMPTY
5
+ * dependency array (filter-value.tsx:481-489), so they froze at popover
6
+ * mount and disagreed with the board from the first filter onwards. These
7
+ * are pure functions of the rows passed in, so the same call with different
8
+ * rows must give different answers.
9
+ */
10
+ import { describe, expect, it } from 'vitest'
11
+ import {
12
+ defineColumns,
13
+ facetedCounts,
14
+ facetedMinMax,
15
+ facetedRowsFor,
16
+ filterRows,
17
+ } from '../index'
18
+ import type { AnyColumnConfig, FiltersState } from '../index'
19
+
20
+ interface Task {
21
+ readonly id: string
22
+ readonly priority: string | null
23
+ readonly labels: readonly string[]
24
+ readonly points: number | null
25
+ }
26
+
27
+ const columns = defineColumns<Task>([
28
+ {
29
+ id: 'priority',
30
+ type: 'option',
31
+ displayName: 'Priority',
32
+ accessor: (t) => t.priority,
33
+ options: [
34
+ { value: 'low', label: 'Low' },
35
+ { value: 'medium', label: 'Medium' },
36
+ { value: 'high', label: 'High' },
37
+ ],
38
+ },
39
+ {
40
+ id: 'labels',
41
+ type: 'multiOption',
42
+ displayName: 'Labels',
43
+ accessor: (t) => t.labels,
44
+ options: [
45
+ { value: 'bug', label: 'Bug' },
46
+ { value: 'ui', label: 'UI' },
47
+ { value: 'perf', label: 'Perf' },
48
+ ],
49
+ },
50
+ {
51
+ id: 'points',
52
+ type: 'number',
53
+ displayName: 'Points',
54
+ accessor: (t) => t.points,
55
+ },
56
+ ])
57
+
58
+ function columnById(id: string): AnyColumnConfig<Task> {
59
+ const column = columns.find((candidate) => candidate.id === id)
60
+ if (!column) throw new Error(`no column ${id}`)
61
+ return column
62
+ }
63
+
64
+ const rows: Task[] = [
65
+ { id: 'a', priority: 'high', labels: ['bug'], points: 3 },
66
+ { id: 'b', priority: 'high', labels: ['ui', 'bug'], points: 8 },
67
+ { id: 'c', priority: 'low', labels: [], points: null },
68
+ { id: 'd', priority: null, labels: ['ui'], points: 5 },
69
+ ]
70
+
71
+ describe('facetedCounts', () => {
72
+ it('counts option values', () => {
73
+ const counts = facetedCounts(rows, columnById('priority'))
74
+ expect(counts.get('high')).toBe(2)
75
+ expect(counts.get('low')).toBe(1)
76
+ })
77
+
78
+ it('keeps declared options with no rows as zero buckets', () => {
79
+ const counts = facetedCounts(rows, columnById('priority'))
80
+ expect(counts.has('medium')).toBe(true)
81
+ expect(counts.get('medium')).toBe(0)
82
+ })
83
+
84
+ it('lists declared options first, in declaration order', () => {
85
+ const counts = facetedCounts(rows, columnById('priority'))
86
+ expect([...counts.keys()]).toEqual(['low', 'medium', 'high'])
87
+ })
88
+
89
+ it('ignores rows with no value rather than inventing a bucket', () => {
90
+ const counts = facetedCounts(rows, columnById('priority'))
91
+ const total = [...counts.values()].reduce((sum, n) => sum + n, 0)
92
+ expect(total).toBe(3)
93
+ })
94
+
95
+ it('counts each member of a multiOption column', () => {
96
+ const counts = facetedCounts(rows, columnById('labels'))
97
+ expect(counts.get('bug')).toBe(2)
98
+ expect(counts.get('ui')).toBe(2)
99
+ expect(counts.get('perf')).toBe(0)
100
+ })
101
+
102
+ it('counts a row once per distinct value, not once per occurrence', () => {
103
+ const duplicated: Task[] = [
104
+ { id: 'x', priority: null, labels: ['bug', 'bug'], points: null },
105
+ ]
106
+ expect(facetedCounts(duplicated, columnById('labels')).get('bug')).toBe(1)
107
+ })
108
+
109
+ it('appends an undeclared value found in the data', () => {
110
+ const extra: Task[] = [
111
+ { id: 'x', priority: 'urgent', labels: [], points: null },
112
+ ]
113
+ const counts = facetedCounts(extra, columnById('priority'))
114
+ expect([...counts.keys()]).toEqual(['low', 'medium', 'high', 'urgent'])
115
+ expect(counts.get('urgent')).toBe(1)
116
+ })
117
+
118
+ it('throws for a column type with no facets', () => {
119
+ expect(() => facetedCounts(rows, columnById('points'))).toThrow(
120
+ /only defined for option and multiOption/,
121
+ )
122
+ })
123
+
124
+ // BUG 3
125
+ it('recomputes from the rows it is given, so counts cannot go stale', () => {
126
+ const priority = columnById('priority')
127
+ const before = facetedCounts(rows, priority)
128
+ expect(before.get('high')).toBe(2)
129
+
130
+ const narrowed = filterRows(rows, columns, [
131
+ { columnId: 'labels', type: 'multiOption', operator: 'include', values: ['ui'] },
132
+ ])
133
+ const after = facetedCounts(narrowed, priority)
134
+
135
+ expect(after.get('high')).toBe(1)
136
+ // And the first result was not mutated by the second call.
137
+ expect(before.get('high')).toBe(2)
138
+ })
139
+ })
140
+
141
+ describe('facetedRowsFor', () => {
142
+ const state: FiltersState = [
143
+ { columnId: 'priority', type: 'option', operator: 'is', values: ['high'] },
144
+ { columnId: 'labels', type: 'multiOption', operator: 'include', values: ['ui'] },
145
+ ]
146
+
147
+ it('excludes the column its own facet list is for', () => {
148
+ // Without this, picking "high" would zero every other priority bucket and
149
+ // the list could never be widened.
150
+ const forPriority = facetedRowsFor(rows, columns, state, 'priority')
151
+ expect(forPriority.map((t) => t.id)).toEqual(['b', 'd'])
152
+ const counts = facetedCounts(forPriority, columnById('priority'))
153
+ expect(counts.get('high')).toBe(1)
154
+ expect(counts.get('low')).toBe(0)
155
+ })
156
+
157
+ it('still applies every other column filter', () => {
158
+ const forLabels = facetedRowsFor(rows, columns, state, 'labels')
159
+ expect(forLabels.map((t) => t.id)).toEqual(['a', 'b'])
160
+ })
161
+ })
162
+
163
+ describe('facetedMinMax', () => {
164
+ it('reports the observed range', () => {
165
+ expect(facetedMinMax(rows, columnById('points'))).toEqual([3, 8])
166
+ })
167
+
168
+ it('returns null when no row has a value', () => {
169
+ expect(facetedMinMax([], columnById('points'))).toBeNull()
170
+ })
171
+
172
+ it('prefers declared bounds over observed ones', () => {
173
+ const bounded = defineColumns<Task>([
174
+ {
175
+ id: 'points',
176
+ type: 'number',
177
+ displayName: 'Points',
178
+ accessor: (t) => t.points,
179
+ min: 0,
180
+ max: 100,
181
+ },
182
+ ])
183
+ const column = bounded[0]
184
+ if (!column) throw new Error('fixture missing')
185
+ expect(facetedMinMax(rows, column)).toEqual([0, 100])
186
+ })
187
+
188
+ it('throws for a non-number column', () => {
189
+ expect(() => facetedMinMax(rows, columnById('priority'))).toThrow(
190
+ /only defined for number columns/,
191
+ )
192
+ })
193
+ })