@hina-ui/vue 1.7.7 → 1.7.8

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 (104) hide show
  1. package/dist/index.js +1318 -554
  2. package/dist/index.js.map +1 -1
  3. package/dist/shared/behavior/anchor-follow.ts +20 -11
  4. package/dist/shared/lib/command-palette/match.ts +9 -0
  5. package/dist/shared/lib/line-clamp.ts +76 -0
  6. package/dist/shared/lib/scroll-restore.ts +145 -0
  7. package/dist/shared/locale/en-US.ts +4 -0
  8. package/dist/shared/locale/index.ts +2 -0
  9. package/dist/shared/locale/ja-JP.ts +229 -0
  10. package/dist/shared/locale/types.ts +4 -0
  11. package/dist/shared/locale/zh-CN.ts +4 -0
  12. package/dist/shared/styles/density.css +4 -0
  13. package/dist/shared/styles/line-clamp.css +98 -0
  14. package/dist/shared/styles/tokens.css +1 -0
  15. package/dist/shared/styles/typography.css +18 -0
  16. package/dist/shared/variants/button.ts +2 -0
  17. package/dist/shared/variants/command-palette.ts +4 -0
  18. package/dist/shared/variants/line-clamp.ts +13 -0
  19. package/dist/shared/variants/tag.ts +1 -0
  20. package/dist/types/shared/src/behavior/anchor-follow.d.ts.map +1 -1
  21. package/dist/types/shared/src/lib/command-palette/match.d.ts +5 -0
  22. package/dist/types/shared/src/lib/command-palette/match.d.ts.map +1 -1
  23. package/dist/types/shared/src/lib/line-clamp.d.ts +6 -0
  24. package/dist/types/shared/src/lib/line-clamp.d.ts.map +1 -0
  25. package/dist/types/shared/src/lib/scroll-restore.d.ts +14 -0
  26. package/dist/types/shared/src/lib/scroll-restore.d.ts.map +1 -1
  27. package/dist/types/shared/src/locale/en-US.d.ts.map +1 -1
  28. package/dist/types/shared/src/locale/index.d.ts +1 -0
  29. package/dist/types/shared/src/locale/index.d.ts.map +1 -1
  30. package/dist/types/shared/src/locale/ja-JP.d.ts +3 -0
  31. package/dist/types/shared/src/locale/ja-JP.d.ts.map +1 -0
  32. package/dist/types/shared/src/locale/types.d.ts +4 -0
  33. package/dist/types/shared/src/locale/types.d.ts.map +1 -1
  34. package/dist/types/shared/src/locale/zh-CN.d.ts.map +1 -1
  35. package/dist/types/shared/src/variants/button.d.ts +6 -0
  36. package/dist/types/shared/src/variants/button.d.ts.map +1 -1
  37. package/dist/types/shared/src/variants/command-palette.d.ts +1 -0
  38. package/dist/types/shared/src/variants/command-palette.d.ts.map +1 -1
  39. package/dist/types/shared/src/variants/line-clamp.d.ts +4 -0
  40. package/dist/types/shared/src/variants/line-clamp.d.ts.map +1 -0
  41. package/dist/types/shared/src/variants/tag.d.ts +3 -0
  42. package/dist/types/shared/src/variants/tag.d.ts.map +1 -1
  43. package/dist/types/shared/src/variants/toggle.d.ts +7 -0
  44. package/dist/types/shared/src/variants/toggle.d.ts.map +1 -1
  45. package/dist/types/vue/src/components/app-shell/AppShell.vue.d.ts +2 -2
  46. package/dist/types/vue/src/components/app-shell/AppShell.vue.d.ts.map +1 -1
  47. package/dist/types/vue/src/components/close-button/CloseButton.vue.d.ts +1 -1
  48. package/dist/types/vue/src/components/command-palette/CommandPalette.vue.d.ts +40 -34
  49. package/dist/types/vue/src/components/command-palette/CommandPalette.vue.d.ts.map +1 -1
  50. package/dist/types/vue/src/components/command-palette/CommandPaletteInput.vue.d.ts +8 -0
  51. package/dist/types/vue/src/components/command-palette/CommandPaletteInput.vue.d.ts.map +1 -0
  52. package/dist/types/vue/src/components/command-palette/CommandPaletteItem.vue.d.ts +21 -7
  53. package/dist/types/vue/src/components/command-palette/CommandPaletteItem.vue.d.ts.map +1 -1
  54. package/dist/types/vue/src/components/command-palette/CommandPalettePanel.vue.d.ts +37 -31
  55. package/dist/types/vue/src/components/command-palette/CommandPalettePanel.vue.d.ts.map +1 -1
  56. package/dist/types/vue/src/components/command-palette/composables/useCommandItems.d.ts +6 -6
  57. package/dist/types/vue/src/components/command-palette/composables/useCommandItems.d.ts.map +1 -1
  58. package/dist/types/vue/src/components/command-palette/context.d.ts +11 -0
  59. package/dist/types/vue/src/components/command-palette/context.d.ts.map +1 -0
  60. package/dist/types/vue/src/components/command-palette/types.d.ts +16 -5
  61. package/dist/types/vue/src/components/command-palette/types.d.ts.map +1 -1
  62. package/dist/types/vue/src/components/command-palette/utils/match.d.ts +3 -3
  63. package/dist/types/vue/src/components/command-palette/utils/match.d.ts.map +1 -1
  64. package/dist/types/vue/src/components/copy-button/CopyButton.vue.d.ts +1 -1
  65. package/dist/types/vue/src/components/description-list/DescriptionList.vue.d.ts +1 -0
  66. package/dist/types/vue/src/components/description-list/DescriptionList.vue.d.ts.map +1 -1
  67. package/dist/types/vue/src/components/dialog/Dialog.vue.d.ts +3 -0
  68. package/dist/types/vue/src/components/dialog/Dialog.vue.d.ts.map +1 -1
  69. package/dist/types/vue/src/components/drawer/Drawer.vue.d.ts +3 -0
  70. package/dist/types/vue/src/components/drawer/Drawer.vue.d.ts.map +1 -1
  71. package/dist/types/vue/src/components/form/Form.vue.d.ts +5 -0
  72. package/dist/types/vue/src/components/form/Form.vue.d.ts.map +1 -1
  73. package/dist/types/vue/src/components/form/scope.d.ts +4 -0
  74. package/dist/types/vue/src/components/form/scope.d.ts.map +1 -0
  75. package/dist/types/vue/src/components/line-clamp/LineClamp.vue.d.ts +28 -0
  76. package/dist/types/vue/src/components/line-clamp/LineClamp.vue.d.ts.map +1 -0
  77. package/dist/types/vue/src/components/line-clamp/line-clamp.variants.d.ts +2 -0
  78. package/dist/types/vue/src/components/line-clamp/line-clamp.variants.d.ts.map +1 -0
  79. package/dist/types/vue/src/components/sheet/Sheet.vue.d.ts +3 -0
  80. package/dist/types/vue/src/components/sheet/Sheet.vue.d.ts.map +1 -1
  81. package/dist/types/vue/src/index.d.ts +6 -4
  82. package/dist/types/vue/src/index.d.ts.map +1 -1
  83. package/dist/types/vue/src/locale/index.d.ts +1 -1
  84. package/dist/types/vue/src/locale/index.d.ts.map +1 -1
  85. package/package.json +1 -1
  86. package/src/components/app-shell/AppShell.vue +54 -4
  87. package/src/components/command-palette/CommandPalette.vue +53 -16
  88. package/src/components/command-palette/CommandPaletteInput.vue +32 -0
  89. package/src/components/command-palette/CommandPaletteItem.vue +7 -5
  90. package/src/components/command-palette/CommandPalettePanel.vue +95 -34
  91. package/src/components/command-palette/composables/useCommandItems.ts +16 -14
  92. package/src/components/command-palette/context.ts +28 -0
  93. package/src/components/command-palette/types.ts +21 -5
  94. package/src/components/command-palette/utils/match.ts +6 -3
  95. package/src/components/description-list/DescriptionList.vue +8 -2
  96. package/src/components/dialog/Dialog.vue +13 -8
  97. package/src/components/drawer/Drawer.vue +13 -8
  98. package/src/components/form/Form.vue +14 -1
  99. package/src/components/form/scope.ts +39 -0
  100. package/src/components/line-clamp/LineClamp.vue +105 -0
  101. package/src/components/line-clamp/line-clamp.variants.ts +1 -0
  102. package/src/components/sheet/Sheet.vue +14 -10
  103. package/src/index.ts +15 -2
  104. package/src/locale/index.ts +1 -1
@@ -24,9 +24,16 @@ export interface AnchorFollowResult {
24
24
  viewport: HTMLElement | undefined
25
25
  }
26
26
 
27
+ const FLIGHT_MS = 1500
28
+
27
29
  export function createAnchorFollow() {
28
30
  let lastCurrent: string | undefined
29
31
  let lastEnd: string | undefined
32
+ let flight: { port: HTMLElement; top: number; until: number } | undefined
33
+
34
+ function land(event: Event) {
35
+ if (flight?.port === event.currentTarget) flight = undefined
36
+ }
30
37
 
31
38
  function follow({
32
39
  nav,
@@ -47,13 +54,18 @@ export function createAnchorFollow() {
47
54
  if (!port || port.clientHeight <= 0 || port.scrollHeight <= port.clientHeight) return result
48
55
 
49
56
  const box = port.getBoundingClientRect()
50
- let row: Pick<DOMRect, 'top' | 'bottom' | 'height'> = link.getBoundingClientRect()
51
57
  const scale = port.offsetHeight ? box.height / port.offsetHeight : 1
52
58
  if (scale <= 0) return result
59
+ const now = performance.now()
60
+ if (flight && (flight.port !== port || now > flight.until)) flight = undefined
61
+ const base = flight ? flight.top : port.scrollTop
62
+ const ahead = (base - port.scrollTop) * scale
63
+ const rect = link.getBoundingClientRect()
64
+ let row = { top: rect.top - ahead, bottom: rect.bottom - ahead, height: rect.height }
53
65
  if (endId !== id) {
54
66
  const end = nav.querySelector<HTMLElement>(`a[href="#${CSS.escape(endId)}"]`)
55
67
  if (end?.getClientRects().length) {
56
- const bottom = end.getBoundingClientRect().bottom
68
+ const bottom = end.getBoundingClientRect().bottom - ahead
57
69
  const height = bottom - row.top
58
70
  if (height >= row.height && height <= port.clientHeight * scale)
59
71
  row = { top: row.top, bottom, height }
@@ -79,15 +91,12 @@ export function createAnchorFollow() {
79
91
  row.height > bottom - top || row.top < top
80
92
  ? (row.top - top) / scale - before
81
93
  : (row.bottom - bottom) / scale + after
82
- const next = Math.max(
83
- 0,
84
- Math.min(port.scrollHeight - port.clientHeight, port.scrollTop + delta),
85
- )
86
- if (Math.abs(next - port.scrollTop) < 1) return result
87
- port.scrollTo({
88
- top: next,
89
- behavior: changed && !prefersReducedMotion() ? 'smooth' : 'instant',
90
- })
94
+ const next = Math.max(0, Math.min(port.scrollHeight - port.clientHeight, base + delta))
95
+ if (Math.abs(next - base) < 1) return result
96
+ const smooth = changed && !prefersReducedMotion()
97
+ flight = smooth ? { port, top: next, until: now + FLIGHT_MS } : undefined
98
+ if (smooth) port.addEventListener('scrollend', land, { once: true })
99
+ port.scrollTo({ top: next, behavior: smooth ? 'smooth' : 'instant' })
91
100
  return result
92
101
  }
93
102
 
@@ -90,3 +90,12 @@ export function filterCommands<T extends CommandMatchable>(
90
90
  if (q) sections.sort((a, b) => b.best - a.best)
91
91
  return sections.map(({ key, label, matches }) => ({ key, label, matches }))
92
92
  }
93
+
94
+ export interface CommandMatchRange {
95
+ start: number
96
+ end: number
97
+ }
98
+
99
+ export function commandMatchRange(match: CommandMatch): CommandMatchRange | null {
100
+ return match.start < 0 ? null : { start: match.start, end: match.end }
101
+ }
@@ -0,0 +1,76 @@
1
+ export const LINE_CLAMP_DEFAULT = 3
2
+
3
+ export function lineClampCount(lines: number | undefined) {
4
+ if (lines === undefined || !Number.isFinite(lines)) return LINE_CLAMP_DEFAULT
5
+ return Math.max(1, Math.floor(lines))
6
+ }
7
+
8
+ function measureHeights(content: HTMLElement) {
9
+ content.setAttribute('data-measuring', '')
10
+ const collapsed = content.clientHeight
11
+ const full = content.scrollHeight
12
+ content.removeAttribute('data-measuring')
13
+ return { collapsed, full }
14
+ }
15
+
16
+ export function measureLineClamp(content: HTMLElement) {
17
+ if (content.hasAttribute('data-animating')) return null
18
+ const { collapsed, full } = measureHeights(content)
19
+ return full - collapsed > 1
20
+ }
21
+
22
+ function toMs(value: string) {
23
+ const amount = Number.parseFloat(value)
24
+ if (Number.isNaN(amount)) return 0
25
+ return value.trim().endsWith('ms') ? amount : amount * 1000
26
+ }
27
+
28
+ const FADE = '--hn-line-clamp-fade'
29
+
30
+ export function animateLineClamp(content: HTMLElement, expanded: boolean, done: () => void) {
31
+ const running = content.style.maxHeight !== ''
32
+ const current = running ? content.getBoundingClientRect().height : undefined
33
+ const fade = running ? getComputedStyle(content).getPropertyValue(FADE) : ''
34
+
35
+ content.style.transition = 'none'
36
+ const { collapsed, full } = measureHeights(content)
37
+ const from = current ?? (expanded ? collapsed : full)
38
+ const to = expanded ? full : collapsed
39
+ content.toggleAttribute('data-expanded', !expanded)
40
+ if (fade) content.style.setProperty(FADE, fade)
41
+ content.style.maxHeight = `${from}px`
42
+ content.setAttribute('data-animating', '')
43
+ void content.offsetHeight
44
+ content.style.transition = ''
45
+ content.style.removeProperty(FADE)
46
+ content.toggleAttribute('data-expanded', expanded)
47
+ content.style.maxHeight = `${to}px`
48
+
49
+ let timer: ReturnType<typeof setTimeout> | undefined
50
+ const stop = () => {
51
+ content.removeEventListener('transitionend', onEnd)
52
+ clearTimeout(timer)
53
+ }
54
+ const finish = () => {
55
+ stop()
56
+ content.style.maxHeight = ''
57
+ content.removeAttribute('data-animating')
58
+ done()
59
+ }
60
+ function onEnd(event: TransitionEvent) {
61
+ if (event.target === content && event.propertyName === 'max-height') finish()
62
+ }
63
+
64
+ if (Math.abs(from - to) < 1) {
65
+ finish()
66
+ return stop
67
+ }
68
+ const duration = Math.max(...getComputedStyle(content).transitionDuration.split(',').map(toMs))
69
+ content.addEventListener('transitionend', onEnd)
70
+ timer = setTimeout(finish, duration + 80)
71
+ return stop
72
+ }
73
+
74
+ export function revealLineClamp(root: HTMLElement | null) {
75
+ root?.scrollIntoView?.({ block: 'nearest' })
76
+ }
@@ -146,3 +146,148 @@ export function createScrollRestorer(key: string, ports: ScrollRestorerPorts): S
146
146
  },
147
147
  }
148
148
  }
149
+
150
+ export const SCROLL_FIRST_PAINT_FIELD = '__hnScrollAt'
151
+
152
+ export function scrollRestoreScript() {
153
+ const collect = `(function(){var s=window.${SCROLL_FIRST_PAINT_FIELD}=window.${SCROLL_FIRST_PAINT_FIELD}||{};var e=document.querySelectorAll('[${SCROLL_AT_ATTR}]');for(var i=0;i<e.length;i++){s[e[i].getAttribute('${SCROLL_RESTORE_ATTR}')]=Number(e[i].getAttribute('${SCROLL_AT_ATTR}'));e[i].removeAttribute('${SCROLL_AT_ATTR}')}})()`
154
+ return `${firstPaintRestoreScript()};${collect}`
155
+ }
156
+
157
+ export interface ScrollRestoreSessionOptions {
158
+ key: string
159
+ viewport: HTMLElement
160
+ target: HTMLElement
161
+ initial?: boolean
162
+ onUpdated: (listener: () => void) => () => void
163
+ onScroll: (listener: () => void) => () => void
164
+ }
165
+
166
+ export interface ScrollRestoreSession {
167
+ dispose: () => void
168
+ }
169
+
170
+ const ABANDON_EVENTS = ['wheel', 'touchstart', 'keydown'] as const
171
+ const NAVIGATION_FALLBACK_MS = 500
172
+
173
+ function takeFirstPaint(key: string, target: HTMLElement) {
174
+ const store = (window as unknown as Record<string, Record<string, number> | undefined>)[
175
+ SCROLL_FIRST_PAINT_FIELD
176
+ ]
177
+ if (store && key in store) {
178
+ const top = store[key]
179
+ delete store[key]
180
+ return top
181
+ }
182
+ const at = target.getAttribute(SCROLL_AT_ATTR)
183
+ target.removeAttribute(SCROLL_AT_ATTR)
184
+ return at === null ? undefined : Number(at)
185
+ }
186
+
187
+ function hashTarget() {
188
+ if (!location.hash) return null
189
+ try {
190
+ return document.getElementById(decodeURIComponent(location.hash.slice(1)))
191
+ } catch {
192
+ return null
193
+ }
194
+ }
195
+
196
+ export function createScrollRestoreSession({
197
+ key,
198
+ viewport,
199
+ target,
200
+ initial = true,
201
+ onUpdated,
202
+ onScroll,
203
+ }: ScrollRestoreSessionOptions): ScrollRestoreSession {
204
+ const page = () => location.pathname + location.search
205
+ const here = () => ({ href: location.href, page: page() })
206
+ const setTimer = (fn: () => void, ms: number) => {
207
+ const id = setTimeout(fn, ms)
208
+ return () => clearTimeout(id)
209
+ }
210
+
211
+ let settled = here()
212
+ let navigating = false
213
+ let clearFallback: (() => void) | null = null
214
+ let disposed = false
215
+
216
+ const restorer = createScrollRestorer(key, {
217
+ getViewport: () => viewport,
218
+ takeFirstPaintTop: () => takeFirstPaint(key, target),
219
+ readState: () => history.state,
220
+ writeState: state => {
221
+ if (!navigating && page() === settled.page) history.replaceState(state, '')
222
+ },
223
+ setTimer,
224
+ })
225
+
226
+ function land(first = false) {
227
+ navigating = false
228
+ clearFallback?.()
229
+ clearFallback = null
230
+ settled = here()
231
+ const saved = readScrollRecord(history.state)[key]
232
+ restorer.restore(first)
233
+ if (!first && saved === undefined) hashTarget()?.scrollIntoView()
234
+ }
235
+
236
+ function detect(traversal = false) {
237
+ if (disposed || location.href === settled.href) return
238
+ if (page() === settled.page) {
239
+ settled = here()
240
+ if (traversal && readScrollRecord(history.state)[key] !== undefined) restorer.restore()
241
+ return
242
+ }
243
+ if (navigating) return
244
+ navigating = true
245
+ restorer.abandon()
246
+ clearFallback = setTimer(() => land(), NAVIGATION_FALLBACK_MS)
247
+ }
248
+
249
+ const check = () => queueMicrotask(() => detect())
250
+ const traverse = () => queueMicrotask(() => detect(true))
251
+ const entryChange = (event: Event) => {
252
+ const traversal = (event as Event & { navigationType?: string }).navigationType === 'traverse'
253
+ queueMicrotask(() => detect(traversal))
254
+ }
255
+ const abandon = () => restorer.abandon()
256
+ const save = () => restorer.save()
257
+ const navigation = (window as Window & { navigation?: EventTarget }).navigation
258
+
259
+ const off = [
260
+ onUpdated(() => {
261
+ detect()
262
+ if (navigating) land()
263
+ else restorer.settle()
264
+ }),
265
+ onScroll(() => {
266
+ detect()
267
+ restorer.saveSoon()
268
+ }),
269
+ ]
270
+ for (const name of ABANDON_EVENTS) viewport.addEventListener(name, abandon, { passive: true })
271
+ navigation?.addEventListener('currententrychange', entryChange)
272
+ window.addEventListener('popstate', traverse)
273
+ window.addEventListener('hashchange', check)
274
+ window.addEventListener('pagehide', save)
275
+ document.addEventListener('click', save, true)
276
+
277
+ land(initial)
278
+
279
+ return {
280
+ dispose() {
281
+ disposed = true
282
+ off.forEach(stop => stop())
283
+ for (const name of ABANDON_EVENTS) viewport.removeEventListener(name, abandon)
284
+ navigation?.removeEventListener('currententrychange', entryChange)
285
+ window.removeEventListener('popstate', traverse)
286
+ window.removeEventListener('hashchange', check)
287
+ window.removeEventListener('pagehide', save)
288
+ document.removeEventListener('click', save, true)
289
+ clearFallback?.()
290
+ restorer.dispose()
291
+ },
292
+ }
293
+ }
@@ -205,6 +205,10 @@ export const enUS: UiMessages = {
205
205
  placeholder: 'Search',
206
206
  empty: 'No matches',
207
207
  },
208
+ lineClamp: {
209
+ expand: 'Show all',
210
+ collapse: 'Show less',
211
+ },
208
212
  calendar: {
209
213
  label: 'Calendar',
210
214
  prev: 'Previous month',
@@ -44,9 +44,11 @@ export function mergeUiMessages(base: UiMessages, patch: PartialUiMessages): UiM
44
44
  rating: { ...base.rating, ...patch.rating },
45
45
  form: { ...base.form, ...patch.form },
46
46
  command: { ...base.command, ...patch.command },
47
+ lineClamp: { ...base.lineClamp, ...patch.lineClamp },
47
48
  }
48
49
  }
49
50
 
50
51
  export { zhCN } from './zh-CN'
51
52
  export { enUS } from './en-US'
53
+ export { jaJP } from './ja-JP'
52
54
  export type { UiMessages, PartialUiMessages } from './types'
@@ -0,0 +1,229 @@
1
+ import type { UiMessages } from './types'
2
+
3
+ export const jaJP: UiMessages = {
4
+ qrCode: {
5
+ label: 'QRコード',
6
+ empty: 'コンテンツがありません',
7
+ expired: 'QRコードの有効期限が切れました',
8
+ scanned: 'スキャン済み',
9
+ error: 'QRコードを生成できません',
10
+ refresh: 'QRコードを更新',
11
+ },
12
+ carousel: {
13
+ label: 'おすすめコンテンツ',
14
+ role: 'カルーセル',
15
+ slide: 'スライド',
16
+ prev: '前のグループ',
17
+ next: '次のグループ',
18
+ choose: '表示位置を選択',
19
+ play: '自動再生を開始',
20
+ pause: '自動再生を停止',
21
+ empty: 'コンテンツがありません',
22
+ position: (index, total) => `${total} 件中 ${index} 件目`,
23
+ goTo: index => `グループ ${index} に移動`,
24
+ },
25
+ monthGrid: { today: '今月に戻る' },
26
+ masonry: { empty: 'コンテンツがありません' },
27
+ splitButton: { more: 'その他の操作' },
28
+ editable: {
29
+ edit: '編集',
30
+ save: '保存',
31
+ placeholder: 'クリックして編集',
32
+ failed: '保存できませんでした。もう一度お試しください',
33
+ },
34
+ tag: 'ja-JP',
35
+ stepper: {
36
+ label: 'ステップ',
37
+ progress: (step, total) => `ステップ ${step} / ${total}`,
38
+ completed: '完了',
39
+ error: 'このステップにエラーがあります',
40
+ },
41
+ common: {
42
+ close: '閉じる',
43
+ copy: 'コピー',
44
+ copied: 'コピーしました',
45
+ clear: 'クリア',
46
+ loading: '読み込み中',
47
+ confirm: 'OK',
48
+ cancel: 'キャンセル',
49
+ },
50
+ banner: {
51
+ prev: '前のお知らせ',
52
+ next: '次のお知らせ',
53
+ },
54
+ pagination: {
55
+ rangeLabel: (from, to, total) =>
56
+ total ? String(from) + '–' + to + ' / 全 ' + total + ' 件' : '全 0 件',
57
+ pageCountLabel: (page, count) => page + ' / ' + count + ' ページ',
58
+ pageSizeLabel: '1 ページあたりの件数',
59
+ pageSizeOption: n => String(n) + ' 件 / ページ',
60
+ jumpLabel: 'ページへ移動',
61
+ previousPagesLabel: n => n + ' ページ戻る',
62
+ nextPagesLabel: n => n + ' ページ進む',
63
+ choosePage: 'ページを選択',
64
+ choosePageHint: '上下の矢印キーでページを選択',
65
+ navLabel: 'ページネーション',
66
+ prev: '前のページ',
67
+ next: '次のページ',
68
+ first: '最初のページ',
69
+ last: '最後のページ',
70
+ pageLabel: n => `${n} ページ`,
71
+ totalLabel: n => `全 ${n} 件`,
72
+ },
73
+ table: {
74
+ edit: '編集',
75
+ save: '保存',
76
+ cancel: 'キャンセル',
77
+ editFailed: '保存できませんでした。もう一度お試しください',
78
+ selectPage: '現在のページを選択',
79
+ selectFiltered: '絞り込み結果を選択',
80
+ expand: '展開',
81
+ collapse: '折りたたむ',
82
+ moveRow: '行を移動',
83
+ moveColumn: '列を移動',
84
+ resizeColumn: '列幅を調整',
85
+
86
+ noColumns: '表示する列がありません',
87
+ empty: 'データがありません',
88
+ loading: '読み込み中',
89
+ sortAsc: '昇順で並べ替え',
90
+ sortDesc: '降順で並べ替え',
91
+ sortNone: '並べ替えを解除',
92
+ selectRow: 'この行を選択',
93
+ selectAll: 'すべて選択',
94
+ },
95
+ select: {
96
+ placeholder: '選択してください',
97
+ empty: '一致する項目がありません',
98
+ optionCountLabel: n => `${n} 件の選択肢があります`,
99
+ },
100
+ upload: {
101
+ choose: 'ファイルを選択',
102
+ dropHint: 'ここにファイルをドロップ、またはクリックして選択',
103
+ tooLargeLabel: readableSize => `ファイルが上限の ${readableSize} を超えています`,
104
+ remove: '削除',
105
+ removeFile: name => `${name} を削除`,
106
+ },
107
+ time: {
108
+ justNow: 'たった今',
109
+ unknown: '日時不明',
110
+ },
111
+ virtualList: { empty: 'データがありません' },
112
+ dataList: {
113
+ empty: 'データがありません',
114
+ list: 'リスト表示',
115
+ grid: 'グリッド表示',
116
+ layout: '表示レイアウト',
117
+ },
118
+ scroll: {
119
+ regionLabel: 'スクロール可能な領域',
120
+ backToTop: 'ページの先頭へ戻る',
121
+ },
122
+ toast: {
123
+ regionLabel: '通知',
124
+ },
125
+ spoiler: {
126
+ revealLabel: 'ネタバレを含みます。クリックして表示',
127
+ hideLabel: 'ネタバレを隠す',
128
+ },
129
+ codeblock: {
130
+ copy: 'コードをコピー',
131
+ },
132
+ splitter: {
133
+ handleLabel: 'パネルのサイズを調整',
134
+ },
135
+ sidebar: {
136
+ navLabel: 'サイドバーナビゲーション',
137
+ toggleLabel: 'サイドバーを切り替え',
138
+ },
139
+ anchor: {
140
+ navLabel: 'このページの目次',
141
+ },
142
+ breadcrumb: {
143
+ navLabel: 'パンくずリスト',
144
+ },
145
+ lightbox: {
146
+ zoomIn: '拡大',
147
+ zoomOut: '縮小',
148
+ resetZoom: 'ウィンドウに合わせる',
149
+ actualSize: '実際のサイズ',
150
+ rotate: '回転',
151
+ download: 'ダウンロード',
152
+ loadingLarge: '高画質版を読み込み中',
153
+ },
154
+ chip: {
155
+ remove: '削除',
156
+ },
157
+ numberInput: {
158
+ increase: '増やす',
159
+ decrease: '減らす',
160
+ },
161
+ passwordInput: {
162
+ show: 'パスワードを表示',
163
+ hide: 'パスワードを隠す',
164
+ },
165
+ tree: { empty: 'ノードがありません' },
166
+ treeSelect: {
167
+ search: 'ノードを検索',
168
+ },
169
+ combobox: {
170
+ placeholder: '入力または選択',
171
+ toggle: '選択肢を表示',
172
+ },
173
+ slider: {
174
+ minimum: '最小値',
175
+ maximum: '最大値',
176
+ },
177
+ pinInput: {
178
+ cellLabel: (index, total) => `${total} 桁中 ${index} 桁目`,
179
+ },
180
+ dateField: {
181
+ year: '年',
182
+ month: '月',
183
+ day: '日',
184
+ hour: '時',
185
+ minute: '分',
186
+ second: '秒',
187
+ dayPeriod: '午前または午後',
188
+ },
189
+ dateRangeField: {
190
+ start: '開始日',
191
+ end: '終了日',
192
+ separator: '〜',
193
+ },
194
+ datePicker: {
195
+ open: 'カレンダーを開く',
196
+ },
197
+ dateTimePicker: {
198
+ time: '時刻',
199
+ },
200
+ rating: {
201
+ star: value => `星 ${value}`,
202
+ label: (value, max) => `星 ${max} 個中 ${value} 個`,
203
+ score: (value, max) => `${max} 点中 ${value} 点`,
204
+ },
205
+ form: {
206
+ required: '必須',
207
+ },
208
+ command: {
209
+ label: 'コマンドパレット',
210
+ placeholder: '検索',
211
+ empty: '一致する項目がありません',
212
+ },
213
+ lineClamp: {
214
+ expand: 'すべて表示',
215
+ collapse: '折りたたむ',
216
+ },
217
+ calendar: {
218
+ label: 'カレンダー',
219
+ prev: '前の月',
220
+ next: '次の月',
221
+ prevYear: '前の年',
222
+ nextYear: '次の年',
223
+ prevYears: '前の 12 年',
224
+ nextYears: '次の 12 年',
225
+ pickMonth: '月を選択',
226
+ pickYear: '年を選択',
227
+ weekStartsOn: 0,
228
+ },
229
+ }
@@ -206,6 +206,10 @@ export interface UiMessages {
206
206
  placeholder: string
207
207
  empty: string
208
208
  }
209
+ lineClamp: {
210
+ expand: string
211
+ collapse: string
212
+ }
209
213
  calendar: {
210
214
  label: string
211
215
  prev: string
@@ -200,6 +200,10 @@ export const zhCN: UiMessages = {
200
200
  placeholder: '搜索',
201
201
  empty: '无匹配项',
202
202
  },
203
+ lineClamp: {
204
+ expand: '展开全部',
205
+ collapse: '收起',
206
+ },
203
207
  calendar: {
204
208
  label: '日历',
205
209
  prev: '上个月',
@@ -1,8 +1,10 @@
1
1
  :root,
2
2
  [data-density='comfortable'] {
3
+ --hn-control-h-xs: 1.5rem;
3
4
  --hn-control-h-sm: 1.75rem;
4
5
  --hn-control-h-md: 2.25rem;
5
6
  --hn-control-h-lg: 2.75rem;
7
+ --hn-control-px-xs: 0.5rem;
6
8
  --hn-control-px-sm: 0.625rem;
7
9
  --hn-control-px-md: 1rem;
8
10
  --hn-control-px-lg: 1.25rem;
@@ -16,9 +18,11 @@
16
18
  }
17
19
 
18
20
  [data-density='compact'] {
21
+ --hn-control-h-xs: 1.25rem;
19
22
  --hn-control-h-sm: 1.5rem;
20
23
  --hn-control-h-md: 1.875rem;
21
24
  --hn-control-h-lg: 2.25rem;
25
+ --hn-control-px-xs: 0.25rem;
22
26
  --hn-control-px-sm: 0.375rem;
23
27
  --hn-control-px-md: 0.5rem;
24
28
  --hn-control-px-lg: 0.75rem;
@@ -0,0 +1,98 @@
1
+ @property --hn-line-clamp-fade {
2
+ syntax: '<number>';
3
+ inherits: false;
4
+ initial-value: 0;
5
+ }
6
+
7
+ .hn-line-clamp-root,
8
+ .hn-line-clamp {
9
+ min-width: 0;
10
+ }
11
+
12
+ .hn-line-clamp:not([data-expanded]),
13
+ .hn-line-clamp[data-animating],
14
+ .hn-line-clamp[data-measuring] {
15
+ overflow: hidden;
16
+ }
17
+
18
+ .hn-line-clamp:not([data-expanded]):not([data-animating]),
19
+ .hn-line-clamp[data-measuring] {
20
+ display: -webkit-box;
21
+ -webkit-box-orient: vertical;
22
+ -webkit-line-clamp: var(--hn-line-clamp, 3);
23
+ }
24
+
25
+ .hn-line-clamp[data-measuring] {
26
+ max-height: none !important;
27
+ transition: none !important;
28
+ }
29
+
30
+ .hn-line-clamp[data-animating] {
31
+ transition:
32
+ max-height var(--hn-duration-base) var(--hn-ease-move),
33
+ --hn-line-clamp-fade var(--hn-duration-base) var(--hn-ease-move);
34
+ }
35
+
36
+ .hn-line-clamp:not([data-expanded]),
37
+ .hn-line-clamp[data-animating] {
38
+ mask-image: linear-gradient(
39
+ to bottom,
40
+ #000 calc(100% - min(1.5lh, calc(var(--hn-line-clamp, 3) * 0.6lh)) * var(--hn-line-clamp-fade)),
41
+ transparent
42
+ );
43
+ }
44
+
45
+ .hn-line-clamp-root[data-truncated='true'] > .hn-line-clamp:not([data-expanded]) {
46
+ --hn-line-clamp-fade: 1;
47
+ }
48
+
49
+ .hn-line-clamp-root[data-truncated='false'] > .hn-line-clamp {
50
+ mask-image: none;
51
+ }
52
+
53
+ .hn-line-clamp-toggle {
54
+ display: flex;
55
+ align-items: center;
56
+ height: 1lh;
57
+ margin-block-start: var(--spacing);
58
+ }
59
+
60
+ .hn-line-clamp-root[data-truncated='false'] > .hn-line-clamp-toggle {
61
+ display: none;
62
+ }
63
+
64
+ @supports (animation-timeline: scroll()) and (timeline-scope: --hn-line-clamp) {
65
+ .hn-line-clamp-root:not([data-truncated]):not([data-expanded]) {
66
+ timeline-scope: --hn-line-clamp;
67
+ }
68
+
69
+ .hn-line-clamp-root:not([data-truncated]):not([data-expanded]) > .hn-line-clamp {
70
+ scroll-timeline: --hn-line-clamp block;
71
+ animation: hn-line-clamp-fade 1ms linear both;
72
+ animation-timeline: --hn-line-clamp;
73
+ }
74
+
75
+ .hn-line-clamp-root:not([data-truncated]):not([data-expanded]) > .hn-line-clamp-toggle {
76
+ height: 0;
77
+ margin-block-start: 0;
78
+ visibility: hidden;
79
+ animation: hn-line-clamp-toggle 1ms linear both;
80
+ animation-timeline: --hn-line-clamp;
81
+ }
82
+ }
83
+
84
+ @keyframes hn-line-clamp-fade {
85
+ from,
86
+ to {
87
+ --hn-line-clamp-fade: 1;
88
+ }
89
+ }
90
+
91
+ @keyframes hn-line-clamp-toggle {
92
+ from,
93
+ to {
94
+ height: 1lh;
95
+ margin-block-start: var(--spacing);
96
+ visibility: visible;
97
+ }
98
+ }