@prestack/thread-ui 0.1.10 → 0.1.12

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 (209) hide show
  1. package/CHANGELOG.md +375 -0
  2. package/dist/ui/attach.js +6 -7
  3. package/dist/ui/compositions.d.ts +0 -8
  4. package/dist/ui/compositions.js +3 -166
  5. package/dist/ui/generators/routes.js +1 -0
  6. package/package.json +2 -2
  7. package/dist/ui/animations/anm_appear.ts +0 -13
  8. package/dist/ui/animations/anm_blur_in.ts +0 -19
  9. package/dist/ui/animations/anm_blur_out.ts +0 -18
  10. package/dist/ui/animations/anm_dismiss.ts +0 -18
  11. package/dist/ui/animations/anm_drop.ts +0 -19
  12. package/dist/ui/animations/anm_fall.ts +0 -18
  13. package/dist/ui/animations/anm_flash.ts +0 -18
  14. package/dist/ui/animations/anm_glow.ts +0 -16
  15. package/dist/ui/animations/anm_lift.ts +0 -16
  16. package/dist/ui/animations/anm_nudge.ts +0 -16
  17. package/dist/ui/animations/anm_pop_in.ts +0 -23
  18. package/dist/ui/animations/anm_pop_out.ts +0 -22
  19. package/dist/ui/animations/anm_press.ts +0 -12
  20. package/dist/ui/animations/anm_pulse.ts +0 -13
  21. package/dist/ui/animations/anm_rise.ts +0 -19
  22. package/dist/ui/animations/anm_shake.ts +0 -13
  23. package/dist/ui/animations/anm_slide_in_left.ts +0 -19
  24. package/dist/ui/animations/anm_slide_in_right.ts +0 -19
  25. package/dist/ui/animations/anm_slide_out_left.ts +0 -18
  26. package/dist/ui/animations/anm_slide_out_right.ts +0 -18
  27. package/dist/ui/animations/anm_spin.ts +0 -12
  28. package/dist/ui/animations/anm_swell.ts +0 -16
  29. package/dist/ui/animations/anm_wobble.ts +0 -24
  30. package/dist/ui/animations/anm_zoom_in.ts +0 -19
  31. package/dist/ui/animations/anm_zoom_out.ts +0 -18
  32. package/dist/ui/animations.ts +0 -308
  33. package/dist/ui/attach.ts +0 -553
  34. package/dist/ui/charts.tsx +0 -69
  35. package/dist/ui/checked.ts +0 -32
  36. package/dist/ui/collector.ts +0 -140
  37. package/dist/ui/compositions.ts +0 -613
  38. package/dist/ui/consent.ts +0 -55
  39. package/dist/ui/content.tsx +0 -27
  40. package/dist/ui/contrast.ts +0 -27
  41. package/dist/ui/controls.tsx +0 -25
  42. package/dist/ui/disclosure.tsx +0 -20
  43. package/dist/ui/dismiss-layer.ts +0 -87
  44. package/dist/ui/door.ts +0 -140
  45. package/dist/ui/drag.ts +0 -316
  46. package/dist/ui/elements/accordion.tsx +0 -47
  47. package/dist/ui/elements/accordion.works.ts +0 -66
  48. package/dist/ui/elements/aspect-ratio.tsx +0 -31
  49. package/dist/ui/elements/aspect-ratio.works.ts +0 -5
  50. package/dist/ui/elements/avatar.tsx +0 -34
  51. package/dist/ui/elements/avatar.works.ts +0 -16
  52. package/dist/ui/elements/badge.tsx +0 -33
  53. package/dist/ui/elements/badge.works.ts +0 -9
  54. package/dist/ui/elements/bars.tsx +0 -50
  55. package/dist/ui/elements/bars.works.ts +0 -29
  56. package/dist/ui/elements/box.tsx +0 -36
  57. package/dist/ui/elements/breadcrumb.tsx +0 -38
  58. package/dist/ui/elements/breadcrumb.works.ts +0 -24
  59. package/dist/ui/elements/button.tsx +0 -46
  60. package/dist/ui/elements/button.works.tsx +0 -52
  61. package/dist/ui/elements/column.tsx +0 -29
  62. package/dist/ui/elements/consent.tsx +0 -71
  63. package/dist/ui/elements/consent.works.tsx +0 -132
  64. package/dist/ui/elements/donut.tsx +0 -45
  65. package/dist/ui/elements/donut.works.ts +0 -42
  66. package/dist/ui/elements/each.tsx +0 -34
  67. package/dist/ui/elements/each.works.tsx +0 -82
  68. package/dist/ui/elements/empty.tsx +0 -33
  69. package/dist/ui/elements/field.tsx +0 -45
  70. package/dist/ui/elements/field.works.ts +0 -65
  71. package/dist/ui/elements/funnel.tsx +0 -50
  72. package/dist/ui/elements/funnel.works.ts +0 -37
  73. package/dist/ui/elements/gauge.tsx +0 -40
  74. package/dist/ui/elements/gauge.works.ts +0 -42
  75. package/dist/ui/elements/glyph.tsx +0 -30
  76. package/dist/ui/elements/glyph.works.tsx +0 -42
  77. package/dist/ui/elements/grid.tsx +0 -29
  78. package/dist/ui/elements/heatmap.tsx +0 -48
  79. package/dist/ui/elements/heatmap.works.ts +0 -24
  80. package/dist/ui/elements/hit-box.tsx +0 -47
  81. package/dist/ui/elements/hit-box.works.tsx +0 -576
  82. package/dist/ui/elements/image.tsx +0 -29
  83. package/dist/ui/elements/line-chart.tsx +0 -45
  84. package/dist/ui/elements/line-chart.works.ts +0 -41
  85. package/dist/ui/elements/link.tsx +0 -38
  86. package/dist/ui/elements/link.works.ts +0 -25
  87. package/dist/ui/elements/media.tsx +0 -57
  88. package/dist/ui/elements/menu.tsx +0 -51
  89. package/dist/ui/elements/menu.works.tsx +0 -154
  90. package/dist/ui/elements/money.tsx +0 -36
  91. package/dist/ui/elements/money.works.ts +0 -9
  92. package/dist/ui/elements/pagination.tsx +0 -39
  93. package/dist/ui/elements/pagination.works.ts +0 -20
  94. package/dist/ui/elements/progress.tsx +0 -34
  95. package/dist/ui/elements/progress.works.ts +0 -8
  96. package/dist/ui/elements/radio-group.tsx +0 -50
  97. package/dist/ui/elements/radio-group.works.ts +0 -33
  98. package/dist/ui/elements/row.tsx +0 -29
  99. package/dist/ui/elements/scatter.tsx +0 -42
  100. package/dist/ui/elements/scatter.works.ts +0 -38
  101. package/dist/ui/elements/select.tsx +0 -43
  102. package/dist/ui/elements/select.works.ts +0 -5
  103. package/dist/ui/elements/separator.tsx +0 -31
  104. package/dist/ui/elements/skeleton.tsx +0 -31
  105. package/dist/ui/elements/slider.tsx +0 -34
  106. package/dist/ui/elements/slider.works.ts +0 -5
  107. package/dist/ui/elements/sparkline.tsx +0 -37
  108. package/dist/ui/elements/sparkline.works.ts +0 -32
  109. package/dist/ui/elements/spinner.tsx +0 -33
  110. package/dist/ui/elements/stack.tsx +0 -29
  111. package/dist/ui/elements/stat.tsx +0 -36
  112. package/dist/ui/elements/stat.works.ts +0 -14
  113. package/dist/ui/elements/surface.tsx +0 -36
  114. package/dist/ui/elements/surface.works.ts +0 -9
  115. package/dist/ui/elements/table.tsx +0 -62
  116. package/dist/ui/elements/table.works.ts +0 -30
  117. package/dist/ui/elements/tabs.tsx +0 -47
  118. package/dist/ui/elements/tabs.works.ts +0 -84
  119. package/dist/ui/elements/text.tsx +0 -33
  120. package/dist/ui/elements/textarea.tsx +0 -38
  121. package/dist/ui/elements/textarea.works.ts +0 -5
  122. package/dist/ui/elements/time.tsx +0 -40
  123. package/dist/ui/elements/time.works.ts +0 -29
  124. package/dist/ui/elements/toggle.tsx +0 -34
  125. package/dist/ui/elements/toggle.works.ts +0 -5
  126. package/dist/ui/elements/tooltip.tsx +0 -40
  127. package/dist/ui/elements/tooltip.works.ts +0 -77
  128. package/dist/ui/elements/value.tsx +0 -38
  129. package/dist/ui/elements/value.works.ts +0 -9
  130. package/dist/ui/elements.ts +0 -127
  131. package/dist/ui/focus-scope.ts +0 -121
  132. package/dist/ui/fonts.ts +0 -355
  133. package/dist/ui/generators/dev.d.ts +0 -1
  134. package/dist/ui/generators/dev.js +0 -62
  135. package/dist/ui/generators/dev.ts +0 -75
  136. package/dist/ui/generators/elements.ts +0 -796
  137. package/dist/ui/generators/i18n.ts +0 -283
  138. package/dist/ui/generators/icons.ts +0 -207
  139. package/dist/ui/generators/react.ts +0 -267
  140. package/dist/ui/generators/routes.ts +0 -847
  141. package/dist/ui/generators/show.ts +0 -261
  142. package/dist/ui/generators/styles.ts +0 -677
  143. package/dist/ui/generators/variants.ts +0 -196
  144. package/dist/ui/i18n/catalogs/en.ts +0 -191
  145. package/dist/ui/i18n/format.ts +0 -221
  146. package/dist/ui/i18n/locales.ts +0 -33
  147. package/dist/ui/i18n/parse.ts +0 -315
  148. package/dist/ui/i18n/translator.ts +0 -31
  149. package/dist/ui/i18n/types.ts +0 -96
  150. package/dist/ui/icons.ts +0 -331
  151. package/dist/ui/islands/map.ts +0 -58
  152. package/dist/ui/localizations.ts +0 -75
  153. package/dist/ui/material.ts +0 -236
  154. package/dist/ui/materials/mat_bordered.ts +0 -13
  155. package/dist/ui/materials/mat_ceramic.ts +0 -13
  156. package/dist/ui/materials/mat_clay.ts +0 -13
  157. package/dist/ui/materials/mat_elevated.ts +0 -24
  158. package/dist/ui/materials/mat_flat.ts +0 -34
  159. package/dist/ui/materials/mat_glass.ts +0 -20
  160. package/dist/ui/materials/mat_lit.ts +0 -13
  161. package/dist/ui/materials/mat_moulded.ts +0 -11
  162. package/dist/ui/materials.ts +0 -197
  163. package/dist/ui/modes.ts +0 -44
  164. package/dist/ui/navigation.tsx +0 -29
  165. package/dist/ui/positioning.ts +0 -90
  166. package/dist/ui/presented.tsx +0 -119
  167. package/dist/ui/provider.tsx +0 -184
  168. package/dist/ui/render-loop.ts +0 -133
  169. package/dist/ui/roving-focus.ts +0 -161
  170. package/dist/ui/screens.ts +0 -141
  171. package/dist/ui/scroll-lock.ts +0 -28
  172. package/dist/ui/series.ts +0 -97
  173. package/dist/ui/signals.ts +0 -386
  174. package/dist/ui/styleguide.ts +0 -231
  175. package/dist/ui/styleguides.ts +0 -374
  176. package/dist/ui/themes.ts +0 -22
  177. package/dist/ui/token.ts +0 -78
  178. package/dist/ui/tokens/shadow.ts +0 -25
  179. package/dist/ui/tokens.ts +0 -234
  180. package/dist/ui/validators.ts +0 -232
  181. package/dist/ui/variant.ts +0 -349
  182. package/dist/ui/variants/var_avatar-round.ts +0 -14
  183. package/dist/ui/variants/var_bubble-me.ts +0 -14
  184. package/dist/ui/variants/var_bubble-them.ts +0 -13
  185. package/dist/ui/variants/var_button-border.ts +0 -76
  186. package/dist/ui/variants/var_button-cta.ts +0 -76
  187. package/dist/ui/variants/var_button-destructive-ghost.ts +0 -72
  188. package/dist/ui/variants/var_button-destructive.ts +0 -76
  189. package/dist/ui/variants/var_button-fill.ts +0 -76
  190. package/dist/ui/variants/var_button-ghost.ts +0 -72
  191. package/dist/ui/variants/var_button-glass.ts +0 -55
  192. package/dist/ui/variants/var_button-normal.ts +0 -76
  193. package/dist/ui/variants/var_button-quiet.ts +0 -74
  194. package/dist/ui/variants/var_card.ts +0 -17
  195. package/dist/ui/variants/var_chip.ts +0 -13
  196. package/dist/ui/variants/var_choice.ts +0 -15
  197. package/dist/ui/variants/var_framed.ts +0 -10
  198. package/dist/ui/variants/var_glyph-plain.ts +0 -11
  199. package/dist/ui/variants/var_input.ts +0 -43
  200. package/dist/ui/variants/var_link-inline.ts +0 -29
  201. package/dist/ui/variants/var_list-row.ts +0 -34
  202. package/dist/ui/variants/var_rule.ts +0 -10
  203. package/dist/ui/variants/var_skeleton-block.ts +0 -12
  204. package/dist/ui/variants/var_spinner-turning.ts +0 -12
  205. package/dist/ui/variants/var_stat-tile.ts +0 -13
  206. package/dist/ui/variants/var_tab-bar.ts +0 -15
  207. package/dist/ui/variants/var_tap-target.ts +0 -25
  208. package/dist/ui/variants.ts +0 -64
  209. package/dist/ui/wrappers.tsx +0 -429
@@ -1,27 +0,0 @@
1
- export const CONTRAST_LARGE = 3
2
-
3
- function channel(value: number): number {
4
- const c = value / 255
5
- return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4
6
- }
7
-
8
- export function luminance(hex: string): number {
9
- const clean = hex.replace('#', '')
10
- const full =
11
- clean.length === 3
12
- ? clean
13
- .split('')
14
- .map((c) => c + c)
15
- .join('')
16
- : clean.slice(0, 6)
17
- const num = Number.parseInt(full, 16)
18
- const r = channel((num >> 16) & 255)
19
- const g = channel((num >> 8) & 255)
20
- const b = channel(num & 255)
21
- return 0.2126 * r + 0.7152 * g + 0.0722 * b
22
- }
23
-
24
- export function contrast(a: string, b: string): number {
25
- const [light, dark] = [luminance(a), luminance(b)].sort((x, y) => y - x) as [number, number]
26
- return (light + 0.05) / (dark + 0.05)
27
- }
@@ -1,25 +0,0 @@
1
- 'use client'
2
-
3
- import type { MessageKey } from './content.tsx'
4
- import type { Dispatching } from './checked.ts'
5
-
6
- export interface PressProps {
7
-
8
- readonly dispatch?: [keyof Dispatching] extends [never] ? string : keyof Dispatching & string
9
-
10
- readonly input?: Readonly<Record<string, unknown>>
11
-
12
- readonly navigateTo?: string
13
-
14
- readonly onPress?: () => void
15
- }
16
-
17
- export interface Option {
18
- readonly value: string
19
- readonly labelKey: MessageKey
20
- }
21
-
22
- export { Menu, type MenuProps } from './elements/menu.tsx'
23
-
24
- export { type MenuItem } from './elements/menu.works.tsx'
25
-
@@ -1,20 +0,0 @@
1
- 'use client'
2
-
3
- import type { ReactNode } from 'react'
4
-
5
- import type { MessageKey } from './content.tsx'
6
-
7
- export interface TabItem {
8
- readonly id: string
9
- readonly labelKey: MessageKey
10
- readonly disabled?: boolean
11
- readonly content: ReactNode
12
- }
13
-
14
- export interface AccordionItem {
15
- readonly id: string
16
- readonly labelKey: MessageKey
17
- readonly disabled?: boolean
18
- readonly content: ReactNode
19
- }
20
-
@@ -1,87 +0,0 @@
1
- 'use client'
2
-
3
- import { useEffect, type RefObject } from 'react'
4
-
5
- export interface Layer {
6
- readonly ref: RefObject<HTMLElement | null>
7
- readonly dismissOnEscape: boolean
8
- readonly dismissOnOutside: boolean
9
- readonly onDismiss: () => void
10
- }
11
-
12
- const layers: Layer[] = []
13
-
14
- function ordered(): readonly Layer[] {
15
- return layers
16
- .filter((layer) => layer.ref.current !== null)
17
- .sort((a, b) => {
18
- const first = a.ref.current as HTMLElement
19
- const second = b.ref.current as HTMLElement
20
- if (first === second) return 0
21
-
22
- return (first.compareDocumentPosition(second) & Node.DOCUMENT_POSITION_FOLLOWING) !== 0 ? -1 : 1
23
- })
24
- }
25
-
26
- function target(kind: 'escape' | 'outside'): Layer | undefined {
27
- const open = ordered()
28
- for (let i = open.length - 1; i >= 0; i -= 1) {
29
- const layer = open[i] as Layer
30
- if (kind === 'escape' ? layer.dismissOnEscape : layer.dismissOnOutside) return layer
31
- }
32
- return undefined
33
- }
34
-
35
- export function useDismissLayer(
36
- ref: RefObject<HTMLElement | null>,
37
- options: {
38
- readonly active: boolean
39
- readonly dismissOnEscape: boolean
40
- readonly dismissOnOutside: boolean
41
- readonly onDismiss: () => void
42
- },
43
- ): void {
44
- const { active, dismissOnEscape, dismissOnOutside, onDismiss } = options
45
-
46
- useEffect(() => {
47
- if (!active) return
48
- const layer: Layer = { ref, dismissOnEscape, dismissOnOutside, onDismiss }
49
- layers.push(layer)
50
-
51
- const onKeyDown = (event: KeyboardEvent): void => {
52
- if (event.key !== 'Escape') return
53
- if (target('escape') !== layer) return
54
- event.preventDefault()
55
-
56
- event.stopPropagation()
57
- layer.onDismiss()
58
- }
59
-
60
- const onPointerDown = (event: Event): void => {
61
- if (target('outside') !== layer) return
62
- const root = ref.current
63
- const node = event.target
64
- if (root === null || !(node instanceof Node)) return
65
- if (root.contains(node)) return
66
-
67
- event.preventDefault()
68
- event.stopPropagation()
69
- layer.onDismiss()
70
- }
71
-
72
- document.addEventListener('keydown', onKeyDown, true)
73
- document.addEventListener('pointerdown', onPointerDown, true)
74
-
75
- return () => {
76
- document.removeEventListener('keydown', onKeyDown, true)
77
- document.removeEventListener('pointerdown', onPointerDown, true)
78
- const at = layers.indexOf(layer)
79
-
80
- if (at !== -1) layers.splice(at, 1)
81
- }
82
- }, [active, dismissOnEscape, dismissOnOutside, onDismiss, ref])
83
- }
84
-
85
- export function openLayers(): number {
86
- return layers.length
87
- }
package/dist/ui/door.ts DELETED
@@ -1,140 +0,0 @@
1
-
2
- export const PRESENTATIONS = ['page', 'sheet', 'popover', 'overlay', 'toast'] as const
3
- export type Presentation = (typeof PRESENTATIONS)[number]
4
-
5
- export const PRESENTATION_BEHAVIOUR: Readonly<Record<Presentation, {
6
- readonly portal: boolean
7
- readonly backdrop: boolean
8
- readonly trapFocus: boolean
9
- readonly returnFocus: boolean
10
- readonly dismissOnEscape: boolean
11
- readonly dismissOnOutside: boolean
12
- }>> = {
13
- page: { portal: false, backdrop: false, trapFocus: false, returnFocus: false, dismissOnEscape: false, dismissOnOutside: false },
14
- sheet: { portal: true, backdrop: true, trapFocus: true, returnFocus: true, dismissOnEscape: true, dismissOnOutside: true },
15
- overlay: { portal: true, backdrop: true, trapFocus: true, returnFocus: true, dismissOnEscape: true, dismissOnOutside: true },
16
-
17
- popover: { portal: true, backdrop: false, trapFocus: false, returnFocus: true, dismissOnEscape: true, dismissOnOutside: true },
18
-
19
- toast: { portal: true, backdrop: false, trapFocus: false, returnFocus: false, dismissOnEscape: false, dismissOnOutside: false },
20
- }
21
-
22
- export interface ClockSubscriber {
23
-
24
- (t: number): void
25
- }
26
-
27
- export class Ticker {
28
- #subscribers = new Set<() => void>()
29
- #stop: (() => void) | undefined
30
- readonly #every: (fn: () => void) => () => void
31
-
32
- constructor(every?: (fn: () => void) => () => void) {
33
- this.#every =
34
- every ??
35
- ((fn) => {
36
- const id = setInterval(fn, 1000)
37
- return () => clearInterval(id)
38
- })
39
- }
40
-
41
- get running(): boolean {
42
- return this.#stop !== undefined
43
- }
44
-
45
- subscribe(fn: () => void): () => void {
46
- this.#subscribers.add(fn)
47
- if (this.#stop === undefined) this.#stop = this.#every(() => this.tick())
48
- return () => {
49
- this.#subscribers.delete(fn)
50
-
51
- if (this.#subscribers.size === 0) {
52
- this.#stop?.()
53
- this.#stop = undefined
54
- }
55
- }
56
- }
57
-
58
- tick(): void {
59
- for (const fn of this.#subscribers) fn()
60
- }
61
- }
62
-
63
- export class Clock {
64
- #subscribers = new Set<ClockSubscriber>()
65
- #visible = true
66
- #running = false
67
- #t = 0
68
- #ticks = 0
69
- readonly #schedule: (fn: () => void) => void
70
-
71
- constructor(schedule?: (fn: () => void) => void) {
72
- this.#schedule =
73
- schedule ??
74
- ((fn) => {
75
- if (typeof requestAnimationFrame === 'function') requestAnimationFrame(() => fn())
76
- else setTimeout(fn, 16)
77
- })
78
- }
79
-
80
- get ticks(): number {
81
- return this.#ticks
82
- }
83
-
84
- get running(): boolean {
85
- return this.#running
86
- }
87
-
88
- subscribe(fn: ClockSubscriber): () => void {
89
- this.#subscribers.add(fn)
90
- this.#maybeStart()
91
- return () => {
92
- this.#subscribers.delete(fn)
93
- if (this.#subscribers.size === 0) this.#running = false
94
- }
95
- }
96
-
97
- setVisible(visible: boolean): void {
98
- this.#visible = visible
99
- if (visible) this.#maybeStart()
100
- else this.#running = false
101
- }
102
-
103
- tick(dt: number): void {
104
- if (!this.#visible) return
105
- this.#t += dt
106
- this.#ticks += 1
107
- for (const fn of this.#subscribers) fn(this.#t)
108
- }
109
-
110
- #maybeStart(): void {
111
- if (this.#running || !this.#visible || this.#subscribers.size === 0) return
112
- this.#running = true
113
- const loop = (): void => {
114
- if (!this.#running) return
115
- this.tick(1 / 60)
116
- this.#schedule(loop)
117
- }
118
- this.#schedule(loop)
119
- }
120
- }
121
-
122
- export interface Surface {
123
- readonly className?: string
124
-
125
- readonly holds: readonly string[]
126
-
127
- readonly marks: Readonly<Record<string, boolean>>
128
-
129
- readonly invokes: Readonly<Record<string, unknown>>
130
-
131
- readonly marked: (signal: string) => boolean
132
-
133
- readonly text: (key: string, params?: Readonly<Record<string, unknown>>) => string
134
-
135
- readonly a11y: Readonly<Record<string, string | boolean | number>>
136
-
137
- readonly measures: Readonly<Record<string, string | number>>
138
-
139
- readonly t: number
140
- }
package/dist/ui/drag.ts DELETED
@@ -1,316 +0,0 @@
1
-
2
-
3
- export const EDGE_SCROLL_DEFAULTS = {
4
-
5
- band: 48,
6
-
7
- speed: 900,
8
- } as const
9
-
10
- export interface Subject {
11
- readonly kind: string
12
- readonly id: string
13
- }
14
-
15
- export interface DragSession {
16
-
17
- readonly gesture: string
18
-
19
- readonly subject: readonly Subject[]
20
-
21
- readonly from: string
22
-
23
- readonly x: number
24
- readonly y: number
25
-
26
- readonly startedAt: number
27
-
28
- readonly at: number
29
-
30
- readonly over?: string
31
- readonly overSince: number
32
- }
33
-
34
- let SESSION: DragSession | undefined
35
-
36
- export function dragSession(): DragSession | undefined {
37
- return SESSION
38
- }
39
-
40
- const LISTENERS = new Set<() => void>()
41
-
42
- export function subscribeDrag(listener: () => void): () => void {
43
- LISTENERS.add(listener)
44
- return () => {
45
- LISTENERS.delete(listener)
46
- }
47
- }
48
-
49
- function announce(): void {
50
- for (const listener of LISTENERS) listener()
51
- }
52
-
53
- export type DragStatus = 'idle' | 'carrying' | 'over' | 'refusing' | 'elsewhere'
54
-
55
- export function dragStatusOf(hitBox: string, accepts: readonly string[] | undefined): DragStatus {
56
- const session = SESSION
57
- if (session === undefined) return 'idle'
58
- if (session.from === hitBox) return 'carrying'
59
- if (session.over !== hitBox) return 'elsewhere'
60
- return accepted(accepts, session) ? 'over' : 'refusing'
61
- }
62
-
63
- export interface Scroller {
64
-
65
- readonly top: number
66
- readonly bottom: number
67
- readonly left: number
68
- readonly right: number
69
-
70
- readonly scrollsVertically: boolean
71
- readonly scrollsHorizontally: boolean
72
-
73
- readonly scrollBy: (dx: number, dy: number) => void
74
- }
75
-
76
- export interface Layout {
77
-
78
- readonly hitBoxesAt: (x: number, y: number) => readonly string[]
79
-
80
- readonly scrollersAt: (x: number, y: number) => readonly Scroller[]
81
- }
82
-
83
- export const DOM_LAYOUT: Layout = {
84
- hitBoxesAt: (x, y) => {
85
- if (typeof document === 'undefined' || typeof document.elementsFromPoint !== 'function') return []
86
- const found: string[] = []
87
- for (const el of document.elementsFromPoint(x, y)) {
88
- const id = el.getAttribute('data-hitbox')
89
- if (id !== null) found.push(id)
90
- }
91
- return found
92
- },
93
- scrollersAt: (x, y) => {
94
- if (typeof document === 'undefined' || typeof document.elementsFromPoint !== 'function') return []
95
- const found: Scroller[] = []
96
- for (const el of document.elementsFromPoint(x, y)) {
97
- if (!(el instanceof HTMLElement)) continue
98
- const box = el.getBoundingClientRect()
99
- found.push({
100
- top: box.top,
101
- bottom: box.bottom,
102
- left: box.left,
103
- right: box.right,
104
- scrollsVertically: el.scrollHeight > el.clientHeight,
105
- scrollsHorizontally: el.scrollWidth > el.clientWidth,
106
- scrollBy: (dx, dy) => {
107
- el.scrollLeft += dx
108
- el.scrollTop += dy
109
- },
110
- })
111
- }
112
- return found
113
- },
114
- }
115
-
116
- let LAYOUT: Layout = DOM_LAYOUT
117
-
118
- export function setLayout(layout: Layout): () => void {
119
- const previous = LAYOUT
120
- LAYOUT = layout
121
- return () => {
122
- LAYOUT = previous
123
- }
124
- }
125
-
126
- export function currentLayout(): Layout {
127
- return LAYOUT
128
- }
129
-
130
- let CLAIM: string | undefined
131
-
132
- export function claimGesture(hitBox: string): boolean {
133
- if (CLAIM !== undefined && CLAIM !== hitBox) return false
134
- CLAIM = hitBox
135
- return true
136
- }
137
-
138
- export function releaseGesture(hitBox: string): void {
139
- if (CLAIM === hitBox) CLAIM = undefined
140
- }
141
-
142
- export function gestureClaim(): string | undefined {
143
- return CLAIM
144
- }
145
-
146
- export interface DropTarget {
147
- readonly id: string
148
-
149
- readonly accepts?: readonly string[]
150
-
151
- readonly collects?: () => Subject | readonly Subject[]
152
-
153
- readonly dwell?: number
154
-
155
- readonly onDrop?: (session: DragSession) => void
156
- }
157
-
158
- const TARGETS = new Map<string, DropTarget>()
159
-
160
- export function registerTarget(target: DropTarget): () => void {
161
- TARGETS.set(target.id, target)
162
- return () => {
163
- TARGETS.delete(target.id)
164
- }
165
- }
166
-
167
- export function targetFor(id: string): DropTarget | undefined {
168
- return TARGETS.get(id)
169
- }
170
-
171
- export function innermostTarget(stack: readonly string[], session: DragSession | undefined = SESSION): DropTarget | undefined {
172
- if (session === undefined) return undefined
173
- for (const id of stack) {
174
- const target = TARGETS.get(id)
175
- if (target !== undefined && accepted(target.accepts, session)) return target
176
- }
177
- return undefined
178
- }
179
-
180
- export interface BeginDrag {
181
- readonly gesture: string
182
- readonly subject: Subject | readonly Subject[]
183
- readonly from: string
184
- readonly x: number
185
- readonly y: number
186
-
187
- readonly t: number
188
- }
189
-
190
- export function beginDrag(input: BeginDrag): DragSession {
191
- if (SESSION !== undefined) {
192
- throw new Error(
193
- `a drag from "${SESSION.from}" is already in progress, so "${input.from}" cannot start one. ` +
194
- 'Two live sessions would both receive moves and the drop would commit whichever wrote last.',
195
- )
196
- }
197
- const subject = Array.isArray(input.subject) ? [...(input.subject as readonly Subject[])] : [input.subject as Subject]
198
- if (subject.length === 0) throw new Error('a drag carries something — beginDrag was given an empty subject')
199
-
200
- SESSION = {
201
- gesture: input.gesture,
202
- subject,
203
- from: input.from,
204
- x: input.x,
205
- y: input.y,
206
- startedAt: input.t,
207
- at: input.t,
208
- overSince: input.t,
209
- }
210
- announce()
211
- return SESSION
212
- }
213
-
214
- export interface MoveDrag {
215
- readonly x: number
216
- readonly y: number
217
-
218
- readonly over?: string
219
- readonly t: number
220
- }
221
-
222
- export function moveDrag(input: MoveDrag): DragSession | undefined {
223
- if (SESSION === undefined) return undefined
224
- const arrived = input.over !== SESSION.over
225
- SESSION = {
226
- ...SESSION,
227
- x: input.x,
228
- y: input.y,
229
- at: input.t,
230
- ...(input.over === undefined ? { over: undefined } : { over: input.over }),
231
- overSince: arrived ? input.t : SESSION.overSince,
232
- }
233
- announce()
234
- return SESSION
235
- }
236
-
237
- export function collect(more: Subject | readonly Subject[]): DragSession | undefined {
238
- if (SESSION === undefined) return undefined
239
- const added = Array.isArray(more) ? (more as readonly Subject[]) : [more as Subject]
240
- const have = new Set(SESSION.subject.map((s) => `${s.kind}:${s.id}`))
241
- const fresh = added.filter((s) => !have.has(`${s.kind}:${s.id}`))
242
- if (fresh.length === 0) return SESSION
243
- SESSION = { ...SESSION, subject: [...SESSION.subject, ...fresh] }
244
- announce()
245
- return SESSION
246
- }
247
-
248
- export function endDrag(): DragSession | undefined {
249
- const finished = SESSION
250
- SESSION = undefined
251
- announce()
252
- return finished
253
- }
254
-
255
- export function resetDrag(): void {
256
- SESSION = undefined
257
- CLAIM = undefined
258
- TARGETS.clear()
259
- announce()
260
- }
261
-
262
- export function dwell(session: DragSession | undefined = SESSION): number {
263
- if (session?.over === undefined) return 0
264
- return Math.max(0, session.at - session.overSince)
265
- }
266
-
267
- export function accepted(kinds: readonly string[] | undefined, session: DragSession | undefined = SESSION): boolean {
268
- if (kinds === undefined || session === undefined) return false
269
- return session.subject.every((s) => kinds.includes(s.kind))
270
- }
271
-
272
- export interface EdgeScrollTuning {
273
- readonly band?: number
274
- readonly speed?: number
275
- }
276
-
277
- export function edgeScroll(
278
- pointer: number,
279
- start: number,
280
- end: number,
281
- dt: number,
282
- { band = EDGE_SCROLL_DEFAULTS.band, speed = EDGE_SCROLL_DEFAULTS.speed }: EdgeScrollTuning = {},
283
- ): number {
284
- const extent = end - start
285
- if (extent <= 0 || speed <= 0 || band <= 0 || dt <= 0) return 0
286
-
287
- const reach = Math.min(band, extent / 2)
288
-
289
- const intoStart = start + reach - pointer
290
- if (intoStart > 0) return -Math.min(1, intoStart / reach) * speed * dt
291
-
292
- const intoEnd = pointer - (end - reach)
293
- if (intoEnd > 0) return Math.min(1, intoEnd / reach) * speed * dt
294
-
295
- return 0
296
- }
297
-
298
- export function pullEdges(x: number, y: number, dt: number, tuning: EdgeScrollTuning = {}, layout: Layout = LAYOUT): { dx: number; dy: number } {
299
- for (const scroller of layout.scrollersAt(x, y)) {
300
- if (scroller.scrollsVertically) {
301
- const dy = edgeScroll(y, scroller.top, scroller.bottom, dt, tuning)
302
- if (dy !== 0) {
303
- scroller.scrollBy(0, dy)
304
- return { dx: 0, dy }
305
- }
306
- }
307
- if (scroller.scrollsHorizontally) {
308
- const dx = edgeScroll(x, scroller.left, scroller.right, dt, tuning)
309
- if (dx !== 0) {
310
- scroller.scrollBy(dx, 0)
311
- return { dx, dy: 0 }
312
- }
313
- }
314
- }
315
- return { dx: 0, dy: 0 }
316
- }
@@ -1,47 +0,0 @@
1
- 'use client'
2
-
3
- import { roleClass } from '../fonts.ts'
4
- import type { TabItem } from '../disclosure.tsx'
5
-
6
- import type { Dispatching } from '../checked.ts'
7
- import { type Element, element, useSurface } from '../wrappers.tsx'
8
- import { Each } from '../elements/each.tsx'
9
- import { shown, section, header, region, uses } from './accordion.works.ts'
10
-
11
-
12
- export interface AccordionProps {
13
- readonly items: readonly TabItem[]
14
- readonly initial: readonly string[]
15
- readonly exclusive?: boolean
16
- readonly onChange?: (said: readonly string[]) => void
17
- readonly dispatch?: keyof Dispatching
18
- readonly input?: Readonly<Record<string, unknown>>
19
- readonly invokeOn?: 'press' | 'shown'
20
- }
21
-
22
- export const Accordion: Element<'Accordion', AccordionProps> = element(
23
- 'Accordion',
24
- (props: AccordionProps, className) => {
25
- const kept = uses(props)
26
- const surface = useSurface({ className: roleClass(undefined, className),
27
- invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) },
28
- holds: [
29
- ...(kept.holds ?? []),
30
- ],
31
- ...(kept.animates === undefined ? {} : { animates: kept.animates }), })
32
- return (
33
- <div className={surface.className} {...surface.marks} {...surface.invokes} {...surface.a11y} {...(kept.on ?? {})}>
34
- <Each rows={props.items} by={'id'}>
35
- {(each0, at) => (
36
- <div {...section(props, kept, each0, at)}>
37
- <button {...header(props, kept, each0, at)}>{shown(props, each0, surface, kept).label}</button>
38
- <div {...region(props, kept, each0, at)}>{shown(props, each0, surface, kept).inside}</div>
39
- </div>
40
- )}
41
- </Each>
42
- </div>
43
- )
44
- },
45
- 'e-accordion',
46
- {"card":"thread-card"},
47
- )