@globalbrain/sefirot 4.34.1 → 4.35.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.
- package/config/nuxt.js +44 -1
- package/config/vite.js +2 -3
- package/lib/blocks/lens/FieldContext.ts +5 -0
- package/lib/blocks/lens/FieldData.ts +140 -0
- package/lib/blocks/lens/FieldRegistry.ts +23 -0
- package/lib/blocks/lens/FileDownloader.ts +1 -0
- package/lib/blocks/lens/FilterOperator.ts +33 -0
- package/lib/blocks/lens/LensQuery.ts +10 -0
- package/lib/blocks/lens/LensResult.ts +20 -0
- package/lib/blocks/lens/ResourceFetcher.ts +3 -0
- package/lib/blocks/lens/Rule.ts +12 -0
- package/lib/blocks/lens/components/LensCatalog.vue +490 -0
- package/lib/blocks/lens/components/LensCatalogControl.vue +220 -0
- package/lib/blocks/lens/components/LensCatalogFooter.vue +46 -0
- package/lib/blocks/lens/components/LensCatalogStateFilter.vue +171 -0
- package/lib/blocks/lens/components/LensCatalogStateFilterCondition.vue +86 -0
- package/lib/blocks/lens/components/LensCatalogStateFilterGroup.vue +102 -0
- package/lib/blocks/lens/components/LensCatalogStateSort.vue +159 -0
- package/lib/blocks/lens/components/LensFormFilter.vue +169 -0
- package/lib/blocks/lens/components/LensFormFilterCondition.vue +205 -0
- package/lib/blocks/lens/components/LensFormFilterGroup.vue +175 -0
- package/lib/blocks/lens/components/LensFormOverride.vue +45 -0
- package/lib/blocks/lens/components/LensFormOverrideBase.vue +204 -0
- package/lib/blocks/lens/components/LensFormView.vue +347 -0
- package/lib/blocks/lens/components/LensTable.vue +154 -0
- package/lib/blocks/lens/composables/FieldFactory.ts +27 -0
- package/lib/blocks/lens/composables/FieldRegistry.ts +16 -0
- package/lib/blocks/lens/composables/FileDownloader.ts +10 -0
- package/lib/blocks/lens/composables/ResourceFetcher.ts +30 -0
- package/lib/blocks/lens/composables/SetupLens.ts +55 -0
- package/lib/blocks/lens/fields/ContentField.ts +34 -0
- package/lib/blocks/lens/fields/DateField.ts +66 -0
- package/lib/blocks/lens/fields/DatetimeField.ts +35 -0
- package/lib/blocks/lens/fields/Field.ts +244 -0
- package/lib/blocks/lens/fields/FileUploadField.ts +63 -0
- package/lib/blocks/lens/fields/IdField.ts +34 -0
- package/lib/blocks/lens/fields/LinkField.ts +53 -0
- package/lib/blocks/lens/fields/NumberField.ts +32 -0
- package/lib/blocks/lens/fields/RelatedManyField.ts +62 -0
- package/lib/blocks/lens/fields/SelectField.ts +198 -0
- package/lib/blocks/lens/fields/SlackMessageField.ts +34 -0
- package/lib/blocks/lens/fields/TextField.ts +46 -0
- package/lib/blocks/lens/fields/TextareaField.ts +49 -0
- package/lib/blocks/lens/filter-inputs/FilterInput.ts +72 -0
- package/lib/blocks/lens/filter-inputs/NumberFilterInput.ts +26 -0
- package/lib/blocks/lens/filter-inputs/SelectFilterInput.ts +76 -0
- package/lib/blocks/lens/filter-inputs/TextFilterInput.ts +26 -0
- package/lib/blocks/lens/validation/RuleMapper.ts +22 -0
- package/lib/components/SInputTextarea.vue +28 -10
- package/lib/components/STable.vue +230 -61
- package/lib/components/STableCell.vue +2 -2
- package/lib/composables/TableAnimation.ts +180 -0
- package/lib/support/Scroll.ts +263 -0
- package/lib/support/Utils.ts +1 -1
- package/package.json +7 -15
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
// The browser-native smooth scroll animation is too soft and slow, too catchy.
|
|
2
|
+
// This module provides a custom smooth scroll that is more subtle and faster.
|
|
3
|
+
|
|
4
|
+
// Duration configuration (in milliseconds)
|
|
5
|
+
const MIN_SCROLL_DURATION = 100
|
|
6
|
+
const MAX_SCROLL_DURATION = 160
|
|
7
|
+
const DURATION_SCALE_FACTOR = 0.15
|
|
8
|
+
const TOP_OFFSET_TOLERANCE_PX = 10
|
|
9
|
+
type ScrollTarget = HTMLElement | Window
|
|
10
|
+
|
|
11
|
+
const topOffsetCache = new WeakMap<HTMLElement, number>()
|
|
12
|
+
|
|
13
|
+
function easeOutCubic(t: number): number {
|
|
14
|
+
return 1 - (1 - t) ** 3
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Calculate scroll animation duration based on distance.
|
|
19
|
+
* Shorter distances get faster animations, longer distances are capped at max duration.
|
|
20
|
+
*/
|
|
21
|
+
function calculateScrollDuration(distance: number): number {
|
|
22
|
+
const absDistance = Math.abs(distance)
|
|
23
|
+
const scaled = absDistance * DURATION_SCALE_FACTOR
|
|
24
|
+
return Math.min(MAX_SCROLL_DURATION, Math.max(MIN_SCROLL_DURATION, scaled))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getScrollTop(target: ScrollTarget): number {
|
|
28
|
+
return target instanceof Window ? target.scrollY : target.scrollTop
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function setScrollTop(target: ScrollTarget, value: number): void {
|
|
32
|
+
if (target instanceof Window) {
|
|
33
|
+
target.scrollTo(0, value)
|
|
34
|
+
} else {
|
|
35
|
+
target.scrollTop = value
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function getEventTarget(target: ScrollTarget): EventTarget {
|
|
40
|
+
return target instanceof Window ? window : target
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Smoothly scroll an element to a target Y position with adaptive duration.
|
|
45
|
+
* The animation can be interrupted by user interaction (wheel, touch, mouse, keyboard).
|
|
46
|
+
*/
|
|
47
|
+
function smoothScrollTo(target: ScrollTarget, targetY: number): Promise<void> {
|
|
48
|
+
const from = getScrollTop(target)
|
|
49
|
+
const delta = targetY - from
|
|
50
|
+
|
|
51
|
+
// Skip animation for very small distances
|
|
52
|
+
if (Math.abs(delta) < 1) {
|
|
53
|
+
return Promise.resolve()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const duration = calculateScrollDuration(delta)
|
|
57
|
+
let startTime: number | null = null
|
|
58
|
+
let animationFrame: number | null = null
|
|
59
|
+
let resolvePromise: () => void = () => undefined
|
|
60
|
+
let settled = false
|
|
61
|
+
|
|
62
|
+
// Allow user to interrupt the scroll
|
|
63
|
+
const controller = new AbortController()
|
|
64
|
+
const finishScroll = () => {
|
|
65
|
+
if (settled) {
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
settled = true
|
|
69
|
+
if (animationFrame !== null) {
|
|
70
|
+
cancelAnimationFrame(animationFrame)
|
|
71
|
+
animationFrame = null
|
|
72
|
+
}
|
|
73
|
+
controller.abort()
|
|
74
|
+
resolvePromise()
|
|
75
|
+
}
|
|
76
|
+
const cancel = () => {
|
|
77
|
+
if (controller.signal.aborted) {
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
finishScroll()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const opts = { passive: true, signal: controller.signal } as const
|
|
84
|
+
|
|
85
|
+
const eventTarget = getEventTarget(target)
|
|
86
|
+
eventTarget.addEventListener('wheel', cancel, opts)
|
|
87
|
+
eventTarget.addEventListener('touchstart', cancel, opts)
|
|
88
|
+
eventTarget.addEventListener('mousedown', cancel, opts)
|
|
89
|
+
eventTarget.addEventListener('keydown', cancel, opts)
|
|
90
|
+
|
|
91
|
+
return new Promise<void>((resolve) => {
|
|
92
|
+
resolvePromise = resolve
|
|
93
|
+
|
|
94
|
+
function step(currentTime: number): void {
|
|
95
|
+
if (controller.signal.aborted) {
|
|
96
|
+
finishScroll()
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (startTime === null) {
|
|
101
|
+
startTime = currentTime
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const elapsed = currentTime - startTime
|
|
105
|
+
const progress = Math.min(elapsed / duration, 1)
|
|
106
|
+
const eased = easeOutCubic(progress)
|
|
107
|
+
const position = from + delta * eased
|
|
108
|
+
|
|
109
|
+
setScrollTop(target, position)
|
|
110
|
+
|
|
111
|
+
if (progress < 1) {
|
|
112
|
+
animationFrame = requestAnimationFrame(step)
|
|
113
|
+
} else {
|
|
114
|
+
animationFrame = null
|
|
115
|
+
finishScroll()
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
animationFrame = requestAnimationFrame(step)
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Detect fixed or sticky elements at the top of the viewport and calculate
|
|
125
|
+
* the total height they occupy.
|
|
126
|
+
*/
|
|
127
|
+
function detectTopOffset(tableRootElement: HTMLElement): number {
|
|
128
|
+
if (typeof document === 'undefined') {
|
|
129
|
+
return 0
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (topOffsetCache.has(tableRootElement)) {
|
|
133
|
+
return topOffsetCache.get(tableRootElement) ?? 0
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
let maxBottom = 0
|
|
137
|
+
const elements = document.querySelectorAll('*')
|
|
138
|
+
|
|
139
|
+
for (const el of elements) {
|
|
140
|
+
const styles = window.getComputedStyle(el)
|
|
141
|
+
const position = styles.position
|
|
142
|
+
|
|
143
|
+
// Only consider fixed or sticky elements
|
|
144
|
+
if (position !== 'fixed' && position !== 'sticky') {
|
|
145
|
+
continue
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const rect = el.getBoundingClientRect()
|
|
149
|
+
|
|
150
|
+
// Check if element is positioned at or near the top
|
|
151
|
+
// and is visible
|
|
152
|
+
if (
|
|
153
|
+
rect.top <= TOP_OFFSET_TOLERANCE_PX
|
|
154
|
+
&& rect.height > 0
|
|
155
|
+
&& rect.width > 0
|
|
156
|
+
) {
|
|
157
|
+
// Track the furthest bottom edge
|
|
158
|
+
maxBottom = Math.max(maxBottom, rect.bottom)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const computed = maxBottom > 0 ? maxBottom : 0
|
|
163
|
+
topOffsetCache.set(tableRootElement, computed)
|
|
164
|
+
return computed
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function isAlreadyVisible(
|
|
168
|
+
relativeTop: number,
|
|
169
|
+
containerHeight: number,
|
|
170
|
+
topOffset: number
|
|
171
|
+
): boolean {
|
|
172
|
+
const isNotCoveredByHeader = relativeTop >= topOffset
|
|
173
|
+
const isInView = relativeTop < containerHeight
|
|
174
|
+
return isNotCoveredByHeader && isInView
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function getTargetScrollTop(
|
|
178
|
+
currentScrollTop: number,
|
|
179
|
+
relativeTop: number,
|
|
180
|
+
borderOffset: number,
|
|
181
|
+
topOffset: number
|
|
182
|
+
): number {
|
|
183
|
+
return currentScrollTop + relativeTop - borderOffset - topOffset
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Scroll the page so the table is positioned at the top of the viewport.
|
|
188
|
+
* Finds the nearest scrollable parent or scrolls the window if needed.
|
|
189
|
+
* Automatically accounts for fixed/sticky headers at the top.
|
|
190
|
+
*/
|
|
191
|
+
export function scrollTableIntoView(
|
|
192
|
+
tableRootElement: HTMLElement,
|
|
193
|
+
headElement: HTMLElement | null,
|
|
194
|
+
bodyElement: HTMLElement | null,
|
|
195
|
+
borderless: boolean,
|
|
196
|
+
borderSize: number
|
|
197
|
+
): Promise<void> {
|
|
198
|
+
const rect = tableRootElement.getBoundingClientRect()
|
|
199
|
+
const borderOffset = borderless ? 0 : borderSize
|
|
200
|
+
const topOffset = detectTopOffset(tableRootElement)
|
|
201
|
+
|
|
202
|
+
const resetHorizontalScroll = () => {
|
|
203
|
+
if (headElement) {
|
|
204
|
+
headElement.scrollLeft = 0
|
|
205
|
+
}
|
|
206
|
+
if (bodyElement) {
|
|
207
|
+
bodyElement.scrollLeft = 0
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Smooth scroll the table body to the top
|
|
212
|
+
const bodyScrollToTop = bodyElement
|
|
213
|
+
? smoothScrollTo(bodyElement, 0)
|
|
214
|
+
: Promise.resolve()
|
|
215
|
+
const resetAfterBodyScrollToTop = () =>
|
|
216
|
+
bodyScrollToTop.then(() => resetHorizontalScroll())
|
|
217
|
+
const runContainerScrollAndReset = (containerScrollToTable: Promise<void>) =>
|
|
218
|
+
Promise.all([containerScrollToTable, bodyScrollToTop]).then(() =>
|
|
219
|
+
resetHorizontalScroll()
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
// Try to find a scrollable parent container
|
|
223
|
+
let scrollableParent = tableRootElement.parentElement
|
|
224
|
+
while (scrollableParent) {
|
|
225
|
+
const overflowY = window.getComputedStyle(scrollableParent).overflowY
|
|
226
|
+
const isScrollable = overflowY === 'auto' || overflowY === 'scroll'
|
|
227
|
+
|
|
228
|
+
if (isScrollable) {
|
|
229
|
+
const parentRect = scrollableParent.getBoundingClientRect()
|
|
230
|
+
const relativeTop = rect.top - parentRect.top
|
|
231
|
+
|
|
232
|
+
if (isAlreadyVisible(relativeTop, parentRect.height, topOffset)) {
|
|
233
|
+
return resetAfterBodyScrollToTop()
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const targetScrollTop = getTargetScrollTop(
|
|
237
|
+
scrollableParent.scrollTop,
|
|
238
|
+
relativeTop,
|
|
239
|
+
borderOffset,
|
|
240
|
+
topOffset
|
|
241
|
+
)
|
|
242
|
+
return runContainerScrollAndReset(
|
|
243
|
+
smoothScrollTo(scrollableParent, targetScrollTop)
|
|
244
|
+
)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
scrollableParent = scrollableParent.parentElement
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// No scrollable parent found, check window scroll
|
|
251
|
+
|
|
252
|
+
if (isAlreadyVisible(rect.top, window.innerHeight, topOffset)) {
|
|
253
|
+
return resetAfterBodyScrollToTop()
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const windowScrollTop = getTargetScrollTop(
|
|
257
|
+
window.scrollY,
|
|
258
|
+
rect.top,
|
|
259
|
+
borderOffset,
|
|
260
|
+
topOffset
|
|
261
|
+
)
|
|
262
|
+
return runContainerScrollAndReset(smoothScrollTo(window, windowScrollTop))
|
|
263
|
+
}
|
package/lib/support/Utils.ts
CHANGED
|
@@ -3,7 +3,7 @@ export function isNumber(value: unknown): value is number {
|
|
|
3
3
|
return Number.isFinite(value)
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
-
export function isObject(value: unknown): value is Record<
|
|
6
|
+
export function isObject(value: unknown): value is Record<PropertyKey, unknown> {
|
|
7
7
|
if (value == null || typeof value !== 'object') { return false }
|
|
8
8
|
|
|
9
9
|
const proto = Object.getPrototypeOf(value)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@globalbrain/sefirot",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.35.0",
|
|
4
4
|
"description": "Vue Components for Global Brain Design System.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"vue",
|
|
@@ -19,25 +19,15 @@
|
|
|
19
19
|
"author": "Kia Ishii <ka.ishii@globalbrains.com>",
|
|
20
20
|
"type": "module",
|
|
21
21
|
"exports": {
|
|
22
|
-
"./client":
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
"./config/
|
|
26
|
-
"types": "./config/nuxt.d.ts",
|
|
27
|
-
"import": "./config/nuxt.js"
|
|
28
|
-
},
|
|
29
|
-
"./config/vite": {
|
|
30
|
-
"types": "./config/vite.d.ts",
|
|
31
|
-
"import": "./config/vite.js"
|
|
32
|
-
},
|
|
22
|
+
"./client": "./client.d.ts",
|
|
23
|
+
"./shared": "./shared.d.ts",
|
|
24
|
+
"./config/nuxt": "./config/nuxt.js",
|
|
25
|
+
"./config/vite": "./config/vite.js",
|
|
33
26
|
"./dompurify": {
|
|
34
27
|
"types": "./lib/dompurify/index.d.ts",
|
|
35
28
|
"browser": "./lib/dompurify/browser.js",
|
|
36
29
|
"default": "./lib/dompurify/node.js"
|
|
37
30
|
},
|
|
38
|
-
"./shared": {
|
|
39
|
-
"types": "./shared.d.ts"
|
|
40
|
-
},
|
|
41
31
|
"./*": "./*"
|
|
42
32
|
},
|
|
43
33
|
"files": [
|
|
@@ -87,6 +77,7 @@
|
|
|
87
77
|
"postcss-nested": "^7.0.2",
|
|
88
78
|
"v-calendar": "3.0.1",
|
|
89
79
|
"vue": "^3.5.27",
|
|
80
|
+
"vue-draggable-plus": "^0.6.0",
|
|
90
81
|
"vue-router": "^4.6.4"
|
|
91
82
|
},
|
|
92
83
|
"dependencies": {
|
|
@@ -148,6 +139,7 @@
|
|
|
148
139
|
"vitepress": "^2.0.0-alpha.15",
|
|
149
140
|
"vitest": "^3.2.4",
|
|
150
141
|
"vue": "^3.5.27",
|
|
142
|
+
"vue-draggable-plus": "^0.6.0",
|
|
151
143
|
"vue-router": "^4.6.4",
|
|
152
144
|
"vue-tsc": "^3.2.2"
|
|
153
145
|
},
|