@hina-ui/vue 1.1.0 → 1.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 (122) hide show
  1. package/dist/index.js +485 -262
  2. package/dist/index.js.map +1 -1
  3. package/dist/types/components/accordion/AccordionContent.vue.d.ts.map +1 -1
  4. package/dist/types/components/avatar/AvatarGroup.vue.d.ts.map +1 -1
  5. package/dist/types/components/avatar/utils/children.d.ts +3 -0
  6. package/dist/types/components/avatar/utils/children.d.ts.map +1 -0
  7. package/dist/types/components/checkbox-group/CheckboxGroup.vue.d.ts +32 -29
  8. package/dist/types/components/checkbox-group/CheckboxGroup.vue.d.ts.map +1 -1
  9. package/dist/types/components/collapsible/CollapsibleContent.vue.d.ts.map +1 -1
  10. package/dist/types/components/combobox/Combobox.vue.d.ts +38 -38
  11. package/dist/types/components/combobox/Combobox.vue.d.ts.map +1 -1
  12. package/dist/types/components/combobox/ComboboxList.vue.d.ts +22 -15
  13. package/dist/types/components/combobox/ComboboxList.vue.d.ts.map +1 -1
  14. package/dist/types/components/dialog/Dialog.vue.d.ts +22 -16
  15. package/dist/types/components/dialog/Dialog.vue.d.ts.map +1 -1
  16. package/dist/types/components/dialog/dialog.variants.d.ts +32 -2
  17. package/dist/types/components/dialog/dialog.variants.d.ts.map +1 -1
  18. package/dist/types/components/image/ImageGroup.vue.d.ts.map +1 -1
  19. package/dist/types/components/image/composables/useImageGroupState.d.ts +7 -0
  20. package/dist/types/components/image/composables/useImageGroupState.d.ts.map +1 -0
  21. package/dist/types/components/input/Input.vue.d.ts +3 -3
  22. package/dist/types/components/input/Input.vue.d.ts.map +1 -1
  23. package/dist/types/components/input/InputBase.vue.d.ts +3 -3
  24. package/dist/types/components/input/InputBase.vue.d.ts.map +1 -1
  25. package/dist/types/components/input/input.variants.d.ts +52 -6
  26. package/dist/types/components/input/input.variants.d.ts.map +1 -1
  27. package/dist/types/components/input-group/InputGroup.vue.d.ts +3 -3
  28. package/dist/types/components/input-group/InputGroup.vue.d.ts.map +1 -1
  29. package/dist/types/components/input-group/context.d.ts +2 -1
  30. package/dist/types/components/input-group/context.d.ts.map +1 -1
  31. package/dist/types/components/input-group/input-group.variants.d.ts +13 -1
  32. package/dist/types/components/input-group/input-group.variants.d.ts.map +1 -1
  33. package/dist/types/components/listbox/Listbox.vue.d.ts +36 -29
  34. package/dist/types/components/listbox/Listbox.vue.d.ts.map +1 -1
  35. package/dist/types/components/listbox/ListboxOptionContent.vue.d.ts +31 -0
  36. package/dist/types/components/listbox/ListboxOptionContent.vue.d.ts.map +1 -0
  37. package/dist/types/components/listbox/listbox.variants.d.ts +19 -1
  38. package/dist/types/components/listbox/listbox.variants.d.ts.map +1 -1
  39. package/dist/types/components/multi-combobox/MultiCombobox.vue.d.ts +41 -43
  40. package/dist/types/components/multi-combobox/MultiCombobox.vue.d.ts.map +1 -1
  41. package/dist/types/components/multi-combobox/composables/useMultiCombobox.d.ts +27 -0
  42. package/dist/types/components/multi-combobox/composables/useMultiCombobox.d.ts.map +1 -0
  43. package/dist/types/components/multi-select/MultiSelect.vue.d.ts +39 -41
  44. package/dist/types/components/multi-select/MultiSelect.vue.d.ts.map +1 -1
  45. package/dist/types/components/number-input/NumberInput.vue.d.ts +3 -3
  46. package/dist/types/components/number-input/NumberInput.vue.d.ts.map +1 -1
  47. package/dist/types/components/number-input/number-input.variants.d.ts +10 -1
  48. package/dist/types/components/number-input/number-input.variants.d.ts.map +1 -1
  49. package/dist/types/components/password-input/PasswordInput.vue.d.ts +3 -3
  50. package/dist/types/components/password-input/PasswordInput.vue.d.ts.map +1 -1
  51. package/dist/types/components/radio-group/RadioGroup.vue.d.ts +32 -29
  52. package/dist/types/components/radio-group/RadioGroup.vue.d.ts.map +1 -1
  53. package/dist/types/components/scroll-area/scroll-restore.d.ts +0 -1
  54. package/dist/types/components/scroll-area/scroll-restore.d.ts.map +1 -1
  55. package/dist/types/components/search-input/SearchInput.vue.d.ts +3 -3
  56. package/dist/types/components/search-input/SearchInput.vue.d.ts.map +1 -1
  57. package/dist/types/components/segmented-control/SegmentedControl.vue.d.ts +30 -30
  58. package/dist/types/components/segmented-control/SegmentedControl.vue.d.ts.map +1 -1
  59. package/dist/types/components/select/Select.vue.d.ts +41 -37
  60. package/dist/types/components/select/Select.vue.d.ts.map +1 -1
  61. package/dist/types/components/select/SelectList.vue.d.ts +22 -15
  62. package/dist/types/components/select/SelectList.vue.d.ts.map +1 -1
  63. package/dist/types/components/select/composables/useClearTransition.d.ts +10 -0
  64. package/dist/types/components/select/composables/useClearTransition.d.ts.map +1 -0
  65. package/dist/types/components/select/select.variants.d.ts +16 -0
  66. package/dist/types/components/select/select.variants.d.ts.map +1 -1
  67. package/dist/types/components/select/types.d.ts +7 -7
  68. package/dist/types/components/select/types.d.ts.map +1 -1
  69. package/dist/types/components/textarea/Textarea.vue.d.ts.map +1 -1
  70. package/dist/types/components/textarea/composables/useTextareaSizing.d.ts +27 -0
  71. package/dist/types/components/textarea/composables/useTextareaSizing.d.ts.map +1 -0
  72. package/dist/types/components/textarea/textarea.variants.d.ts +9 -6
  73. package/dist/types/components/textarea/textarea.variants.d.ts.map +1 -1
  74. package/dist/types/lib/caret.d.ts +0 -4
  75. package/dist/types/lib/caret.d.ts.map +1 -1
  76. package/dist/types/lib/collapse.d.ts +2 -0
  77. package/dist/types/lib/collapse.d.ts.map +1 -1
  78. package/dist/types/lib/field-focus.d.ts +0 -4
  79. package/dist/types/lib/field-focus.d.ts.map +1 -1
  80. package/package.json +1 -1
  81. package/src/components/accordion/AccordionContent.vue +2 -11
  82. package/src/components/avatar/AvatarGroup.vue +3 -8
  83. package/src/components/avatar/utils/children.ts +7 -0
  84. package/src/components/checkbox-group/CheckboxGroup.vue +3 -3
  85. package/src/components/collapsible/CollapsibleContent.vue +2 -11
  86. package/src/components/combobox/Combobox.vue +3 -3
  87. package/src/components/combobox/ComboboxList.vue +3 -3
  88. package/src/components/dialog/Dialog.vue +70 -39
  89. package/src/components/dialog/dialog.variants.ts +9 -3
  90. package/src/components/image/ImageGroup.vue +2 -30
  91. package/src/components/image/composables/useImageGroupState.ts +35 -0
  92. package/src/components/input/Input.vue +3 -3
  93. package/src/components/input/InputBase.vue +5 -5
  94. package/src/components/input/input.variants.ts +13 -3
  95. package/src/components/input-group/InputGroup.vue +11 -4
  96. package/src/components/input-group/context.ts +2 -1
  97. package/src/components/input-group/input-group.variants.ts +8 -1
  98. package/src/components/listbox/Listbox.vue +26 -29
  99. package/src/components/listbox/ListboxOptionContent.vue +28 -0
  100. package/src/components/listbox/listbox.variants.ts +6 -1
  101. package/src/components/multi-combobox/MultiCombobox.vue +14 -52
  102. package/src/components/multi-combobox/composables/useMultiCombobox.ts +63 -0
  103. package/src/components/multi-select/MultiSelect.vue +3 -4
  104. package/src/components/number-input/NumberInput.vue +8 -7
  105. package/src/components/number-input/number-input.variants.ts +5 -1
  106. package/src/components/password-input/PasswordInput.vue +3 -3
  107. package/src/components/radio-group/RadioGroup.vue +3 -3
  108. package/src/components/scroll-area/scroll-restore.ts +0 -5
  109. package/src/components/search-input/SearchInput.vue +3 -3
  110. package/src/components/segmented-control/SegmentedControl.vue +3 -3
  111. package/src/components/select/Select.vue +68 -25
  112. package/src/components/select/SelectList.vue +3 -3
  113. package/src/components/select/composables/useClearTransition.ts +25 -0
  114. package/src/components/select/select.variants.ts +24 -0
  115. package/src/components/select/types.ts +10 -8
  116. package/src/components/textarea/Textarea.vue +2 -41
  117. package/src/components/textarea/composables/useTextareaSizing.ts +54 -0
  118. package/src/components/textarea/textarea.variants.ts +5 -4
  119. package/src/lib/caret.ts +0 -4
  120. package/src/lib/collapse.ts +21 -1
  121. package/src/lib/field-focus.ts +0 -4
  122. package/src/styles/interaction.css +4 -2
@@ -1,8 +1,7 @@
1
1
  <script setup lang="ts">
2
- import { computed, nextTick, onMounted, shallowRef, watch } from 'vue'
3
2
  import { cn } from '../../lib/cn'
4
3
  import { useFieldControl } from '../form-field/context'
5
- import { caretTop } from '../../lib/caret'
4
+ import { useTextareaSizing } from './composables/useTextareaSizing'
6
5
  import ScrollArea from '../scroll-area/ScrollArea.vue'
7
6
  import { textarea, textareaField, type TextareaVariants } from './textarea.variants'
8
7
 
@@ -34,45 +33,7 @@
34
33
 
35
34
  const model = defineModel<string>()
36
35
 
37
- const el = shallowRef<HTMLTextAreaElement | null>(null)
38
- const area = shallowRef<InstanceType<typeof ScrollArea>>()
39
-
40
- const bounds = computed(() => {
41
- const options = props.autosize === true ? {} : props.autosize || null
42
- return { min: options?.minRows ?? props.rows, max: options?.maxRows }
43
- })
44
-
45
- function fit() {
46
- const node = el.value
47
- if (!node) return
48
- node.style.height = 'auto'
49
- node.style.height = `${node.scrollHeight}px`
50
- if (document.activeElement === node) reveal(node)
51
- }
52
-
53
- function reveal(node: HTMLTextAreaElement) {
54
- const scroller =
55
- area.value?.viewport ?? node.closest<HTMLElement>('[data-overlayscrollbars-initialize]')
56
- if (!scroller) return
57
- const box = scroller.getBoundingClientRect()
58
- const top = node.getBoundingClientRect().top + caretTop(node)
59
- const bottom = top + parseFloat(getComputedStyle(node).lineHeight)
60
- if (bottom > box.bottom) scroller.scrollTop += bottom - box.bottom
61
- else if (top < box.top) scroller.scrollTop -= box.top - top
62
- }
63
-
64
- function focus(event: MouseEvent) {
65
- const node = el.value
66
- if (!node || node.disabled || event.target === node) return
67
- node.focus()
68
- node.setSelectionRange(node.value.length, node.value.length)
69
- }
70
-
71
- onMounted(fit)
72
- watch(
73
- () => [model.value, props.size, bounds.value.min],
74
- () => void nextTick(fit),
75
- )
36
+ const { el, area, bounds, fit, focus } = useTextareaSizing(props, model)
76
37
  </script>
77
38
 
78
39
  <template>
@@ -0,0 +1,54 @@
1
+ import { computed, nextTick, onMounted, shallowRef, watch, type Ref } from 'vue'
2
+ import { caretTop } from '../../../lib/caret'
3
+ import type { TextareaVariants } from '../textarea.variants'
4
+
5
+ interface TextareaSizingProps {
6
+ autosize?: boolean | { minRows?: number; maxRows?: number }
7
+ rows: number
8
+ size?: TextareaVariants['size']
9
+ variant?: TextareaVariants['variant']
10
+ }
11
+
12
+ export function useTextareaSizing(props: TextareaSizingProps, model: Ref<string | undefined>) {
13
+ const el = shallowRef<HTMLTextAreaElement | null>(null)
14
+ const area = shallowRef<{ viewport?: HTMLElement }>()
15
+
16
+ const bounds = computed(() => {
17
+ const options = props.autosize === true ? {} : props.autosize || null
18
+ return { min: options?.minRows ?? props.rows, max: options?.maxRows }
19
+ })
20
+
21
+ function fit() {
22
+ const node = el.value
23
+ if (!node) return
24
+ node.style.height = 'auto'
25
+ node.style.height = `${node.scrollHeight}px`
26
+ if (document.activeElement === node) reveal(node)
27
+ }
28
+
29
+ function reveal(node: HTMLTextAreaElement) {
30
+ const scroller =
31
+ area.value?.viewport ?? node.closest<HTMLElement>('[data-overlayscrollbars-initialize]')
32
+ if (!scroller) return
33
+ const box = scroller.getBoundingClientRect()
34
+ const top = node.getBoundingClientRect().top + caretTop(node)
35
+ const bottom = top + parseFloat(getComputedStyle(node).lineHeight)
36
+ if (bottom > box.bottom) scroller.scrollTop += bottom - box.bottom
37
+ else if (top < box.top) scroller.scrollTop -= box.top - top
38
+ }
39
+
40
+ function focus(event: MouseEvent) {
41
+ const node = el.value
42
+ if (!node || node.disabled || event.target === node) return
43
+ node.focus()
44
+ node.setSelectionRange(node.value.length, node.value.length)
45
+ }
46
+
47
+ onMounted(fit)
48
+ watch(
49
+ () => [model.value, props.size, props.variant, bounds.value.min],
50
+ () => void nextTick(fit),
51
+ )
52
+
53
+ return { el, area, bounds, fit, focus }
54
+ }
@@ -2,14 +2,15 @@ import { tv, type VariantProps } from '../../lib/tv'
2
2
 
3
3
  export const textarea = tv({
4
4
  base: [
5
- 'hn-field text-fg flex w-full min-w-0 cursor-text flex-col overflow-hidden rounded-md border font-medium',
5
+ 'text-fg flex w-full min-w-0 cursor-text flex-col overflow-hidden rounded-md border font-medium',
6
6
  'data-disabled:cursor-not-allowed data-disabled:opacity-50',
7
7
  ],
8
8
  variants: {
9
9
  variant: {
10
+ bare: 'rounded-none border-0 bg-transparent shadow-none outline-none [--hn-textarea-border-w:0px]',
10
11
  primary:
11
- 'border-line [--hn-field-bg:var(--hn-surface)] [--hn-field-shadow:var(--hn-shadow-sm)]',
12
- secondary: 'border-transparent [--hn-field-bg:var(--hn-bg-inset)]',
12
+ 'hn-field border-line [--hn-field-bg:var(--hn-surface)] [--hn-field-shadow:var(--hn-shadow-sm)]',
13
+ secondary: 'hn-field border-transparent [--hn-field-bg:var(--hn-bg-inset)]',
13
14
  },
14
15
  size: {
15
16
  sm: 'text-sm [--hn-textarea-h:var(--hn-control-h-sm)] [--hn-textarea-px:var(--hn-control-px-sm)]',
@@ -39,7 +40,7 @@ export const textarea = tv({
39
40
  export const textareaField = tv({
40
41
  base: [
41
42
  'block w-full resize-none overflow-hidden bg-transparent text-inherit outline-none [font:inherit]',
42
- 'px-[var(--hn-textarea-px)] py-[calc((var(--hn-textarea-h)_-_1lh)/2_-_1px)]',
43
+ 'px-[var(--hn-textarea-px)] py-[calc((var(--hn-textarea-h)_-_1lh)/2_-_var(--hn-textarea-border-w,1px))]',
43
44
  'placeholder:text-faint placeholder:font-normal disabled:cursor-not-allowed',
44
45
  ],
45
46
  })
package/src/lib/caret.ts CHANGED
@@ -22,10 +22,6 @@ const MIRRORED = [
22
22
  'border-left-width',
23
23
  ]
24
24
 
25
- /**
26
- * 量出 textarea 光标所在行的顶边(相对 textarea 自身的边框盒)。
27
- * 原生没有光标几何 API,只能用同排版的镜像块把光标前的文字排一遍。
28
- */
29
25
  export function caretTop(node: HTMLTextAreaElement, at = node.selectionEnd): number {
30
26
  const style = getComputedStyle(node)
31
27
  const mirror = node.ownerDocument.createElement('div')
@@ -1,4 +1,10 @@
1
- import { getCurrentInstance, onBeforeUpdate, onMounted } from 'vue'
1
+ import {
2
+ getCurrentInstance,
3
+ onBeforeUpdate,
4
+ onMounted,
5
+ shallowRef,
6
+ type ComponentPublicInstance,
7
+ } from 'vue'
2
8
 
3
9
  export type CollapseAxis = 'y' | 'x'
4
10
 
@@ -43,3 +49,17 @@ export function useCollapseHooks(axis: CollapseAxis = 'y') {
43
49
 
44
50
  return collapseHooks(axis, () => parent)
45
51
  }
52
+
53
+ export function useCollapseGap() {
54
+ const content = shallowRef<ComponentPublicInstance | null>(null)
55
+
56
+ function measure() {
57
+ const el = content.value?.$el as Element | undefined
58
+ if (el instanceof Element) writeCollapseGap(el, el.parentElement)
59
+ }
60
+
61
+ onMounted(measure)
62
+ onBeforeUpdate(measure)
63
+
64
+ return content
65
+ }
@@ -8,10 +8,6 @@ function controlOf(node: Element) {
8
8
  : null
9
9
  }
10
10
 
11
- /**
12
- * 点击输入面里非交互的部分(附属格、前后缀、空白)时把焦点交给最近的输入区,返回被聚焦的输入区;点击落在交互元素上或者没有可聚焦的输入区时返回 null。
13
- * 点击所在的段自己含输入区时只认它、不动光标(段内的处理器已经定过位);否则先向后找,再向前找;落在点击位置之后的输入区把光标放到开头,之前的放到末尾。
14
- */
15
11
  export function focusFieldFrom(root: HTMLElement, target: HTMLElement) {
16
12
  if (target.closest(INTERACTIVE)) return null
17
13
  const segments = Array.from(root.children)
@@ -155,7 +155,7 @@
155
155
  }
156
156
 
157
157
  &:focus-visible,
158
- &:has(:is(input, textarea, [data-hn-segment]):focus-visible) {
158
+ &:has(:is(input, textarea, [data-hn-segment], [data-hn-select-trigger]):focus-visible) {
159
159
  --hn-field-ring-w: 2px;
160
160
  }
161
161
 
@@ -170,7 +170,9 @@
170
170
  }
171
171
 
172
172
  &[data-invalid]:focus-visible,
173
- &[data-invalid]:has(:is(input, textarea, [data-hn-segment]):focus-visible) {
173
+ &[data-invalid]:has(
174
+ :is(input, textarea, [data-hn-segment], [data-hn-select-trigger]):focus-visible
175
+ ) {
174
176
  --hn-field-ring-w: 1px;
175
177
  }
176
178
  }