@passui/react 0.1.44 → 0.1.46
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/README.md +57 -46
- package/dist/types/src/components/accordion/accordion.d.ts +1 -1
- package/dist/types/src/components/breadcrumb/breadcrumb.d.ts +1 -1
- package/dist/types/src/components/inline-disclosure/inline-disclosure.d.ts +1 -1
- package/dist/types/src/components/popover/anchored-surface.d.ts +5 -1
- package/dist/types/src/components/popover/index.d.ts +41 -0
- package/dist/types/src/components/popover/popover-content.d.ts +32 -0
- package/dist/types/src/components/popover/popover-root.d.ts +22 -0
- package/dist/types/src/components/popover/popover-trigger.d.ts +23 -0
- package/dist/types/src/components/stepper/rolling-digit.d.ts +1 -1
- package/dist/types/src/components/table/table.d.ts +1 -1
- package/dist/types/src/components/view-controller/view-controller.d.ts +2 -2
- package/dist/types/src/index.d.ts +1 -1
- package/dist/types/src/machines/popover.machine.d.ts +30 -0
- package/package.json +307 -307
- package/src/appearance/index.ts +2 -2
- package/src/appearance/platform.tsx +76 -60
- package/src/appearance/theme.tsx +409 -377
- package/src/components/accordion/accordion.tsx +58 -49
- package/src/components/ambient/ambient.tsx +25 -25
- package/src/components/ambient/index.ts +1 -1
- package/src/components/aspect-ratio/aspect-ratio.tsx +21 -16
- package/src/components/breadcrumb/breadcrumb.tsx +63 -57
- package/src/components/button/button-styles.ts +39 -32
- package/src/components/button/button.tsx +91 -84
- package/src/components/button/icon-button.tsx +27 -25
- package/src/components/button/pending-spinner.tsx +8 -8
- package/src/components/button/use-pending-indicator.ts +11 -11
- package/src/components/chip/chip.tsx +35 -30
- package/src/components/code-block/code-block.tsx +40 -39
- package/src/components/combo-box/combo-box.tsx +5 -5
- package/src/components/command/command.tsx +124 -94
- package/src/components/compact-date-picker/calendar-month.ts +75 -53
- package/src/components/compact-date-picker/compact-date-picker.tsx +308 -289
- package/src/components/compact-time-picker/compact-time-picker.tsx +288 -281
- package/src/components/compact-time-picker/time-value.ts +32 -29
- package/src/components/content-unavailable-view/content-unavailable-view.tsx +64 -48
- package/src/components/content-view/content-view.tsx +168 -159
- package/src/components/content-view/index.ts +1 -1
- package/src/components/context-menu/conceal-menu-source.ts +25 -25
- package/src/components/context-menu/context-menu-host.tsx +439 -398
- package/src/components/context-menu/context-menu-store.ts +125 -123
- package/src/components/context-menu/menu-navigation.ts +25 -18
- package/src/components/context-menu/menu-position.ts +33 -29
- package/src/components/context-menu/menu-reveal.ts +32 -32
- package/src/components/context-menu/use-context-menu.ts +63 -56
- package/src/components/conversation/conversation.tsx +265 -263
- package/src/components/conversation/index.ts +1 -1
- package/src/components/date-picker/date-picker.tsx +95 -94
- package/src/components/date-picker/date-value.ts +20 -10
- package/src/components/dialog/dialog-host.tsx +105 -103
- package/src/components/dialog/dialog-store.ts +37 -36
- package/src/components/dialog/dialog.tsx +192 -179
- package/src/components/dialog/index.ts +1 -1
- package/src/components/disclosure-group/disclosure-group.tsx +93 -86
- package/src/components/divider/divider.tsx +24 -22
- package/src/components/inline-disclosure/inline-disclosure.tsx +40 -36
- package/src/components/keyboard-shortcut/keyboard-shortcut.tsx +23 -19
- package/src/components/labeled-content/labeled-content.tsx +68 -68
- package/src/components/locale-switcher/index.ts +1 -1
- package/src/components/locale-switcher/locale-switcher.tsx +279 -251
- package/src/components/menu/menu.tsx +75 -58
- package/src/components/morph/index.ts +2 -2
- package/src/components/morph/morph.tsx +227 -201
- package/src/components/morph/use-morph-disclosure.ts +19 -19
- package/src/components/one-time-code/one-time-code.tsx +216 -200
- package/src/components/picker/picker-trigger.ts +16 -13
- package/src/components/picker/picker.tsx +371 -345
- package/src/components/popover/anchored-surface.tsx +253 -229
- package/src/components/popover/index.ts +50 -0
- package/src/components/popover/popover-content.tsx +334 -0
- package/src/components/popover/popover-fit.ts +58 -53
- package/src/components/popover/popover-root.tsx +97 -0
- package/src/components/popover/popover-trigger.tsx +71 -0
- package/src/components/profile-image/profile-image.tsx +80 -64
- package/src/components/progress-view/progress-view.tsx +61 -59
- package/src/components/progress-view/use-liquid-progress.ts +38 -38
- package/src/components/radio-group/radio-group.tsx +71 -68
- package/src/components/search-field/expanding-search-field.tsx +191 -177
- package/src/components/search-field/search-field.tsx +59 -50
- package/src/components/search-list/search-list.tsx +246 -236
- package/src/components/section/section.tsx +58 -58
- package/src/components/secure-field/secure-field.tsx +3 -3
- package/src/components/segmented-control/segmented-control.tsx +136 -136
- package/src/components/sheet/modal-environment.ts +71 -64
- package/src/components/sheet/sheet-portal.tsx +58 -53
- package/src/components/sheet/sheet.tsx +146 -141
- package/src/components/sheet/use-sheet-focus.ts +97 -82
- package/src/components/sidebar/sidebar.tsx +78 -78
- package/src/components/skeleton/skeleton.tsx +30 -21
- package/src/components/slider/slider.tsx +113 -104
- package/src/components/slosh-slider/liquid-step.ts +32 -31
- package/src/components/slosh-slider/slosh-slider.tsx +265 -228
- package/src/components/stat-tile/index.ts +1 -1
- package/src/components/stat-tile/stat-tile.tsx +79 -68
- package/src/components/status-indicator/status-indicator.tsx +40 -37
- package/src/components/stepper/rolling-digit.tsx +82 -68
- package/src/components/stepper/stepper.tsx +122 -113
- package/src/components/swatch-picker/swatch-picker.tsx +120 -120
- package/src/components/tab-bar/tab-bar.tsx +149 -148
- package/src/components/tab-view/tab-view.tsx +140 -137
- package/src/components/table/table.tsx +117 -98
- package/src/components/text-editor/text-editor.tsx +34 -29
- package/src/components/text-field/text-field.tsx +182 -180
- package/src/components/toast/toast-store.ts +127 -115
- package/src/components/toast/toaster.tsx +361 -347
- package/src/components/toggle/toggle.tsx +297 -282
- package/src/components/tooltip/tooltip-host.tsx +157 -150
- package/src/components/view-controller/index.ts +1 -1
- package/src/components/view-controller/view-controller.tsx +64 -53
- package/src/components/wake-slider/wake-slider.tsx +220 -205
- package/src/hooks/scroll/minimize-decision.ts +11 -7
- package/src/hooks/use-anchored-disclosure.ts +19 -19
- package/src/hooks/use-anchored-placement.ts +29 -25
- package/src/hooks/use-dismiss.ts +33 -29
- package/src/hooks/use-floating-dismiss.ts +24 -22
- package/src/hooks/use-media-query.ts +67 -60
- package/src/hooks/use-minimize-on-scroll.ts +69 -60
- package/src/hooks/use-visual-viewport.ts +63 -63
- package/src/hooks/viewport/types.ts +16 -16
- package/src/hooks/viewport/viewport-metrics.ts +16 -10
- package/src/icons/icons.tsx +440 -328
- package/src/index.ts +91 -91
- package/src/lib/anchored-placement.ts +70 -65
- package/src/lib/class-names.ts +14 -13
- package/src/lib/clipboard.ts +8 -7
- package/src/lib/date.ts +61 -60
- package/src/lib/floating-layers.ts +39 -24
- package/src/lib/haptics.ts +10 -10
- package/src/lib/language.ts +9 -9
- package/src/lib/range-value.ts +27 -21
- package/src/lib/screen-edges.ts +33 -30
- package/src/lib/shapes.ts +38 -34
- package/src/machines/popover.machine.ts +51 -0
- package/src/motion/transitions.ts +46 -43
- package/src/styles/code-block.css +69 -43
- package/src/styles/motion-controls.css +393 -191
- package/src/tokens.ts +1 -1
- package/dist/types/src/components/popover/popover.d.ts +0 -21
- package/src/components/popover/popover.tsx +0 -322
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { TextField, type TextFieldProps } from "../text-field/text-field"
|
|
1
|
+
import { TextField, type TextFieldProps } from "../text-field/text-field";
|
|
2
2
|
|
|
3
3
|
/** TextField's native input props, with the password type managed by SecureField. */
|
|
4
|
-
export type SecureFieldProps = Omit<TextFieldProps, "type"
|
|
4
|
+
export type SecureFieldProps = Omit<TextFieldProps, "type">;
|
|
5
5
|
|
|
6
6
|
export function SecureField(props: SecureFieldProps) {
|
|
7
|
-
|
|
7
|
+
return <TextField {...props} type="password" />;
|
|
8
8
|
}
|
|
@@ -1,46 +1,46 @@
|
|
|
1
|
-
import { motion, useReducedMotion } from "motion/react"
|
|
2
|
-
import { useId, useLayoutEffect, useRef, useState } from "react"
|
|
3
|
-
import { cn } from "../../lib/class-names"
|
|
4
|
-
import { haptic } from "../../lib/haptics"
|
|
5
|
-
import { spring } from "../../motion/transitions"
|
|
6
|
-
import { Picker } from "../picker/picker"
|
|
1
|
+
import { motion, useReducedMotion } from "motion/react";
|
|
2
|
+
import { useId, useLayoutEffect, useRef, useState } from "react";
|
|
3
|
+
import { cn } from "../../lib/class-names";
|
|
4
|
+
import { haptic } from "../../lib/haptics";
|
|
5
|
+
import { spring } from "../../motion/transitions";
|
|
6
|
+
import { Picker } from "../picker/picker";
|
|
7
7
|
|
|
8
|
-
export type SegmentedControlSize = "sm" | "md" | "lg"
|
|
8
|
+
export type SegmentedControlSize = "sm" | "md" | "lg";
|
|
9
9
|
|
|
10
10
|
const segmentPaddings: Record<SegmentedControlSize, string> = {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
11
|
+
sm: "p-[2px]",
|
|
12
|
+
md: "p-[3px]",
|
|
13
|
+
lg: "p-[4px]",
|
|
14
|
+
};
|
|
15
15
|
const segmentLabelClasses: Record<SegmentedControlSize, string> = {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
16
|
+
sm: "min-w-[52px] px-2 py-0.5 text-caption2 whitespace-nowrap",
|
|
17
|
+
md: "min-w-[64px] px-3 py-1 text-footnote whitespace-nowrap",
|
|
18
|
+
lg: "min-w-[80px] px-4 py-1.5 text-subheadline whitespace-nowrap",
|
|
19
|
+
};
|
|
20
20
|
const segmentRadius: Record<SegmentedControlSize, string> = {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
21
|
+
sm: "rounded-sm",
|
|
22
|
+
md: "rounded-md",
|
|
23
|
+
lg: "rounded-lg",
|
|
24
|
+
};
|
|
25
25
|
const fallbackPickerSizes: Record<SegmentedControlSize, string> = {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
26
|
+
sm: "rounded-md py-1 text-caption2 font-semibold",
|
|
27
|
+
md: "rounded-lg py-1.5 text-footnote font-semibold",
|
|
28
|
+
lg: "rounded-lg py-2 text-subheadline font-semibold",
|
|
29
|
+
};
|
|
30
30
|
|
|
31
31
|
export interface SegmentedControlProps<T extends string> {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
32
|
+
/** Value of the selected segment. */
|
|
33
|
+
value: T;
|
|
34
|
+
/** The choices, in display order. */
|
|
35
|
+
options: ReadonlyArray<{ value: T; label: string }>;
|
|
36
|
+
/** Called with the selected segment's value. */
|
|
37
|
+
onChange: (value: NoInfer<T>) => void;
|
|
38
|
+
/** Accessible name for the group and its compact picker. */
|
|
39
|
+
label: string;
|
|
40
|
+
/** Custom classes for the control container. */
|
|
41
|
+
className?: string;
|
|
42
|
+
/** Segment padding, label text scale and min-width. @default "md" */
|
|
43
|
+
size?: SegmentedControlSize;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/**
|
|
@@ -50,108 +50,108 @@ export interface SegmentedControlProps<T extends string> {
|
|
|
50
50
|
* squeezing or spilling its text.
|
|
51
51
|
*/
|
|
52
52
|
export function SegmentedControl<T extends string>({
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
53
|
+
value,
|
|
54
|
+
options,
|
|
55
|
+
onChange,
|
|
56
|
+
label,
|
|
57
|
+
className,
|
|
58
|
+
size = "md",
|
|
59
59
|
}: SegmentedControlProps<T>) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
60
|
+
const id = useId();
|
|
61
|
+
const reducedMotion = useReducedMotion();
|
|
62
|
+
const frame = useRef<HTMLDivElement>(null);
|
|
63
|
+
const measure = useRef<HTMLDivElement>(null);
|
|
64
|
+
const [fits, setFits] = useState(true);
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
66
|
+
useLayoutEffect(() => {
|
|
67
|
+
const box = frame.current;
|
|
68
|
+
const natural = measure.current;
|
|
69
|
+
if (!box || !natural) return;
|
|
70
|
+
const check = () => setFits(natural.offsetWidth <= box.clientWidth + 0.5);
|
|
71
|
+
check();
|
|
72
|
+
const observer = new ResizeObserver(check);
|
|
73
|
+
observer.observe(box);
|
|
74
|
+
observer.observe(natural);
|
|
75
|
+
return () => observer.disconnect();
|
|
76
|
+
}, []);
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
78
|
+
return (
|
|
79
|
+
<div ref={frame} className={cn("relative w-full min-w-0", className)}>
|
|
80
|
+
{/* The control at its natural size, to know whether it fits. */}
|
|
81
|
+
<div
|
|
82
|
+
ref={measure}
|
|
83
|
+
aria-hidden="true"
|
|
84
|
+
className={cn(
|
|
85
|
+
"pointer-events-none invisible absolute top-0 flex w-max font-semibold",
|
|
86
|
+
segmentPaddings[size],
|
|
87
|
+
segmentLabelClasses[size].replace(/min-w-\[[^\]]+\]\s*/, ""),
|
|
88
|
+
)}
|
|
89
|
+
>
|
|
90
|
+
{options.map((option) => (
|
|
91
|
+
<span key={option.value} className={segmentLabelClasses[size]}>
|
|
92
|
+
{option.label}
|
|
93
|
+
</span>
|
|
94
|
+
))}
|
|
95
|
+
</div>
|
|
96
|
+
{fits ? (
|
|
97
|
+
<fieldset
|
|
98
|
+
className={cn(
|
|
99
|
+
"bg-fill flex w-full rounded-lg shadow-[inset_0_1px_2px_rgb(40_30_0/0.06)]",
|
|
100
|
+
segmentPaddings[size],
|
|
101
|
+
)}
|
|
102
|
+
>
|
|
103
|
+
<legend className="sr-only">{label}</legend>
|
|
104
|
+
{options.map((option) => {
|
|
105
|
+
const selected = option.value === value;
|
|
106
|
+
return (
|
|
107
|
+
<label
|
|
108
|
+
key={option.value}
|
|
109
|
+
className={cn(
|
|
110
|
+
"has-[:focus-visible]:ring-accent/60 relative isolate flex-1 cursor-pointer text-center whitespace-nowrap transition-colors has-[:focus-visible]:ring-2",
|
|
111
|
+
segmentLabelClasses[size].replace("whitespace-nowrap", ""),
|
|
112
|
+
segmentRadius[size],
|
|
113
|
+
selected
|
|
114
|
+
? "text-label font-semibold"
|
|
115
|
+
: "text-label-secondary hover:text-label font-medium",
|
|
116
|
+
)}
|
|
117
|
+
>
|
|
118
|
+
<input
|
|
119
|
+
type="radio"
|
|
120
|
+
name={id}
|
|
121
|
+
value={option.value}
|
|
122
|
+
checked={selected}
|
|
123
|
+
onChange={() => {
|
|
124
|
+
haptic("selection");
|
|
125
|
+
onChange(option.value);
|
|
126
|
+
}}
|
|
127
|
+
className="sr-only"
|
|
128
|
+
/>
|
|
129
|
+
{selected && (
|
|
130
|
+
<motion.span
|
|
131
|
+
layoutId={`segment-${id}`}
|
|
132
|
+
className={cn(
|
|
133
|
+
"bg-elevated absolute inset-0 -z-10 shadow-[0_0_0_0.5px_rgb(0_0_0/0.06),0_1px_2px_rgb(0_0_0/0.08),0_3px_8px_-2px_rgb(0_0_0/0.10)]",
|
|
134
|
+
segmentRadius[size],
|
|
135
|
+
)}
|
|
136
|
+
transition={reducedMotion ? { duration: 0 } : spring.snappy}
|
|
137
|
+
/>
|
|
138
|
+
)}
|
|
139
|
+
{option.label}
|
|
140
|
+
</label>
|
|
141
|
+
);
|
|
142
|
+
})}
|
|
143
|
+
</fieldset>
|
|
144
|
+
) : (
|
|
145
|
+
<Picker<T>
|
|
146
|
+
label={label}
|
|
147
|
+
value={value}
|
|
148
|
+
options={options}
|
|
149
|
+
onChange={onChange}
|
|
150
|
+
searchable={false}
|
|
151
|
+
className={fallbackPickerSizes[size]}
|
|
152
|
+
size={size === "sm" ? "sm" : size === "lg" ? "lg" : "md"}
|
|
153
|
+
/>
|
|
154
|
+
)}
|
|
155
|
+
</div>
|
|
156
|
+
);
|
|
157
157
|
}
|
|
@@ -1,75 +1,82 @@
|
|
|
1
|
-
import { FLOATING_LAYER, ownsFloatingLayer } from "../../lib/floating-layers"
|
|
1
|
+
import { FLOATING_LAYER, ownsFloatingLayer } from "../../lib/floating-layers";
|
|
2
2
|
|
|
3
|
-
const surfaces: HTMLElement[] = []
|
|
4
|
-
const isolated = new Map<HTMLElement, boolean>()
|
|
5
|
-
let overflow = ""
|
|
6
|
-
let overflowPriority = ""
|
|
3
|
+
const surfaces: HTMLElement[] = [];
|
|
4
|
+
const isolated = new Map<HTMLElement, boolean>();
|
|
5
|
+
let overflow = "";
|
|
6
|
+
let overflowPriority = "";
|
|
7
7
|
|
|
8
8
|
function restoreBackground() {
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
for (const [element, inert] of isolated) element.inert = inert;
|
|
10
|
+
isolated.clear();
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
function isolateBackground() {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
14
|
+
restoreBackground();
|
|
15
|
+
const surface = surfaces.at(-1);
|
|
16
|
+
if (!surface) return;
|
|
17
|
+
const roots = [surface.parentElement ?? surface];
|
|
18
|
+
for (const layer of document.querySelectorAll<HTMLElement>(
|
|
19
|
+
`[${FLOATING_LAYER}]`,
|
|
20
|
+
)) {
|
|
21
|
+
if (ownsFloatingLayer(surface, layer)) roots.push(layer);
|
|
22
|
+
}
|
|
23
|
+
const visit = (parent: HTMLElement) => {
|
|
24
|
+
for (const child of parent.children) {
|
|
25
|
+
if (!(child instanceof HTMLElement)) continue;
|
|
26
|
+
if (roots.includes(child)) continue;
|
|
27
|
+
if (roots.some((root) => child.contains(root))) {
|
|
28
|
+
visit(child);
|
|
29
|
+
} else if (!child.matches("script, style, link")) {
|
|
30
|
+
isolated.set(child, child.inert);
|
|
31
|
+
child.inert = true;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
visit(document.body);
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
/** Keeps nested sheets and their portaled controls above an inactive background. */
|
|
37
39
|
export function enterModalEnvironment(surface: HTMLElement): () => void {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
40
|
+
if (!surfaces.length) {
|
|
41
|
+
overflow = document.body.style.getPropertyValue("overflow");
|
|
42
|
+
overflowPriority = document.body.style.getPropertyPriority("overflow");
|
|
43
|
+
document.body.style.setProperty("overflow", "hidden");
|
|
44
|
+
}
|
|
45
|
+
surfaces.push(surface);
|
|
46
|
+
isolateBackground();
|
|
47
|
+
const preventBackgroundScroll = (event: WheelEvent | TouchEvent) => {
|
|
48
|
+
if (surfaces.at(-1) !== surface) return;
|
|
49
|
+
const target = event.target;
|
|
50
|
+
if (
|
|
51
|
+
target instanceof Element &&
|
|
52
|
+
(surface.contains(target) || ownsFloatingLayer(surface, target))
|
|
53
|
+
)
|
|
54
|
+
return;
|
|
55
|
+
event.preventDefault();
|
|
56
|
+
};
|
|
57
|
+
document.addEventListener("wheel", preventBackgroundScroll, {
|
|
58
|
+
passive: false,
|
|
59
|
+
});
|
|
60
|
+
document.addEventListener("touchmove", preventBackgroundScroll, {
|
|
61
|
+
passive: false,
|
|
62
|
+
});
|
|
63
|
+
const observer = new MutationObserver(() => {
|
|
64
|
+
if (surfaces.at(-1) === surface) isolateBackground();
|
|
65
|
+
});
|
|
66
|
+
observer.observe(document.body, {
|
|
67
|
+
childList: true,
|
|
68
|
+
subtree: true,
|
|
69
|
+
});
|
|
70
|
+
return () => {
|
|
71
|
+
observer.disconnect();
|
|
72
|
+
document.removeEventListener("wheel", preventBackgroundScroll);
|
|
73
|
+
document.removeEventListener("touchmove", preventBackgroundScroll);
|
|
74
|
+
surfaces.splice(surfaces.indexOf(surface), 1);
|
|
75
|
+
isolateBackground();
|
|
76
|
+
if (!surfaces.length) {
|
|
77
|
+
if (overflow)
|
|
78
|
+
document.body.style.setProperty("overflow", overflow, overflowPriority);
|
|
79
|
+
else document.body.style.removeProperty("overflow");
|
|
80
|
+
}
|
|
81
|
+
};
|
|
75
82
|
}
|
|
@@ -1,57 +1,62 @@
|
|
|
1
|
-
import { type ReactNode, useLayoutEffect, useRef, useState } from "react"
|
|
2
|
-
import { createPortal } from "react-dom"
|
|
1
|
+
import { type ReactNode, useLayoutEffect, useRef, useState } from "react";
|
|
2
|
+
import { createPortal } from "react-dom";
|
|
3
3
|
|
|
4
4
|
/** Escape transformed page layers while retaining the source's scoped appearance. */
|
|
5
5
|
export function SheetPortal({ children }: { children: ReactNode }) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
6
|
+
const source = useRef<HTMLSpanElement>(null);
|
|
7
|
+
const [host, setHost] = useState<HTMLDivElement | null>(null);
|
|
8
|
+
useLayoutEffect(() => {
|
|
9
|
+
const marker = source.current;
|
|
10
|
+
if (!marker) return;
|
|
11
|
+
const document = marker.ownerDocument;
|
|
12
|
+
const layer = document.createElement("div");
|
|
13
|
+
layer.dataset.passuiOverlay = "";
|
|
14
|
+
layer.style.display = "contents";
|
|
15
|
+
const attributes = ["data-theme", "data-accent", "data-os", "dir"];
|
|
16
|
+
let properties = new Set<string>();
|
|
17
|
+
const inherit = () => {
|
|
18
|
+
for (const name of attributes) {
|
|
19
|
+
const value = marker.closest(`[${name}]`)?.getAttribute(name);
|
|
20
|
+
if (value === null || value === undefined) layer.removeAttribute(name);
|
|
21
|
+
else layer.setAttribute(name, value);
|
|
22
|
+
}
|
|
23
|
+
const computed = getComputedStyle(marker);
|
|
24
|
+
const next = new Set<string>();
|
|
25
|
+
for (const name of computed) {
|
|
26
|
+
if (!name.startsWith("--")) continue;
|
|
27
|
+
next.add(name);
|
|
28
|
+
layer.style.setProperty(name, computed.getPropertyValue(name));
|
|
29
|
+
}
|
|
30
|
+
for (const name of properties)
|
|
31
|
+
if (!next.has(name)) layer.style.removeProperty(name);
|
|
32
|
+
properties = next;
|
|
33
|
+
layer.style.colorScheme = computed.colorScheme;
|
|
34
|
+
layer.style.fontFamily = computed.fontFamily;
|
|
35
|
+
layer.style.direction = computed.direction;
|
|
36
|
+
};
|
|
37
|
+
inherit();
|
|
38
|
+
document.body.appendChild(layer);
|
|
39
|
+
setHost(layer);
|
|
40
|
+
const observer = new MutationObserver(inherit);
|
|
41
|
+
for (
|
|
42
|
+
let ancestor = marker.parentElement;
|
|
43
|
+
ancestor;
|
|
44
|
+
ancestor = ancestor.parentElement
|
|
45
|
+
) {
|
|
46
|
+
observer.observe(ancestor, {
|
|
47
|
+
attributes: true,
|
|
48
|
+
attributeFilter: [...attributes, "style", "class"],
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return () => {
|
|
52
|
+
observer.disconnect();
|
|
53
|
+
layer.remove();
|
|
54
|
+
};
|
|
55
|
+
}, []);
|
|
56
|
+
return (
|
|
57
|
+
<>
|
|
58
|
+
<span ref={source} hidden aria-hidden="true" />
|
|
59
|
+
{host && createPortal(children, host)}
|
|
60
|
+
</>
|
|
61
|
+
);
|
|
57
62
|
}
|