@asteby/metacore-runtime-react 42.0.0 → 43.0.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/CHANGELOG.md +28 -0
- package/dist/api-context.d.ts.map +1 -1
- package/dist/api-context.js +6 -4
- package/dist/dynamic-icon.d.ts.map +1 -1
- package/dist/dynamic-icon.js +9 -13
- package/dist/dynamic-kanban.d.ts +83 -6
- package/dist/dynamic-kanban.d.ts.map +1 -1
- package/dist/dynamic-kanban.js +647 -146
- package/dist/dynamic-table.d.ts +1 -1
- package/dist/dynamic-table.d.ts.map +1 -1
- package/dist/dynamic-table.js +14 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/license/license-gate.d.ts +0 -21
- package/dist/license/license-gate.d.ts.map +1 -1
- package/dist/license/license-gate.js +5 -2
- package/dist/portal-drag-overlay.d.ts +5 -0
- package/dist/portal-drag-overlay.d.ts.map +1 -0
- package/dist/portal-drag-overlay.js +8 -0
- package/dist/query-batch.d.ts.map +1 -1
- package/dist/query-batch.js +6 -3
- package/dist/sidebar-layout-manager.js +3 -2
- package/dist/use-flip-animation.d.ts +6 -0
- package/dist/use-flip-animation.d.ts.map +1 -1
- package/dist/use-flip-animation.js +11 -4
- package/package.json +4 -3
- package/src/__tests__/dynamic-icon-slot.test.tsx +50 -0
- package/src/__tests__/dynamic-kanban-lane-reorder.test.tsx +2 -1
- package/src/__tests__/dynamic-kanban-pipelines-pro.test.tsx +388 -0
- package/src/__tests__/dynamic-kanban.test.tsx +15 -2
- package/src/__tests__/image-cell-lucide.test.tsx +7 -5
- package/src/api-context.tsx +7 -5
- package/src/dynamic-icon.tsx +12 -16
- package/src/dynamic-kanban.tsx +950 -262
- package/src/dynamic-table.tsx +16 -2
- package/src/index.ts +6 -0
- package/src/license/license-gate.tsx +6 -2
- package/src/portal-drag-overlay.tsx +20 -0
- package/src/query-batch.ts +6 -3
- package/src/sidebar-layout-manager.tsx +3 -3
- package/src/use-flip-animation.ts +16 -3
package/src/dynamic-table.tsx
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
// column cells are tightly coupled to their design system).
|
|
13
13
|
import { useEffect, useState, useMemo, useCallback, useRef, type MouseEvent } from 'react'
|
|
14
14
|
import { useTranslation } from 'react-i18next'
|
|
15
|
+
import { useTimeZone, useCurrency } from './org-runtime-context'
|
|
15
16
|
import { format } from 'date-fns'
|
|
16
17
|
import type { DateRange } from 'react-day-picker'
|
|
17
18
|
import { useVirtualizer } from '@tanstack/react-virtual'
|
|
@@ -35,6 +36,7 @@ import {
|
|
|
35
36
|
useReactTable,
|
|
36
37
|
} from '@tanstack/react-table'
|
|
37
38
|
import { cn } from '@asteby/metacore-ui/lib'
|
|
39
|
+
import { preloadGlyphs } from '@asteby/metacore-ui/icons'
|
|
38
40
|
import {
|
|
39
41
|
Table,
|
|
40
42
|
TableBody,
|
|
@@ -282,12 +284,18 @@ export function DynamicTable({
|
|
|
282
284
|
defaultFilters,
|
|
283
285
|
extraColumns = [],
|
|
284
286
|
getDynamicColumns = defaultGetDynamicColumns,
|
|
285
|
-
timeZone,
|
|
286
|
-
currency,
|
|
287
|
+
timeZone: timeZoneProp,
|
|
288
|
+
currency: currencyProp,
|
|
287
289
|
pagination: paginationMode,
|
|
288
290
|
infiniteScroll: infiniteScrollProp = false,
|
|
289
291
|
virtualizeRows,
|
|
290
292
|
}: DynamicTableProps) {
|
|
293
|
+
// The org's timezone/currency: an explicit prop wins, else the app-wide
|
|
294
|
+
// OrgRuntimeProvider (without it, money fell back to USD).
|
|
295
|
+
const orgTimeZone = useTimeZone()
|
|
296
|
+
const orgCurrency = useCurrency()
|
|
297
|
+
const timeZone = timeZoneProp ?? orgTimeZone
|
|
298
|
+
const currency = currencyProp ?? orgCurrency
|
|
291
299
|
// The explicit `pagination` prop wins; the legacy `infiniteScroll` boolean
|
|
292
300
|
// still selects the mode when `pagination` is absent (back-compat).
|
|
293
301
|
const infiniteScroll = paginationMode ? paginationMode === 'infinite' : infiniteScrollProp
|
|
@@ -754,6 +762,12 @@ export function DynamicTable({
|
|
|
754
762
|
return gateTableMetadata(metadata, model, can, (key, fallback) => t(key, { defaultValue: fallback }))
|
|
755
763
|
}, [metadata, permissionsActive, can, model, t])
|
|
756
764
|
|
|
765
|
+
// Row-action menus mount their icons on open: load them with the
|
|
766
|
+
// metadata so the first open draws every glyph at once.
|
|
767
|
+
useEffect(() => {
|
|
768
|
+
void preloadGlyphs((metadata?.actions ?? []).map((a) => a.icon))
|
|
769
|
+
}, [metadata])
|
|
770
|
+
|
|
757
771
|
// Importing is offered when the host opts in explicitly (`canImport`) or,
|
|
758
772
|
// when it says nothing, whenever the kernel served an import spec for the
|
|
759
773
|
// model — which it does for every model with at least one importable form
|
package/src/index.ts
CHANGED
|
@@ -57,9 +57,15 @@ export {
|
|
|
57
57
|
groupByStage,
|
|
58
58
|
isTransitionAllowed,
|
|
59
59
|
applyOptimisticMove,
|
|
60
|
+
applyStageOverrides,
|
|
61
|
+
laneDeltasFromOverrides,
|
|
60
62
|
selectCardColumns,
|
|
63
|
+
visibleCardFields,
|
|
64
|
+
isEmptyCardValue,
|
|
65
|
+
nextLaneCoordinates,
|
|
61
66
|
UNASSIGNED_LANE,
|
|
62
67
|
} from './dynamic-kanban'
|
|
68
|
+
export { PortalDragOverlay, type PortalDragOverlayProps } from './portal-drag-overlay'
|
|
63
69
|
export {
|
|
64
70
|
useStageAutomations,
|
|
65
71
|
StageAutomationsButton,
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* INDEPENDIENTE del kernel: no importa su cliente ni fija su versión. Solo
|
|
20
20
|
* consume `LicenseState` y una promesa `onActivate`.
|
|
21
21
|
*/
|
|
22
|
+
import { createPortal } from 'react-dom'
|
|
22
23
|
import { useState, type ReactNode } from 'react'
|
|
23
24
|
import { useTranslation } from 'react-i18next'
|
|
24
25
|
import { ShieldAlert, Clock, Loader2 } from 'lucide-react'
|
|
@@ -165,7 +166,9 @@ function LicenseGateModal({
|
|
|
165
166
|
}
|
|
166
167
|
}
|
|
167
168
|
|
|
168
|
-
|
|
169
|
+
// In <body>: mounted inside a glass panel (backdrop-filter), a fixed veil
|
|
170
|
+
// would cover only that panel.
|
|
171
|
+
return createPortal(
|
|
169
172
|
<div
|
|
170
173
|
role="dialog"
|
|
171
174
|
aria-modal="true"
|
|
@@ -300,6 +303,7 @@ function LicenseGateModal({
|
|
|
300
303
|
)}
|
|
301
304
|
</div>
|
|
302
305
|
</div>
|
|
303
|
-
</div
|
|
306
|
+
</div>,
|
|
307
|
+
document.body,
|
|
304
308
|
)
|
|
305
309
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// PortalDragOverlay — dnd-kit's <DragOverlay> rendered into <body>.
|
|
2
|
+
//
|
|
3
|
+
// The overlay is `position: fixed` at the viewport coordinates of the item
|
|
4
|
+
// being dragged. Any ancestor with a transform, filter or backdrop-filter
|
|
5
|
+
// becomes the containing block of a fixed descendant, so inside such an
|
|
6
|
+
// ancestor (a glass panel from @asteby/metacore-theme/glass.css, an animated
|
|
7
|
+
// dialog) the lifted item shows up shifted by that ancestor's offset instead of
|
|
8
|
+
// under the pointer. Rendering it into <body> keeps the viewport as its
|
|
9
|
+
// containing block wherever the board or list is mounted. React context (the
|
|
10
|
+
// DndContext) still reaches it through the portal.
|
|
11
|
+
import * as React from 'react'
|
|
12
|
+
import { createPortal } from 'react-dom'
|
|
13
|
+
import { DragOverlay } from '@dnd-kit/core'
|
|
14
|
+
|
|
15
|
+
export type PortalDragOverlayProps = React.ComponentProps<typeof DragOverlay>
|
|
16
|
+
|
|
17
|
+
export function PortalDragOverlay(props: PortalDragOverlayProps) {
|
|
18
|
+
if (typeof document === 'undefined') return null
|
|
19
|
+
return createPortal(<DragOverlay {...props} />, document.body)
|
|
20
|
+
}
|
package/src/query-batch.ts
CHANGED
|
@@ -377,8 +377,11 @@ function batchForbidden(message?: string): boolean {
|
|
|
377
377
|
* or options read. Anything the batch refuses falls back to the original GET.
|
|
378
378
|
*/
|
|
379
379
|
export async function batchGet(api: ApiClient, url: string, config?: { params?: Record<string, unknown> }) {
|
|
380
|
+
// The fallback GET gets exactly the arguments the caller passed: no
|
|
381
|
+
// trailing `undefined` config reaching the host client.
|
|
382
|
+
const plainGet = () => (config === undefined ? api.get(url) : api.get(url, config))
|
|
380
383
|
const token = tokenForGet(url, config?.params)
|
|
381
|
-
if (!token) return
|
|
384
|
+
if (!token) return plainGet()
|
|
382
385
|
try {
|
|
383
386
|
const part = await loadQueryPart(api, token)
|
|
384
387
|
if (!part.success) {
|
|
@@ -391,13 +394,13 @@ export async function batchGet(api: ApiClient, url: string, config?: { params?:
|
|
|
391
394
|
err.response = { status: 403 }
|
|
392
395
|
throw err
|
|
393
396
|
}
|
|
394
|
-
return
|
|
397
|
+
return plainGet()
|
|
395
398
|
}
|
|
396
399
|
return { data: { success: true, data: part.data, meta: part.meta } }
|
|
397
400
|
} catch (err) {
|
|
398
401
|
const status = (err as { status?: number; response?: { status?: number } })?.status
|
|
399
402
|
?? (err as { response?: { status?: number } })?.response?.status
|
|
400
403
|
if (status === 403) throw err
|
|
401
|
-
return
|
|
404
|
+
return plainGet()
|
|
402
405
|
}
|
|
403
406
|
}
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
import * as React from 'react'
|
|
6
6
|
import {
|
|
7
7
|
DndContext,
|
|
8
|
-
DragOverlay,
|
|
9
8
|
PointerSensor,
|
|
10
9
|
closestCenter,
|
|
11
10
|
useSensor,
|
|
@@ -20,6 +19,7 @@ import {
|
|
|
20
19
|
verticalListSortingStrategy,
|
|
21
20
|
} from '@dnd-kit/sortable'
|
|
22
21
|
import { CSS } from '@dnd-kit/utilities'
|
|
22
|
+
import { PortalDragOverlay } from './portal-drag-overlay'
|
|
23
23
|
import {
|
|
24
24
|
ChevronDown,
|
|
25
25
|
ChevronRight,
|
|
@@ -733,13 +733,13 @@ export function SidebarLayoutManager({
|
|
|
733
733
|
) : (
|
|
734
734
|
renderList(tree, [])
|
|
735
735
|
)}
|
|
736
|
-
<
|
|
736
|
+
<PortalDragOverlay>
|
|
737
737
|
{activeLabel ? (
|
|
738
738
|
<div className="bg-background border-border rounded-md border px-3 py-1.5 text-sm shadow-lg">
|
|
739
739
|
{activeLabel}
|
|
740
740
|
</div>
|
|
741
741
|
) : null}
|
|
742
|
-
</
|
|
742
|
+
</PortalDragOverlay>
|
|
743
743
|
</DndContext>
|
|
744
744
|
|
|
745
745
|
<div className="mt-3 flex gap-2 px-2">
|
|
@@ -23,6 +23,12 @@ export interface UseFlipAnimationOptions {
|
|
|
23
23
|
easing?: MotionEasing | string
|
|
24
24
|
/** Off switch; the hook also stays still under prefers-reduced-motion. */
|
|
25
25
|
disabled?: boolean
|
|
26
|
+
/**
|
|
27
|
+
* Leave one element still on this change while still tracking where it is
|
|
28
|
+
* (e.g. the card a drag overlay is already carrying into place). Default:
|
|
29
|
+
* animate everything.
|
|
30
|
+
*/
|
|
31
|
+
shouldAnimate?: (el: HTMLElement, key: string) => boolean
|
|
26
32
|
}
|
|
27
33
|
|
|
28
34
|
const DEFAULT_SELECTOR = '[data-flip-key]'
|
|
@@ -54,12 +60,13 @@ export function useFlipAnimation(
|
|
|
54
60
|
duration = 'moderate',
|
|
55
61
|
easing = 'standard',
|
|
56
62
|
disabled = false,
|
|
63
|
+
shouldAnimate,
|
|
57
64
|
} = options
|
|
58
65
|
const positionsRef = useRef<Positions | null>(null)
|
|
59
66
|
const triggerRef = useRef(trigger)
|
|
60
|
-
const configRef = useRef({ selector, keyOf, scrollContainer })
|
|
67
|
+
const configRef = useRef({ selector, keyOf, scrollContainer, shouldAnimate })
|
|
61
68
|
useEffect(() => {
|
|
62
|
-
configRef.current = { selector, keyOf, scrollContainer }
|
|
69
|
+
configRef.current = { selector, keyOf, scrollContainer, shouldAnimate }
|
|
63
70
|
})
|
|
64
71
|
|
|
65
72
|
const measure = (): Positions | null => {
|
|
@@ -105,11 +112,16 @@ export function useFlipAnimation(
|
|
|
105
112
|
schedule()
|
|
106
113
|
document.addEventListener('pointerup', onInteraction, true)
|
|
107
114
|
document.addEventListener('keyup', onInteraction, true)
|
|
115
|
+
// Scrolling a list inside the root moves its items without a trigger
|
|
116
|
+
// change; without a fresh snapshot the next change would replay the scroll
|
|
117
|
+
// distance as movement. (Scroll does not bubble: listen in capture.)
|
|
118
|
+
document.addEventListener('scroll', onInteraction, true)
|
|
108
119
|
window.addEventListener('resize', schedule)
|
|
109
120
|
return () => {
|
|
110
121
|
if (timer) clearTimeout(timer)
|
|
111
122
|
document.removeEventListener('pointerup', onInteraction, true)
|
|
112
123
|
document.removeEventListener('keyup', onInteraction, true)
|
|
124
|
+
document.removeEventListener('scroll', onInteraction, true)
|
|
113
125
|
window.removeEventListener('resize', schedule)
|
|
114
126
|
}
|
|
115
127
|
}, [rootRef])
|
|
@@ -127,7 +139,7 @@ export function useFlipAnimation(
|
|
|
127
139
|
if (durationMs <= 0) return
|
|
128
140
|
const timing = easing in MOTION_EASING_KEYS ? motionEasing(easing as MotionEasing) : easing
|
|
129
141
|
|
|
130
|
-
const { selector: sel, keyOf: key } = configRef.current
|
|
142
|
+
const { selector: sel, keyOf: key, shouldAnimate: animates } = configRef.current
|
|
131
143
|
// A stale snapshot can report huge jumps; those would read as a glitch.
|
|
132
144
|
const maxJump = root.clientHeight > 0 ? root.clientHeight * 1.5 : Infinity
|
|
133
145
|
root.querySelectorAll<HTMLElement>(sel).forEach((el) => {
|
|
@@ -136,6 +148,7 @@ export function useFlipAnimation(
|
|
|
136
148
|
const from = before.get(k)
|
|
137
149
|
const to = after.get(k)
|
|
138
150
|
if (!to || typeof el.animate !== 'function') return
|
|
151
|
+
if (animates && !animates(el, k)) return
|
|
139
152
|
if (!from) {
|
|
140
153
|
// Entered with this change: fade in instead of popping.
|
|
141
154
|
el.animate([{ opacity: 0 }, { opacity: 1 }], { duration: durationMs, easing: timing })
|