@epoch32/visage 1.2.0 → 1.3.1
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/api/index.js +4 -4
- package/dist/charts/index.js +5 -5
- package/dist/devtools/index.js +7 -7
- package/dist/dom/index.js +3 -3
- package/dist/form/index.js +2 -2
- package/dist/{index-grhdtcb5.js → index-0qat98zs.js} +3 -3
- package/dist/{index-h6tjqk6e.js → index-1jcd8gjf.js} +28 -17
- package/dist/{index-6kfe1nr4.js → index-1yhbg34h.js} +41 -9
- package/dist/{index-9fvarkrx.js → index-3y73zgbk.js} +1 -1
- package/dist/{index-y9bq7h9g.js → index-5jhzwtdz.js} +2 -2
- package/dist/{index-a4xns6cc.js → index-65j7a9et.js} +1 -1
- package/dist/{index-dyvgtp43.js → index-9bez1tmf.js} +2 -2
- package/dist/{index-b5wc752a.js → index-aagpn02x.js} +346 -5
- package/dist/{index-df5e614s.js → index-awcrz19g.js} +1 -1
- package/dist/{index-8x8xz80v.js → index-b7emqmej.js} +3 -3
- package/dist/{index-061jf06a.js → index-cgepvvd4.js} +1 -1
- package/dist/{index-1xtrtz1t.js → index-dmhmde2z.js} +2 -2
- package/dist/{index-0madxn60.js → index-enc8cmea.js} +1 -1
- package/dist/{index-js199er4.js → index-g1drdx7k.js} +12 -1
- package/dist/{index-zr6cvset.js → index-ptj8rt6b.js} +5 -5
- package/dist/{index-8czhpea8.js → index-x6whpv54.js} +30 -19
- package/dist/{index-5s255b3w.js → index-xby428n5.js} +23 -22
- package/dist/{index-x2rs3vab.js → index-y3nprgn7.js} +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +32 -18
- package/dist/jsx-runtime/index.js +4 -4
- package/dist/query/index.js +3 -3
- package/dist/router/index.js +5 -5
- package/dist/rpc/index.js +4 -4
- package/dist/server/index.js +2 -2
- package/dist/signals/index.js +1 -1
- package/dist/signals/system.d.ts +2 -0
- package/dist/style/index.js +6 -6
- package/dist/template/index.js +2 -2
- package/dist/ui/bottom-bar/action.d.ts +3 -0
- package/dist/ui/bottom-bar/badge.d.ts +3 -0
- package/dist/ui/bottom-bar/context.d.ts +3 -0
- package/dist/ui/bottom-bar/index.d.ts +19 -0
- package/dist/ui/bottom-bar/item.d.ts +3 -0
- package/dist/ui/bottom-bar/root.d.ts +3 -0
- package/dist/ui/bottom-bar/separator.d.ts +3 -0
- package/dist/ui/bottom-bar/types.d.ts +72 -0
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.js +21 -7
- package/dist/virtual/index.js +5 -5
- package/package.json +1 -1
- package/src/dom/control-flow.ts +31 -21
- package/src/form/form.ts +29 -17
- package/src/index.ts +14 -0
- package/src/signals/effect.ts +2 -2
- package/src/signals/scope.ts +10 -8
- package/src/signals/store.ts +32 -1
- package/src/signals/system.ts +8 -0
- package/src/template/template.ts +12 -1
- package/src/ui/bottom-bar/action.tsx +60 -0
- package/src/ui/bottom-bar/badge.tsx +51 -0
- package/src/ui/bottom-bar/context.ts +8 -0
- package/src/ui/bottom-bar/index.ts +29 -0
- package/src/ui/bottom-bar/item.tsx +119 -0
- package/src/ui/bottom-bar/root.tsx +148 -0
- package/src/ui/bottom-bar/separator.tsx +26 -0
- package/src/ui/bottom-bar/types.ts +79 -0
- package/src/ui/index.ts +16 -0
- package/src/virtual/virtualizer.ts +31 -18
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type { Child } from "../../dom/render";
|
|
2
|
+
import { signal } from "../../signals";
|
|
3
|
+
import { createControllableSignal, createId } from "../utils";
|
|
4
|
+
import { BottomBarContext } from "./context";
|
|
5
|
+
import { BottomBarItem } from "./item";
|
|
6
|
+
import type { BottomBarContextValue, BottomBarProps } from "./types";
|
|
7
|
+
|
|
8
|
+
export function BottomBarRoot(props: BottomBarProps): Child {
|
|
9
|
+
const [value, setValue] = createControllableSignal<string>({
|
|
10
|
+
value: props.value,
|
|
11
|
+
defaultValue:
|
|
12
|
+
props.defaultValue ?? (props.items && props.items.length > 0 ? props.items[0]?.value : ""),
|
|
13
|
+
onChange: props.onValueChange
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const baseId = createId("bottom-bar");
|
|
17
|
+
const variant = props.variant ?? "floating";
|
|
18
|
+
const hideOnScroll = props.hideOnScroll ?? false;
|
|
19
|
+
const threshold = props.scrollThreshold ?? 20;
|
|
20
|
+
|
|
21
|
+
const isVisible = signal(true);
|
|
22
|
+
|
|
23
|
+
if (hideOnScroll && typeof window !== "undefined") {
|
|
24
|
+
let lastScrollY = window.scrollY || 0;
|
|
25
|
+
const onScroll = () => {
|
|
26
|
+
const currentScrollY = window.scrollY || 0;
|
|
27
|
+
const diff = currentScrollY - lastScrollY;
|
|
28
|
+
if (Math.abs(diff) < 5) return;
|
|
29
|
+
|
|
30
|
+
if (currentScrollY > threshold && diff > 0) {
|
|
31
|
+
if (isVisible()) isVisible.set(false);
|
|
32
|
+
} else if (diff < 0) {
|
|
33
|
+
if (!isVisible()) isVisible.set(true);
|
|
34
|
+
}
|
|
35
|
+
lastScrollY = Math.max(0, currentScrollY);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
window.addEventListener("scroll", onScroll, { passive: true });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const ctx: BottomBarContextValue = {
|
|
42
|
+
value,
|
|
43
|
+
setValue,
|
|
44
|
+
baseId,
|
|
45
|
+
variant
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const {
|
|
49
|
+
as: Tag = "nav",
|
|
50
|
+
value: _v,
|
|
51
|
+
defaultValue: _dv,
|
|
52
|
+
onValueChange: _ovc,
|
|
53
|
+
variant: _var,
|
|
54
|
+
hideOnScroll: _hos,
|
|
55
|
+
scrollThreshold: _st,
|
|
56
|
+
items,
|
|
57
|
+
itemClass,
|
|
58
|
+
itemStyle,
|
|
59
|
+
children,
|
|
60
|
+
class: className,
|
|
61
|
+
style,
|
|
62
|
+
...rest
|
|
63
|
+
} = props;
|
|
64
|
+
|
|
65
|
+
const defaultFloatingStyle = {
|
|
66
|
+
position: "fixed",
|
|
67
|
+
bottom: "max(16px, env(safe-area-inset-bottom, 16px))",
|
|
68
|
+
left: "50%",
|
|
69
|
+
zIndex: 50,
|
|
70
|
+
display: "inline-flex",
|
|
71
|
+
alignItems: "center",
|
|
72
|
+
gap: "6px",
|
|
73
|
+
padding: "6px 10px",
|
|
74
|
+
borderRadius: "9999px",
|
|
75
|
+
backgroundColor: "rgba(18, 18, 24, 0.88)",
|
|
76
|
+
backdropFilter: "blur(20px)",
|
|
77
|
+
WebkitBackdropFilter: "blur(20px)",
|
|
78
|
+
border: "1px solid rgba(255, 255, 255, 0.1)",
|
|
79
|
+
boxShadow: "0 10px 30px -5px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.05)",
|
|
80
|
+
transform: () =>
|
|
81
|
+
isVisible() ? "translateX(-50%) translateY(0)" : "translateX(-50%) translateY(160%)",
|
|
82
|
+
opacity: () => (isVisible() ? 1 : 0),
|
|
83
|
+
pointerEvents: () => (isVisible() ? "auto" : "none"),
|
|
84
|
+
transition: "transform 0.3s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.25s ease",
|
|
85
|
+
maxWidth: "calc(100vw - 32px)",
|
|
86
|
+
boxSizing: "border-box"
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const defaultDockedStyle = {
|
|
90
|
+
position: "fixed",
|
|
91
|
+
bottom: 0,
|
|
92
|
+
left: 0,
|
|
93
|
+
right: 0,
|
|
94
|
+
zIndex: 50,
|
|
95
|
+
display: "flex",
|
|
96
|
+
justifyContent: "space-around",
|
|
97
|
+
alignItems: "center",
|
|
98
|
+
paddingTop: "8px",
|
|
99
|
+
paddingBottom: "max(10px, env(safe-area-inset-bottom, 10px))",
|
|
100
|
+
paddingLeft: "12px",
|
|
101
|
+
paddingRight: "12px",
|
|
102
|
+
backgroundColor: "rgba(18, 18, 24, 0.94)",
|
|
103
|
+
backdropFilter: "blur(20px)",
|
|
104
|
+
WebkitBackdropFilter: "blur(20px)",
|
|
105
|
+
borderTop: "1px solid rgba(255, 255, 255, 0.1)",
|
|
106
|
+
boxShadow: "0 -4px 20px rgba(0, 0, 0, 0.35)",
|
|
107
|
+
transform: () => (isVisible() ? "translateY(0)" : "translateY(110%)"),
|
|
108
|
+
opacity: () => (isVisible() ? 1 : 0),
|
|
109
|
+
pointerEvents: () => (isVisible() ? "auto" : "none"),
|
|
110
|
+
transition: "transform 0.3s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.25s ease",
|
|
111
|
+
boxSizing: "border-box"
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const computedDefaultStyle = variant === "floating" ? defaultFloatingStyle : defaultDockedStyle;
|
|
115
|
+
const combinedStyle = style ? { ...computedDefaultStyle, ...style } : computedDefaultStyle;
|
|
116
|
+
|
|
117
|
+
return BottomBarContext.Provider({
|
|
118
|
+
value: ctx,
|
|
119
|
+
children: () => (
|
|
120
|
+
<Tag
|
|
121
|
+
role="toolbar"
|
|
122
|
+
aria-label="Mobile toolbar"
|
|
123
|
+
data-variant={variant}
|
|
124
|
+
data-state={() => (isVisible() ? "visible" : "hidden")}
|
|
125
|
+
class={`visage-bottom-bar visage-bottom-bar-${variant} ${className ?? ""}`.trim()}
|
|
126
|
+
style={combinedStyle}
|
|
127
|
+
{...rest}
|
|
128
|
+
>
|
|
129
|
+
{items
|
|
130
|
+
? items.map((item) => (
|
|
131
|
+
<BottomBarItem
|
|
132
|
+
value={item.value}
|
|
133
|
+
icon={item.icon}
|
|
134
|
+
label={item.label}
|
|
135
|
+
badge={item.badge}
|
|
136
|
+
disabled={item.disabled}
|
|
137
|
+
onClick={item.onClick}
|
|
138
|
+
class={item.class ?? itemClass}
|
|
139
|
+
style={item.style ?? itemStyle}
|
|
140
|
+
/>
|
|
141
|
+
))
|
|
142
|
+
: typeof children === "function"
|
|
143
|
+
? children()
|
|
144
|
+
: children}
|
|
145
|
+
</Tag>
|
|
146
|
+
)
|
|
147
|
+
});
|
|
148
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Child } from "../../dom/render";
|
|
2
|
+
import type { BottomBarSeparatorProps } from "./types";
|
|
3
|
+
|
|
4
|
+
export function BottomBarSeparator(props: BottomBarSeparatorProps): Child {
|
|
5
|
+
const { as: Tag = "div", class: className, style, ...rest } = props;
|
|
6
|
+
|
|
7
|
+
const defaultSeparatorStyle = {
|
|
8
|
+
width: "1px",
|
|
9
|
+
height: "22px",
|
|
10
|
+
backgroundColor: "rgba(255, 255, 255, 0.12)",
|
|
11
|
+
margin: "0 2px",
|
|
12
|
+
flexShrink: 0
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const combinedStyle = style ? { ...defaultSeparatorStyle, ...style } : defaultSeparatorStyle;
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<Tag
|
|
19
|
+
role="separator"
|
|
20
|
+
aria-orientation="vertical"
|
|
21
|
+
class={`visage-bottom-bar-separator ${className ?? ""}`.trim()}
|
|
22
|
+
style={combinedStyle}
|
|
23
|
+
{...rest}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { Child } from "../../dom/render";
|
|
2
|
+
import type { ReadonlySignal } from "../../signals";
|
|
3
|
+
|
|
4
|
+
export interface BottomBarContextValue {
|
|
5
|
+
value: ReadonlySignal<string>;
|
|
6
|
+
setValue: (value: string) => void;
|
|
7
|
+
baseId: string;
|
|
8
|
+
variant: "floating" | "docked";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface BottomBarItemConfig {
|
|
12
|
+
value: string;
|
|
13
|
+
label?: Child | (() => Child);
|
|
14
|
+
icon?: Child | (() => Child);
|
|
15
|
+
badge?: Child | number | string | (() => Child | number | string | undefined);
|
|
16
|
+
disabled?: boolean | (() => boolean);
|
|
17
|
+
onClick?: (e: MouseEvent) => void;
|
|
18
|
+
class?: any;
|
|
19
|
+
style?: any;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface BottomBarProps {
|
|
23
|
+
value?: () => string | undefined;
|
|
24
|
+
defaultValue?: string;
|
|
25
|
+
onValueChange?: (value: string) => void;
|
|
26
|
+
variant?: "floating" | "docked";
|
|
27
|
+
hideOnScroll?: boolean;
|
|
28
|
+
scrollThreshold?: number;
|
|
29
|
+
items?: BottomBarItemConfig[];
|
|
30
|
+
itemClass?: any;
|
|
31
|
+
itemStyle?: any;
|
|
32
|
+
as?: string;
|
|
33
|
+
class?: any;
|
|
34
|
+
style?: any;
|
|
35
|
+
children?: Child | (() => Child);
|
|
36
|
+
[key: string]: any;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface BottomBarItemProps {
|
|
40
|
+
value: string;
|
|
41
|
+
icon?: Child | (() => Child);
|
|
42
|
+
label?: Child | (() => Child);
|
|
43
|
+
badge?: Child | number | string | (() => Child | number | string | undefined);
|
|
44
|
+
disabled?: boolean | (() => boolean);
|
|
45
|
+
onClick?: (e: MouseEvent) => void;
|
|
46
|
+
as?: string;
|
|
47
|
+
class?: any;
|
|
48
|
+
style?: any;
|
|
49
|
+
children?: Child | (() => Child);
|
|
50
|
+
[key: string]: any;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface BottomBarActionProps {
|
|
54
|
+
icon?: Child | (() => Child);
|
|
55
|
+
label?: string;
|
|
56
|
+
disabled?: boolean | (() => boolean);
|
|
57
|
+
onClick?: (e: MouseEvent) => void;
|
|
58
|
+
as?: string;
|
|
59
|
+
class?: any;
|
|
60
|
+
style?: any;
|
|
61
|
+
children?: Child | (() => Child);
|
|
62
|
+
[key: string]: any;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface BottomBarBadgeProps {
|
|
66
|
+
count?: Child | number | string | (() => Child | number | string | undefined);
|
|
67
|
+
as?: string;
|
|
68
|
+
class?: any;
|
|
69
|
+
style?: any;
|
|
70
|
+
children?: Child | (() => Child);
|
|
71
|
+
[key: string]: any;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface BottomBarSeparatorProps {
|
|
75
|
+
as?: string;
|
|
76
|
+
class?: any;
|
|
77
|
+
style?: any;
|
|
78
|
+
[key: string]: any;
|
|
79
|
+
}
|
package/src/ui/index.ts
CHANGED
|
@@ -10,6 +10,22 @@ export {
|
|
|
10
10
|
useAccordionContext,
|
|
11
11
|
useAccordionItemContext
|
|
12
12
|
} from "./accordion";
|
|
13
|
+
export {
|
|
14
|
+
BottomBar,
|
|
15
|
+
BottomBarAction,
|
|
16
|
+
type BottomBarActionProps,
|
|
17
|
+
BottomBarBadge,
|
|
18
|
+
type BottomBarBadgeProps,
|
|
19
|
+
type BottomBarContextValue,
|
|
20
|
+
BottomBarItem,
|
|
21
|
+
type BottomBarItemConfig,
|
|
22
|
+
type BottomBarItemProps,
|
|
23
|
+
type BottomBarProps,
|
|
24
|
+
BottomBarRoot,
|
|
25
|
+
BottomBarSeparator,
|
|
26
|
+
type BottomBarSeparatorProps,
|
|
27
|
+
useBottomBarContext
|
|
28
|
+
} from "./bottom-bar";
|
|
13
29
|
export {
|
|
14
30
|
Command,
|
|
15
31
|
CommandDialog,
|
|
@@ -56,30 +56,41 @@ export function createVirtualizer(options: VirtualizerOptions): Virtualizer {
|
|
|
56
56
|
return getItemHeight(index);
|
|
57
57
|
};
|
|
58
58
|
|
|
59
|
-
//
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
for (let i = 0; i < count; i++) {
|
|
67
|
-
total += getItemSize(i);
|
|
68
|
-
}
|
|
69
|
-
return total;
|
|
70
|
-
});
|
|
59
|
+
// Cached prefix offsets for O(1) offset lookups and true O(log N) binary search
|
|
60
|
+
const offsetsCache: number[] = [0];
|
|
61
|
+
let offsetsValidUpTo = 0;
|
|
62
|
+
|
|
63
|
+
const invalidateOffsetsFrom = (index: number) => {
|
|
64
|
+
offsetsValidUpTo = Math.min(offsetsValidUpTo, index);
|
|
65
|
+
};
|
|
71
66
|
|
|
72
67
|
const getOffsetForIndex = (targetIndex: number): number => {
|
|
68
|
+
if (targetIndex <= 0) return 0;
|
|
73
69
|
if (typeof options.itemHeight === "number" && dynamicSizeCache.size === 0) {
|
|
74
70
|
return targetIndex * options.itemHeight;
|
|
75
71
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
offset += getItemSize(i);
|
|
72
|
+
if (targetIndex <= offsetsValidUpTo) {
|
|
73
|
+
return offsetsCache[targetIndex]!;
|
|
79
74
|
}
|
|
80
|
-
|
|
75
|
+
for (let i = offsetsValidUpTo; i < targetIndex; i++) {
|
|
76
|
+
offsetsCache[i + 1] = (offsetsCache[i] ?? 0) + getItemSize(i);
|
|
77
|
+
}
|
|
78
|
+
offsetsValidUpTo = targetIndex;
|
|
79
|
+
return offsetsCache[targetIndex]!;
|
|
81
80
|
};
|
|
82
81
|
|
|
82
|
+
const measurementVersion = signal(0, { internal: true });
|
|
83
|
+
|
|
84
|
+
// Compute prefix offsets for fast range calculation
|
|
85
|
+
const totalSize = computed(() => {
|
|
86
|
+
measurementVersion();
|
|
87
|
+
const count = getCount();
|
|
88
|
+
if (typeof options.itemHeight === "number" && dynamicSizeCache.size === 0) {
|
|
89
|
+
return count * options.itemHeight;
|
|
90
|
+
}
|
|
91
|
+
return getOffsetForIndex(count);
|
|
92
|
+
});
|
|
93
|
+
|
|
83
94
|
const findIndexAtOffset = (offset: number): number => {
|
|
84
95
|
const count = getCount();
|
|
85
96
|
if (count === 0) return 0;
|
|
@@ -110,12 +121,14 @@ export function createVirtualizer(options: VirtualizerOptions): Virtualizer {
|
|
|
110
121
|
};
|
|
111
122
|
|
|
112
123
|
const startIndex = computed(() => {
|
|
124
|
+
measurementVersion();
|
|
113
125
|
const offset = scrollOffset();
|
|
114
126
|
const rawStart = findIndexAtOffset(offset);
|
|
115
127
|
return Math.max(0, rawStart - overscan);
|
|
116
128
|
});
|
|
117
129
|
|
|
118
130
|
const endIndex = computed(() => {
|
|
131
|
+
measurementVersion();
|
|
119
132
|
const count = getCount();
|
|
120
133
|
if (count === 0) return 0;
|
|
121
134
|
const endOffset = scrollOffset() + (containerSize() || 500);
|
|
@@ -255,8 +268,8 @@ export function createVirtualizer(options: VirtualizerOptions): Virtualizer {
|
|
|
255
268
|
: element.getBoundingClientRect().height;
|
|
256
269
|
if (measuredSize > 0 && dynamicSizeCache.get(index) !== measuredSize) {
|
|
257
270
|
dynamicSizeCache.set(index, measuredSize);
|
|
258
|
-
|
|
259
|
-
|
|
271
|
+
invalidateOffsetsFrom(index);
|
|
272
|
+
measurementVersion.update((v) => v + 1);
|
|
260
273
|
}
|
|
261
274
|
};
|
|
262
275
|
|