@hina-ui/vue 1.7.10 → 1.7.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 (45) hide show
  1. package/dist/index.js +901 -815
  2. package/dist/index.js.map +1 -1
  3. package/dist/shared/behavior/spoiler/painter.ts +87 -60
  4. package/dist/shared/behavior/spoiler/worklet.ts +7 -9
  5. package/dist/shared/lib/css-time.ts +6 -0
  6. package/dist/shared/lib/line-clamp.ts +8 -8
  7. package/dist/shared/primitives/pin-input/index.ts +394 -97
  8. package/dist/shared/styles/interaction.css +6 -2
  9. package/dist/shared/styles/line-clamp.css +6 -2
  10. package/dist/shared/styles/motion.css +7 -1
  11. package/dist/shared/styles/pin-input.css +84 -0
  12. package/dist/shared/styles/spoiler.css +6 -0
  13. package/dist/shared/styles/tokens.css +1 -0
  14. package/dist/shared/styles/typography.css +18 -3
  15. package/dist/shared/variants/pin-input.ts +26 -7
  16. package/dist/types/shared/src/behavior/spoiler/painter.d.ts +10 -9
  17. package/dist/types/shared/src/behavior/spoiler/painter.d.ts.map +1 -1
  18. package/dist/types/shared/src/behavior/spoiler/worklet.d.ts.map +1 -1
  19. package/dist/types/shared/src/lib/css-time.d.ts +2 -0
  20. package/dist/types/shared/src/lib/css-time.d.ts.map +1 -0
  21. package/dist/types/shared/src/lib/line-clamp.d.ts.map +1 -1
  22. package/dist/types/shared/src/primitives/pin-input/index.d.ts +35 -27
  23. package/dist/types/shared/src/primitives/pin-input/index.d.ts.map +1 -1
  24. package/dist/types/shared/src/variants/pin-input.d.ts +30 -10
  25. package/dist/types/shared/src/variants/pin-input.d.ts.map +1 -1
  26. package/dist/types/vue/src/components/image/composables/useImage.d.ts.map +1 -1
  27. package/dist/types/vue/src/components/pin-input/PinInput.vue.d.ts +2 -0
  28. package/dist/types/vue/src/components/pin-input/PinInput.vue.d.ts.map +1 -1
  29. package/package.json +1 -1
  30. package/src/components/image/composables/useImage.ts +2 -1
  31. package/src/components/pin-input/PinInput.vue +103 -33
  32. package/dist/shared/primitives/pin-input/LICENSE +0 -21
  33. package/dist/types/vue/src/primitives/pin-input/PinInputInput.d.ts +0 -52
  34. package/dist/types/vue/src/primitives/pin-input/PinInputInput.d.ts.map +0 -1
  35. package/dist/types/vue/src/primitives/pin-input/PinInputRoot.d.ts +0 -149
  36. package/dist/types/vue/src/primitives/pin-input/PinInputRoot.d.ts.map +0 -1
  37. package/dist/types/vue/src/primitives/pin-input/context.d.ts +0 -20
  38. package/dist/types/vue/src/primitives/pin-input/context.d.ts.map +0 -1
  39. package/dist/types/vue/src/primitives/pin-input/index.d.ts +0 -4
  40. package/dist/types/vue/src/primitives/pin-input/index.d.ts.map +0 -1
  41. package/src/primitives/pin-input/LICENSE +0 -44
  42. package/src/primitives/pin-input/PinInputInput.ts +0 -218
  43. package/src/primitives/pin-input/PinInputRoot.ts +0 -136
  44. package/src/primitives/pin-input/context.ts +0 -24
  45. package/src/primitives/pin-input/index.ts +0 -3
@@ -32,26 +32,50 @@ const DEFAULTS = {
32
32
  accent: '0 0% 20%',
33
33
  } as const
34
34
 
35
+ export const SPOILER_WORKLET_INPUTS = [
36
+ '--hn-nz-t',
37
+ '--hn-nz-stop',
38
+ '--hn-nz-fade',
39
+ '--hn-nz-gap',
40
+ '--hn-nz-accent',
41
+ '--hn-nz-words',
42
+ '--hn-nz-density',
43
+ ] as const
44
+
35
45
  const BLOCK_MAX_TILE = 293
36
46
  const INLINE_MAX_TILE = 333
37
47
  const GAP_RATIO = 8
38
48
  const DEFAULT_FADE_S = 1.5
49
+ const CLOCK_S = 3600
50
+
51
+ const painters = new WeakMap<Element, SpoilerPainter>()
52
+ let viewport: IntersectionObserver | undefined
53
+
54
+ function watchViewport() {
55
+ viewport ??= new IntersectionObserver(entries => {
56
+ for (const entry of entries) painters.get(entry.target)?.setInView(entry.isIntersecting)
57
+ })
58
+ return viewport
59
+ }
39
60
 
40
61
  export class SpoilerPainter {
41
62
  readonly el: HTMLElement
42
63
  maxFPS: number = DEFAULTS.fps
43
64
 
44
- private t = 0
45
- private t0 = 0
46
- private tstop: number | null = null
47
- private raf: number | null = null
48
- private fadeS = 0
65
+ private clock: Animation | null = null
66
+ private settle: ReturnType<typeof setTimeout> | undefined
67
+ private running = false
68
+ private inView = false
49
69
  private hidden = false
50
70
  private destroyed = false
51
71
 
52
72
  constructor(el: HTMLElement, options: SpoilerPainterOptions = {}) {
53
73
  registerSpoilerWorklet()
54
74
  this.el = el
75
+ painters.set(el, this)
76
+ watchViewport().observe(el)
77
+ this.setFade(0)
78
+ this.setTstop(null)
55
79
  this.update(options)
56
80
  }
57
81
 
@@ -68,13 +92,19 @@ export class SpoilerPainter {
68
92
  }: SpoilerPainterOptions = {}) {
69
93
  if (this.destroyed) return
70
94
 
71
- this.maxFPS = prefersReducedMotion() ? 0 : fps
95
+ const maxFPS = prefersReducedMotion() ? 0 : fps
96
+ if (maxFPS !== this.maxFPS) {
97
+ this.maxFPS = maxFPS
98
+ if (maxFPS > 0) this.clock?.effect?.updateTiming({ easing: this.easing() })
99
+ else this.stopClock()
100
+ }
72
101
 
73
102
  this.el.style.setProperty('--hn-nz-density', String(density))
74
103
  this.el.style.setProperty('--hn-nz-accent', accent)
75
104
 
76
105
  const isBlockElement = getComputedStyle(this.el).getPropertyValue('display') !== 'inline'
77
106
  this.useBackgroundStyle('auto', 'auto', false)
107
+ this.el.style.setProperty('--hn-nz-gap', '0px 0px')
78
108
 
79
109
  if (isBlockElement) {
80
110
  this.el.style.setProperty('--hn-nz-words', 'false')
@@ -109,23 +139,21 @@ export class SpoilerPainter {
109
139
  this.el.style.background = `paint(hn-spoiler) ${repeatAndPosition} / ${w} ${h}`
110
140
  }
111
141
 
112
- private setFade(value: number) {
113
- this.fadeS = value
114
- this.el.style.setProperty('--hn-nz-fade', `${value}s`)
142
+ private easing() {
143
+ return `steps(${Math.round(CLOCK_S * this.maxFPS)})`
115
144
  }
116
145
 
117
- private setT(value: number) {
118
- this.t = value
119
- this.el.style.setProperty('--hn-nz-t', value.toFixed(3))
146
+ private time() {
147
+ const elapsed = Number(this.clock?.currentTime ?? 0) / 1000
148
+ return this.maxFPS > 0 ? Math.floor(elapsed * this.maxFPS) / this.maxFPS : 0
149
+ }
150
+
151
+ private setFade(value: number) {
152
+ this.el.style.setProperty('--hn-nz-fade', `${value}s`)
120
153
  }
121
154
 
122
155
  private setTstop(value: number | null) {
123
- this.tstop = value
124
- if (value !== null) {
125
- this.el.style.setProperty('--hn-nz-stop', value.toFixed(3))
126
- } else {
127
- this.el.style.removeProperty('--hn-nz-stop')
128
- }
156
+ this.el.style.setProperty('--hn-nz-stop', value === null ? 'Infinity' : value.toFixed(3))
129
157
  }
130
158
 
131
159
  private parseFade(value: number | boolean | undefined) {
@@ -133,13 +161,40 @@ export class SpoilerPainter {
133
161
  return value === true || value === undefined ? DEFAULT_FADE_S : Number(value)
134
162
  }
135
163
 
164
+ private sync() {
165
+ if (!this.clock) return
166
+ if (this.running && this.inView) this.clock.play()
167
+ else this.clock.pause()
168
+ }
169
+
170
+ private stopClock() {
171
+ clearTimeout(this.settle)
172
+ this.settle = undefined
173
+ this.clock?.cancel()
174
+ this.clock = null
175
+ this.running = false
176
+ }
177
+
178
+ setInView(value: boolean) {
179
+ if (this.destroyed || value === this.inView) return
180
+ this.inView = value
181
+ this.sync()
182
+ }
183
+
136
184
  hide(animate?: number | boolean) {
137
185
  if (this.destroyed) return
138
186
  this.hidden = true
139
187
  this.setFade(this.parseFade(animate))
140
188
  this.setTstop(null)
141
- this.setT(0)
142
- this.startAnimation()
189
+ this.stopClock()
190
+ if (this.maxFPS <= 0) return
191
+ this.clock = this.el.animate({ '--hn-nz-t': [0, CLOCK_S] } as PropertyIndexedKeyframes, {
192
+ duration: CLOCK_S * 1000,
193
+ easing: this.easing(),
194
+ iterations: Infinity,
195
+ })
196
+ this.running = true
197
+ this.sync()
143
198
  }
144
199
 
145
200
  reveal(animate?: number | boolean) {
@@ -147,51 +202,23 @@ export class SpoilerPainter {
147
202
  this.hidden = false
148
203
  const duration = this.parseFade(animate)
149
204
  this.setFade(duration)
150
- this.setTstop(this.t)
151
- if (duration <= 0) this.stopAnimation()
152
- }
153
-
154
- private frame = (now: DOMHighResTimeStamp) => {
155
- if (this.destroyed) return
156
- const shouldStop = this.tstop !== null && this.t > this.tstop + this.fadeS
157
-
158
- if (this.maxFPS > 0 && !shouldStop) {
159
- this.raf = requestAnimationFrame(this.frame)
160
- }
161
-
162
- const dt = now - this.t0
163
- if (dt > 0 && dt < 1000 / this.maxFPS) return
164
-
165
- this.setT(this.t + dt / 1000)
166
- this.t0 = now
167
- }
168
-
169
- private startAnimation() {
170
- this.t0 = performance.now()
171
- this.frame(this.t0)
172
- }
173
-
174
- private stopAnimation() {
175
- if (this.raf !== null) {
176
- cancelAnimationFrame(this.raf)
177
- this.raf = null
205
+ this.setTstop(this.time())
206
+ clearTimeout(this.settle)
207
+ const stop = () => {
208
+ this.settle = undefined
209
+ this.running = false
210
+ this.sync()
178
211
  }
212
+ if (duration <= 0) stop()
213
+ else this.settle = setTimeout(stop, duration * 1000 + 1000 / this.maxFPS)
179
214
  }
180
215
 
181
216
  destroy() {
182
217
  this.destroyed = true
183
- this.stopAnimation()
218
+ this.stopClock()
219
+ viewport?.unobserve(this.el)
220
+ painters.delete(this.el)
184
221
  this.el.style.removeProperty('background')
185
- for (const prop of [
186
- '--hn-nz-t',
187
- '--hn-nz-stop',
188
- '--hn-nz-fade',
189
- '--hn-nz-gap',
190
- '--hn-nz-words',
191
- '--hn-nz-density',
192
- '--hn-nz-accent',
193
- ]) {
194
- this.el.style.removeProperty(prop)
195
- }
222
+ for (const prop of SPOILER_WORKLET_INPUTS) this.el.style.removeProperty(prop)
196
223
  }
197
224
  }
@@ -11,10 +11,6 @@ const workletMain = () => {
11
11
  0x7fffffff
12
12
 
13
13
  const pol2vec = (l: number, a = 0) => [l * M.cos(a), l * M.sin(a)] as const
14
- const vecmag = ([x, y]: readonly number[]) => M.sqrt(x! * x! + y! * y!)
15
- const vecnorm = ([x, y]: readonly number[], l = vecmag([x!, y!])) =>
16
- l === 0 ? ([0, 0] as const) : ([x! / l, y! / l] as const)
17
-
18
14
  const trapezoidalWave = (l: number, a: number, b: number) => {
19
15
  const s = M.max(a, l - b)
20
16
  return (t: number) => {
@@ -144,20 +140,21 @@ const workletMain = () => {
144
140
 
145
141
  ctx.clearRect(0, 0, size.width, size.height)
146
142
 
143
+ const l = parseInt(accent[2] ?? '0')
144
+ const ldir = l > 50 ? -1 : 1
145
+ const hue = `hsl(${accent[0]} ${accent[1]} `
146
+
147
147
  for (let i = 0; i < world.n; ++i) {
148
148
  const x0 = hgap + wordDist(rand())
149
149
  const y0 = rand(vgap, height - vgap)
150
150
  const v0mag = rand(vmin, vmax)
151
151
  const size0 = rand(1, 1 + sizedev)
152
152
 
153
- const l = parseInt(accent[2] ?? '0')
154
- const ldir = l > 50 ? -1 : 1
155
153
  const lightness = M.floor(clamp(0, l + ldir * rand(0, 30), 100))
156
154
 
157
155
  const v0 = pol2vec(v0mag, rand(0, M.PI * 2))
158
156
  const vx0 = v0[0]
159
157
  const vy0 = v0[1]
160
- vecnorm(v0)
161
158
 
162
159
  const shape = rand() > 0.5 ? 'square' : 'circle'
163
160
  const lifetime = rand(0.3, 1.5)
@@ -175,12 +172,13 @@ const workletMain = () => {
175
172
  const y = y0 + vy0 * t
176
173
 
177
174
  const fade = animateFadeInOut(world, i, fadeDuration)
178
- const alpha = fade * (1 - t / lifetime)
175
+ const alpha = M.round(fade * (1 - t / lifetime) * 100)
179
176
  const psize = fade * (size0 * visibilityFn(t))
177
+ if (alpha <= 0 || psize <= 0) continue
180
178
 
179
+ ctx.fillStyle = `${hue}${lightness}% / ${alpha}%)`
181
180
  for (const [wx, wy] of cycleBounds([x, y], [width, height], psize / 2)) {
182
181
  ctx.beginPath()
183
- ctx.fillStyle = `hsl(${accent[0]} ${accent[1]} ${lightness}% / ${M.round(alpha * 100)}%)`
184
182
  if (shape === 'square') {
185
183
  ctx.rect(wx!, wy!, psize, psize)
186
184
  } else {
@@ -0,0 +1,6 @@
1
+ export function cssTimeToMs(value: string) {
2
+ const text = value.trim().toLowerCase()
3
+ const amount = Number.parseFloat(text)
4
+ if (!Number.isFinite(amount) || amount < 0) return 0
5
+ return text.endsWith('s') && !text.endsWith('ms') ? amount * 1000 : amount
6
+ }
@@ -5,18 +5,17 @@ export function lineClampCount(lines: number | undefined) {
5
5
  return Math.max(1, Math.floor(lines))
6
6
  }
7
7
 
8
- function measureHeights(content: HTMLElement) {
9
- content.setAttribute('data-measuring', '')
10
- const collapsed = content.clientHeight
11
- const full = content.scrollHeight
8
+ function measure(content: HTMLElement, layout: 'clamped' | 'full') {
9
+ content.setAttribute('data-measuring', layout)
10
+ const size = { height: content.clientHeight, overflow: content.scrollHeight }
12
11
  content.removeAttribute('data-measuring')
13
- return { collapsed, full }
12
+ return size
14
13
  }
15
14
 
16
15
  export function measureLineClamp(content: HTMLElement) {
17
16
  if (content.hasAttribute('data-animating')) return null
18
- const { collapsed, full } = measureHeights(content)
19
- return full - collapsed > 1
17
+ const { height, overflow } = measure(content, 'clamped')
18
+ return overflow - height > 1
20
19
  }
21
20
 
22
21
  function toMs(value: string) {
@@ -33,7 +32,8 @@ export function animateLineClamp(content: HTMLElement, expanded: boolean, done:
33
32
  const fade = running ? getComputedStyle(content).getPropertyValue(FADE) : ''
34
33
 
35
34
  content.style.transition = 'none'
36
- const { collapsed, full } = measureHeights(content)
35
+ const collapsed = measure(content, 'clamped').height
36
+ const full = measure(content, 'full').height
37
37
  const from = current ?? (expanded ? collapsed : full)
38
38
  const to = expanded ? full : collapsed
39
39
  content.toggleAttribute('data-expanded', !expanded)