@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.
- package/dist/index.js +901 -815
- package/dist/index.js.map +1 -1
- package/dist/shared/behavior/spoiler/painter.ts +87 -60
- package/dist/shared/behavior/spoiler/worklet.ts +7 -9
- package/dist/shared/lib/css-time.ts +6 -0
- package/dist/shared/lib/line-clamp.ts +8 -8
- package/dist/shared/primitives/pin-input/index.ts +394 -97
- package/dist/shared/styles/interaction.css +6 -2
- package/dist/shared/styles/line-clamp.css +6 -2
- package/dist/shared/styles/motion.css +7 -1
- package/dist/shared/styles/pin-input.css +84 -0
- package/dist/shared/styles/spoiler.css +6 -0
- package/dist/shared/styles/tokens.css +1 -0
- package/dist/shared/styles/typography.css +18 -3
- package/dist/shared/variants/pin-input.ts +26 -7
- package/dist/types/shared/src/behavior/spoiler/painter.d.ts +10 -9
- package/dist/types/shared/src/behavior/spoiler/painter.d.ts.map +1 -1
- package/dist/types/shared/src/behavior/spoiler/worklet.d.ts.map +1 -1
- package/dist/types/shared/src/lib/css-time.d.ts +2 -0
- package/dist/types/shared/src/lib/css-time.d.ts.map +1 -0
- package/dist/types/shared/src/lib/line-clamp.d.ts.map +1 -1
- package/dist/types/shared/src/primitives/pin-input/index.d.ts +35 -27
- package/dist/types/shared/src/primitives/pin-input/index.d.ts.map +1 -1
- package/dist/types/shared/src/variants/pin-input.d.ts +30 -10
- package/dist/types/shared/src/variants/pin-input.d.ts.map +1 -1
- package/dist/types/vue/src/components/image/composables/useImage.d.ts.map +1 -1
- package/dist/types/vue/src/components/pin-input/PinInput.vue.d.ts +2 -0
- package/dist/types/vue/src/components/pin-input/PinInput.vue.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/image/composables/useImage.ts +2 -1
- package/src/components/pin-input/PinInput.vue +103 -33
- package/dist/shared/primitives/pin-input/LICENSE +0 -21
- package/dist/types/vue/src/primitives/pin-input/PinInputInput.d.ts +0 -52
- package/dist/types/vue/src/primitives/pin-input/PinInputInput.d.ts.map +0 -1
- package/dist/types/vue/src/primitives/pin-input/PinInputRoot.d.ts +0 -149
- package/dist/types/vue/src/primitives/pin-input/PinInputRoot.d.ts.map +0 -1
- package/dist/types/vue/src/primitives/pin-input/context.d.ts +0 -20
- package/dist/types/vue/src/primitives/pin-input/context.d.ts.map +0 -1
- package/dist/types/vue/src/primitives/pin-input/index.d.ts +0 -4
- package/dist/types/vue/src/primitives/pin-input/index.d.ts.map +0 -1
- package/src/primitives/pin-input/LICENSE +0 -44
- package/src/primitives/pin-input/PinInputInput.ts +0 -218
- package/src/primitives/pin-input/PinInputRoot.ts +0 -136
- package/src/primitives/pin-input/context.ts +0 -24
- package/src/primitives/pin-input/index.ts +0 -3
|
@@ -1,126 +1,423 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
export interface PinRange {
|
|
2
|
+
start: number
|
|
3
|
+
end: number
|
|
4
|
+
}
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
export interface PinInputSelection extends PinRange {
|
|
7
|
+
focused: boolean
|
|
8
|
+
}
|
|
8
9
|
|
|
9
|
-
export
|
|
10
|
-
|
|
10
|
+
export interface PinEdit extends PinRange {
|
|
11
|
+
value: string
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
export
|
|
14
|
-
|
|
14
|
+
export interface PinCell {
|
|
15
|
+
char: string
|
|
16
|
+
active: boolean
|
|
17
|
+
caret: boolean
|
|
15
18
|
}
|
|
16
19
|
|
|
17
|
-
export
|
|
18
|
-
|
|
20
|
+
export interface PinInputOptions {
|
|
21
|
+
length: () => number
|
|
22
|
+
numeric: () => boolean
|
|
23
|
+
value: () => string
|
|
24
|
+
rtl: () => boolean
|
|
25
|
+
cells: () => ArrayLike<Element>
|
|
26
|
+
change: (value: string) => void
|
|
27
|
+
select: (selection: PinInputSelection) => void
|
|
19
28
|
}
|
|
20
29
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
30
|
+
export interface PinInputController {
|
|
31
|
+
sync: () => void
|
|
32
|
+
dispose: () => void
|
|
24
33
|
}
|
|
25
34
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
) {
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
else values[index] = number
|
|
37
|
-
} else values[index] = value
|
|
38
|
-
return removeTrailingEmptyStrings(values)
|
|
35
|
+
const FULL_WIDTH = /[!-~]/g
|
|
36
|
+
const UNSUPPORTED = /[\s\p{C}]|[\u{10000}-\u{10FFFF}]/gu
|
|
37
|
+
const NON_DIGIT = /\D/g
|
|
38
|
+
const IDLE: PinInputSelection = { focused: false, start: 0, end: 0 }
|
|
39
|
+
|
|
40
|
+
export function cleanPinText(text: string, numeric: boolean) {
|
|
41
|
+
const folded = text.replace(FULL_WIDTH, character =>
|
|
42
|
+
String.fromCharCode(character.charCodeAt(0) - 0xfee0),
|
|
43
|
+
)
|
|
44
|
+
return folded.replace(numeric ? NON_DIGIT : UNSUPPORTED, '')
|
|
39
45
|
}
|
|
40
46
|
|
|
41
|
-
export function
|
|
42
|
-
|
|
43
|
-
characters: string,
|
|
44
|
-
index: number,
|
|
45
|
-
count: number,
|
|
46
|
-
numeric: boolean,
|
|
47
|
-
) {
|
|
48
|
-
const values = [...current]
|
|
49
|
-
const start = characters.length >= count ? 0 : index
|
|
50
|
-
const end = Math.min(start + characters.length, count)
|
|
51
|
-
const filled: number[] = []
|
|
52
|
-
for (let at = start; at < end; at++) {
|
|
53
|
-
const character = characters[at - start]!
|
|
54
|
-
if (numeric) {
|
|
55
|
-
const number = Number.parseInt(character)
|
|
56
|
-
if (Number.isNaN(number)) continue
|
|
57
|
-
values[at] = number
|
|
58
|
-
} else values[at] = character
|
|
59
|
-
filled.push(at)
|
|
60
|
-
}
|
|
61
|
-
return { values, filled, end }
|
|
47
|
+
export function pinInputValue(value: string, length: number) {
|
|
48
|
+
return value.slice(0, Math.max(0, length))
|
|
62
49
|
}
|
|
63
50
|
|
|
64
|
-
export
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (text.length > 1) return { kind: 'spread', characters: text }
|
|
74
|
-
return { kind: 'single', character: text }
|
|
51
|
+
export function pinSelectionAt(
|
|
52
|
+
caret: number,
|
|
53
|
+
size: number,
|
|
54
|
+
length: number,
|
|
55
|
+
backward = false,
|
|
56
|
+
): [number, number] {
|
|
57
|
+
if (caret >= size) return size < length || !size ? [size, size] : [size - 1, size]
|
|
58
|
+
if (backward && caret > 0) return [caret - 1, caret]
|
|
59
|
+
return [caret, caret + 1]
|
|
75
60
|
}
|
|
76
61
|
|
|
77
|
-
export function
|
|
78
|
-
|
|
79
|
-
|
|
62
|
+
export function editPinValue(
|
|
63
|
+
base: string,
|
|
64
|
+
raw: string,
|
|
65
|
+
selection: PinRange | null,
|
|
66
|
+
length: number,
|
|
80
67
|
numeric: boolean,
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
68
|
+
type = '',
|
|
69
|
+
): PinEdit {
|
|
70
|
+
const limit = Math.min(base.length, raw.length)
|
|
71
|
+
let prefix = 0
|
|
72
|
+
while (prefix < limit && base[prefix] === raw[prefix]) prefix++
|
|
73
|
+
if (selection) prefix = Math.min(prefix, selection.start)
|
|
74
|
+
let suffix = 0
|
|
75
|
+
while (suffix < limit - prefix && base[base.length - 1 - suffix] === raw[raw.length - 1 - suffix])
|
|
76
|
+
suffix++
|
|
77
|
+
if (selection) suffix = Math.max(0, Math.min(suffix, base.length - selection.end))
|
|
78
|
+
const inserted = raw.slice(prefix, raw.length - suffix)
|
|
79
|
+
const removed = base.length - suffix - prefix
|
|
80
|
+
const kept = cleanPinText(inserted, numeric)
|
|
81
|
+
if (inserted && !kept)
|
|
82
|
+
return {
|
|
83
|
+
value: base,
|
|
84
|
+
start: selection?.start ?? prefix,
|
|
85
|
+
end: selection?.end ?? prefix + removed,
|
|
86
|
+
}
|
|
87
|
+
const value =
|
|
88
|
+
kept.length >= length
|
|
89
|
+
? kept.slice(0, length)
|
|
90
|
+
: (base.slice(0, prefix) + kept + base.slice(prefix + Math.max(removed, kept.length))).slice(
|
|
91
|
+
0,
|
|
92
|
+
length,
|
|
93
|
+
)
|
|
94
|
+
const caret = kept.length >= length ? length : prefix + kept.length
|
|
95
|
+
const [start, end] = pinSelectionAt(
|
|
96
|
+
caret,
|
|
97
|
+
value.length,
|
|
98
|
+
length,
|
|
99
|
+
!kept && type.includes('Backward'),
|
|
100
|
+
)
|
|
101
|
+
return { value, start, end }
|
|
85
102
|
}
|
|
86
103
|
|
|
87
|
-
export function
|
|
88
|
-
|
|
104
|
+
export function movePinSelection(
|
|
105
|
+
selection: PinRange,
|
|
106
|
+
backward: boolean,
|
|
107
|
+
size: number,
|
|
108
|
+
length: number,
|
|
109
|
+
target: number | 'previous' | 'next',
|
|
110
|
+
extend: boolean,
|
|
111
|
+
): PinRange & { backward: boolean } {
|
|
112
|
+
const last = Math.max(0, Math.min(size, length - 1))
|
|
113
|
+
const collapsed = selection.start === selection.end
|
|
114
|
+
const focus = collapsed || backward ? selection.start : selection.end - 1
|
|
115
|
+
const anchor = collapsed ? selection.start : backward ? selection.end - 1 : selection.start
|
|
116
|
+
const wide = selection.end - selection.start > 1
|
|
117
|
+
const stepped =
|
|
118
|
+
target === 'previous'
|
|
119
|
+
? wide && !extend
|
|
120
|
+
? selection.start
|
|
121
|
+
: focus - 1
|
|
122
|
+
: target === 'next'
|
|
123
|
+
? wide && !extend
|
|
124
|
+
? selection.end
|
|
125
|
+
: focus + 1
|
|
126
|
+
: target
|
|
127
|
+
const next = Math.max(0, Math.min(stepped, last))
|
|
128
|
+
if (!extend) {
|
|
129
|
+
const [start, end] = pinSelectionAt(next, size, length)
|
|
130
|
+
return { start, end, backward: false }
|
|
131
|
+
}
|
|
132
|
+
const start = Math.min(anchor, next)
|
|
133
|
+
const end = Math.min(Math.max(anchor, next) + 1, size)
|
|
134
|
+
return { start: Math.min(start, end), end, backward: next < anchor }
|
|
89
135
|
}
|
|
90
136
|
|
|
91
|
-
export function
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
137
|
+
export function pinCells(value: string, length: number, selection: PinInputSelection): PinCell[] {
|
|
138
|
+
const collapsed = selection.start === selection.end
|
|
139
|
+
return Array.from({ length: Math.max(0, length) }, (_, index) => {
|
|
140
|
+
const char = value[index] ?? ''
|
|
141
|
+
const active =
|
|
142
|
+
selection.focused &&
|
|
143
|
+
(collapsed ? index === selection.start : index >= selection.start && index < selection.end)
|
|
144
|
+
return { char, active, caret: active && !char }
|
|
145
|
+
})
|
|
95
146
|
}
|
|
96
147
|
|
|
97
|
-
export function
|
|
98
|
-
|
|
148
|
+
export function pinCellAt(cells: ArrayLike<Element>, x: number) {
|
|
149
|
+
let nearest = 0
|
|
150
|
+
let distance = Infinity
|
|
151
|
+
for (let index = 0; index < cells.length; index++) {
|
|
152
|
+
const rect = cells[index]!.getBoundingClientRect()
|
|
153
|
+
const gap = x < rect.left ? rect.left - x : x > rect.right ? x - rect.right : 0
|
|
154
|
+
if (gap < distance) {
|
|
155
|
+
distance = gap
|
|
156
|
+
nearest = index
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return nearest
|
|
99
160
|
}
|
|
100
161
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
dir: 'ltr' | 'rtl',
|
|
106
|
-
) {
|
|
107
|
-
arrowNavigation(event, current, undefined, {
|
|
108
|
-
itemsArray: inputs,
|
|
109
|
-
focus: true,
|
|
110
|
-
loop: false,
|
|
111
|
-
arrowKeyOptions: 'horizontal',
|
|
112
|
-
dir,
|
|
113
|
-
})
|
|
162
|
+
function activeElement(document: Document) {
|
|
163
|
+
let active = document.activeElement
|
|
164
|
+
while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement
|
|
165
|
+
return active
|
|
114
166
|
}
|
|
115
167
|
|
|
116
|
-
export function
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
168
|
+
export function createPinInput(
|
|
169
|
+
input: HTMLInputElement,
|
|
170
|
+
options: PinInputOptions,
|
|
171
|
+
): PinInputController {
|
|
172
|
+
const document = input.ownerDocument
|
|
173
|
+
const shown = () => pinInputValue(options.value(), options.length())
|
|
174
|
+
let text = shown()
|
|
175
|
+
let base: string | null = null
|
|
176
|
+
let anchor: PinRange | null = null
|
|
177
|
+
let live: PinEdit | null = null
|
|
178
|
+
let composing = false
|
|
179
|
+
let pointer = 'mouse'
|
|
180
|
+
let pressed = false
|
|
181
|
+
let away = false
|
|
182
|
+
let landing = false
|
|
183
|
+
let disposed = false
|
|
184
|
+
let view = IDLE
|
|
185
|
+
|
|
186
|
+
const focused = () => activeElement(document) === input
|
|
187
|
+
|
|
188
|
+
function range(): PinRange {
|
|
189
|
+
const start = Math.min(input.selectionStart ?? text.length, text.length)
|
|
190
|
+
return { start, end: Math.max(start, Math.min(input.selectionEnd ?? start, text.length)) }
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function publish() {
|
|
194
|
+
const next = !focused()
|
|
195
|
+
? IDLE
|
|
196
|
+
: { focused: true, ...(composing && live ? { start: live.start, end: live.end } : range()) }
|
|
197
|
+
if (next.focused === view.focused && next.start === view.start && next.end === view.end) return
|
|
198
|
+
view = next
|
|
199
|
+
options.select(next)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function place(start: number, end: number, backward = false) {
|
|
203
|
+
if (!focused()) return
|
|
204
|
+
if (input.selectionStart === start && input.selectionEnd === end) return
|
|
205
|
+
input.setSelectionRange(start, end, backward ? 'backward' : 'forward')
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function settle() {
|
|
209
|
+
const { start, end } = range()
|
|
210
|
+
if (start !== end) return
|
|
211
|
+
const [from, to] = pinSelectionAt(start, text.length, options.length())
|
|
212
|
+
place(from, to)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function emit(value: string) {
|
|
216
|
+
if (value === text) return
|
|
217
|
+
text = value
|
|
218
|
+
options.change(value)
|
|
219
|
+
queueMicrotask(sync)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function sync() {
|
|
223
|
+
if (disposed) return
|
|
224
|
+
const next = shown()
|
|
225
|
+
if (next !== text) {
|
|
226
|
+
text = next
|
|
227
|
+
composing = false
|
|
228
|
+
base = anchor = live = null
|
|
229
|
+
} else if (composing || input.value === text) return
|
|
230
|
+
if (input.value !== text) {
|
|
231
|
+
const caret = Math.min(input.selectionStart ?? text.length, text.length)
|
|
232
|
+
input.value = text
|
|
233
|
+
const [start, end] = pinSelectionAt(caret, text.length, options.length())
|
|
234
|
+
place(start, end)
|
|
235
|
+
}
|
|
236
|
+
publish()
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function derive(type: string): PinEdit {
|
|
240
|
+
const length = options.length()
|
|
241
|
+
const numeric = options.numeric()
|
|
242
|
+
if (type.startsWith('history')) {
|
|
243
|
+
const value = cleanPinText(input.value, numeric).slice(0, length)
|
|
244
|
+
const caret = Math.min(input.selectionStart ?? value.length, value.length)
|
|
245
|
+
const [start, end] = pinSelectionAt(caret, value.length, length)
|
|
246
|
+
return { value, start, end }
|
|
247
|
+
}
|
|
248
|
+
return editPinValue(base ?? text, input.value, anchor, length, numeric, type)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function preview(type: string) {
|
|
252
|
+
composing = true
|
|
253
|
+
base ??= text
|
|
254
|
+
live = derive(type)
|
|
255
|
+
emit(live.value)
|
|
256
|
+
publish()
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function commit(type: string) {
|
|
260
|
+
const edit = derive(type)
|
|
261
|
+
composing = false
|
|
262
|
+
base = anchor = live = null
|
|
263
|
+
if (input.value !== edit.value) input.value = edit.value
|
|
264
|
+
emit(edit.value)
|
|
265
|
+
place(edit.start, edit.end, type.includes('Backward'))
|
|
266
|
+
publish()
|
|
267
|
+
}
|
|
123
268
|
|
|
124
|
-
|
|
125
|
-
|
|
269
|
+
function onBeforeInput(event: InputEvent) {
|
|
270
|
+
landing = false
|
|
271
|
+
if (composing || event.isComposing) return
|
|
272
|
+
if (
|
|
273
|
+
event.inputType === 'insertText' &&
|
|
274
|
+
event.data &&
|
|
275
|
+
!cleanPinText(event.data, options.numeric())
|
|
276
|
+
) {
|
|
277
|
+
event.preventDefault()
|
|
278
|
+
return
|
|
279
|
+
}
|
|
280
|
+
base = input.value
|
|
281
|
+
anchor = range()
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function onInput(event: Event) {
|
|
285
|
+
const { inputType = '', isComposing } = event as InputEvent
|
|
286
|
+
if (composing || isComposing) preview(inputType)
|
|
287
|
+
else if (base !== null || input.value !== text) commit(inputType)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function onCompositionStart() {
|
|
291
|
+
landing = false
|
|
292
|
+
composing = true
|
|
293
|
+
base = input.value
|
|
294
|
+
anchor = range()
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function onCompositionEnd() {
|
|
298
|
+
if (composing) commit('insertCompositionText')
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function land() {
|
|
302
|
+
const size = text.length
|
|
303
|
+
if (size && size >= options.length()) place(0, size)
|
|
304
|
+
else place(size, size)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function onSelection() {
|
|
308
|
+
if (composing) return
|
|
309
|
+
if (focused()) {
|
|
310
|
+
if (landing) land()
|
|
311
|
+
else settle()
|
|
312
|
+
}
|
|
313
|
+
publish()
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function onFocus() {
|
|
317
|
+
landing = !pressed && !away && !composing
|
|
318
|
+
if (landing) land()
|
|
319
|
+
away = false
|
|
320
|
+
publish()
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function onBlur() {
|
|
324
|
+
away = !document.hasFocus()
|
|
325
|
+
pressed = false
|
|
326
|
+
landing = false
|
|
327
|
+
if (composing) commit('insertCompositionText')
|
|
328
|
+
publish()
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function onKeydown(event: KeyboardEvent) {
|
|
332
|
+
landing = false
|
|
333
|
+
if (composing || event.isComposing || event.keyCode === 229) return
|
|
334
|
+
if (event.metaKey || event.ctrlKey || event.altKey) return
|
|
335
|
+
const { key } = event
|
|
336
|
+
const forward = options.rtl() ? 'ArrowLeft' : 'ArrowRight'
|
|
337
|
+
const back = options.rtl() ? 'ArrowRight' : 'ArrowLeft'
|
|
338
|
+
const target =
|
|
339
|
+
key === forward
|
|
340
|
+
? 'next'
|
|
341
|
+
: key === back
|
|
342
|
+
? 'previous'
|
|
343
|
+
: key === 'Home'
|
|
344
|
+
? 0
|
|
345
|
+
: key === 'End'
|
|
346
|
+
? options.length()
|
|
347
|
+
: null
|
|
348
|
+
if (target === null) return
|
|
349
|
+
event.preventDefault()
|
|
350
|
+
const next = movePinSelection(
|
|
351
|
+
range(),
|
|
352
|
+
input.selectionDirection === 'backward',
|
|
353
|
+
text.length,
|
|
354
|
+
options.length(),
|
|
355
|
+
target,
|
|
356
|
+
event.shiftKey,
|
|
357
|
+
)
|
|
358
|
+
place(next.start, next.end, next.backward)
|
|
359
|
+
publish()
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function pick(event: MouseEvent) {
|
|
363
|
+
if (composing) return
|
|
364
|
+
if (event.detail > 1 && text) place(0, text.length)
|
|
365
|
+
else {
|
|
366
|
+
const index = Math.min(pinCellAt(options.cells(), event.clientX), text.length)
|
|
367
|
+
const [start, end] = pinSelectionAt(index, text.length, options.length())
|
|
368
|
+
place(start, end)
|
|
369
|
+
}
|
|
370
|
+
publish()
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function onPointerDown(event: PointerEvent) {
|
|
374
|
+
landing = false
|
|
375
|
+
pointer = event.pointerType
|
|
376
|
+
pressed = pointer !== 'mouse'
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function onMouseDown(event: MouseEvent) {
|
|
380
|
+
if (event.button !== 0 || pointer !== 'mouse' || input.disabled) return
|
|
381
|
+
event.preventDefault()
|
|
382
|
+
pressed = true
|
|
383
|
+
input.focus()
|
|
384
|
+
pressed = false
|
|
385
|
+
pick(event)
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function onClick(event: MouseEvent) {
|
|
389
|
+
const touched = pressed
|
|
390
|
+
pressed = false
|
|
391
|
+
if (touched && pointer !== 'mouse') pick(event)
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const listeners: Array<[EventTarget, string, (event: never) => void]> = [
|
|
395
|
+
[input, 'beforeinput', onBeforeInput],
|
|
396
|
+
[input, 'input', onInput],
|
|
397
|
+
[input, 'compositionstart', onCompositionStart],
|
|
398
|
+
[input, 'compositionend', onCompositionEnd],
|
|
399
|
+
[input, 'select', onSelection],
|
|
400
|
+
[input, 'focus', onFocus],
|
|
401
|
+
[input, 'blur', onBlur],
|
|
402
|
+
[input, 'keydown', onKeydown],
|
|
403
|
+
[input, 'pointerdown', onPointerDown],
|
|
404
|
+
[input, 'mousedown', onMouseDown],
|
|
405
|
+
[input, 'click', onClick],
|
|
406
|
+
[document, 'selectionchange', onSelection],
|
|
407
|
+
]
|
|
408
|
+
for (const [target, type, listener] of listeners)
|
|
409
|
+
target.addEventListener(type, listener as EventListener)
|
|
410
|
+
|
|
411
|
+
if (input.value !== text) input.value = text
|
|
412
|
+
if (focused()) settle()
|
|
413
|
+
publish()
|
|
414
|
+
|
|
415
|
+
return {
|
|
416
|
+
sync,
|
|
417
|
+
dispose() {
|
|
418
|
+
disposed = true
|
|
419
|
+
for (const [target, type, listener] of listeners)
|
|
420
|
+
target.removeEventListener(type, listener as EventListener)
|
|
421
|
+
},
|
|
422
|
+
}
|
|
126
423
|
}
|
|
@@ -132,7 +132,9 @@
|
|
|
132
132
|
@media (hover: hover) {
|
|
133
133
|
&:hover:not(:disabled):not([data-disabled]):not(
|
|
134
134
|
:has(:is(input, textarea, [data-hn-segment]):focus-visible)
|
|
135
|
-
):not([data-invalid])
|
|
135
|
+
):not([data-invalid]),
|
|
136
|
+
:where([data-hn-field-group]:hover:not(:has(input:focus-visible)))
|
|
137
|
+
&:not([data-disabled]):not([data-invalid]) {
|
|
136
138
|
background-color: color-mix(
|
|
137
139
|
in oklab,
|
|
138
140
|
var(--hn-field-bg, var(--hn-surface)) calc(100% - var(--hn-state-hover-opacity) * 100%),
|
|
@@ -142,7 +144,9 @@
|
|
|
142
144
|
|
|
143
145
|
&[data-invalid]:hover:not(:disabled):not([data-disabled]):not(
|
|
144
146
|
:has(:is(input, textarea, [data-hn-segment]):focus-visible)
|
|
145
|
-
)
|
|
147
|
+
),
|
|
148
|
+
:where([data-hn-field-group]:hover:not(:has(input:focus-visible)))
|
|
149
|
+
&[data-invalid]:not([data-disabled]) {
|
|
146
150
|
background-color: color-mix(
|
|
147
151
|
in oklab,
|
|
148
152
|
color-mix(
|
|
@@ -9,14 +9,18 @@
|
|
|
9
9
|
min-width: 0;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
.hn-line-clamp {
|
|
13
|
+
display: flow-root;
|
|
14
|
+
}
|
|
15
|
+
|
|
12
16
|
.hn-line-clamp:not([data-expanded]),
|
|
13
17
|
.hn-line-clamp[data-animating],
|
|
14
18
|
.hn-line-clamp[data-measuring] {
|
|
15
19
|
overflow: hidden;
|
|
16
20
|
}
|
|
17
21
|
|
|
18
|
-
.hn-line-clamp:not([data-expanded]):not([data-animating]),
|
|
19
|
-
.hn-line-clamp[data-measuring] {
|
|
22
|
+
.hn-line-clamp:not([data-expanded]):not([data-animating]):not([data-measuring='full']),
|
|
23
|
+
.hn-line-clamp[data-measuring='clamped'] {
|
|
20
24
|
display: -webkit-box;
|
|
21
25
|
-webkit-box-orient: vertical;
|
|
22
26
|
-webkit-line-clamp: var(--hn-line-clamp, 3);
|
|
@@ -510,8 +510,14 @@
|
|
|
510
510
|
}
|
|
511
511
|
}
|
|
512
512
|
|
|
513
|
+
@layer components {
|
|
514
|
+
.hn-anim-collapse {
|
|
515
|
+
display: flow-root;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
513
519
|
@utility hn-anim-collapse {
|
|
514
|
-
overflow:
|
|
520
|
+
overflow: clip;
|
|
515
521
|
&[data-state='open'] {
|
|
516
522
|
animation: hn-collapse-in var(--hn-duration-base) var(--hn-ease-move);
|
|
517
523
|
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
.hn-pin-native {
|
|
2
|
+
position: absolute;
|
|
3
|
+
inset: 0;
|
|
4
|
+
width: 100%;
|
|
5
|
+
height: 100%;
|
|
6
|
+
margin: 0;
|
|
7
|
+
padding: 0;
|
|
8
|
+
border: 0;
|
|
9
|
+
border-radius: 0;
|
|
10
|
+
outline: 0;
|
|
11
|
+
background: transparent;
|
|
12
|
+
box-shadow: none;
|
|
13
|
+
color: transparent;
|
|
14
|
+
caret-color: transparent;
|
|
15
|
+
cursor: text;
|
|
16
|
+
font-family: ui-monospace, monospace;
|
|
17
|
+
font-size: var(--hn-pin-size, 2.5rem);
|
|
18
|
+
line-height: 1;
|
|
19
|
+
letter-spacing: -0.5em;
|
|
20
|
+
appearance: none;
|
|
21
|
+
forced-color-adjust: none;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@supports (-webkit-touch-callout: none) {
|
|
25
|
+
.hn-pin-native {
|
|
26
|
+
left: -1px;
|
|
27
|
+
right: 1px;
|
|
28
|
+
font-weight: 100;
|
|
29
|
+
font-stretch: ultra-condensed;
|
|
30
|
+
font-optical-sizing: none;
|
|
31
|
+
letter-spacing: -0.6em;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.hn-pin-native:disabled {
|
|
36
|
+
cursor: not-allowed;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.hn-pin-native::selection {
|
|
40
|
+
background: transparent;
|
|
41
|
+
color: transparent;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.hn-pin-native::-ms-reveal {
|
|
45
|
+
display: none;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.hn-pin-native::-ms-clear {
|
|
49
|
+
display: none;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.hn-pin-native::-webkit-credentials-auto-fill-button,
|
|
53
|
+
.hn-pin-native::-webkit-contacts-auto-fill-button,
|
|
54
|
+
.hn-pin-native::-webkit-strong-password-auto-fill-button,
|
|
55
|
+
.hn-pin-native::-webkit-caps-lock-indicator {
|
|
56
|
+
display: none !important;
|
|
57
|
+
visibility: hidden;
|
|
58
|
+
pointer-events: none;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.hn-pin-native:-webkit-autofill {
|
|
62
|
+
opacity: 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.hn-pin-native:autofill {
|
|
66
|
+
opacity: 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
[data-hn-pin-cell]::before {
|
|
70
|
+
content: '\200b';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.hn-pin-caret {
|
|
74
|
+
width: 1px;
|
|
75
|
+
height: 1.25em;
|
|
76
|
+
background-color: var(--hn-fg-default);
|
|
77
|
+
animation: hn-pin-caret 1.2s step-end infinite;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@keyframes hn-pin-caret {
|
|
81
|
+
50% {
|
|
82
|
+
opacity: 0;
|
|
83
|
+
}
|
|
84
|
+
}
|