@window-splitter/svelte 0.8.4

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 (42) hide show
  1. package/.storybook/main.ts +33 -0
  2. package/.storybook/preview.ts +14 -0
  3. package/.tshy/build.json +8 -0
  4. package/.tshy/commonjs.json +19 -0
  5. package/.tshy/esm.json +18 -0
  6. package/.turbo/turbo-build.log +6 -0
  7. package/CHANGELOG.md +20 -0
  8. package/README.md +58 -0
  9. package/dist/Panel.svelte +178 -0
  10. package/dist/Panel.svelte.d.ts +19 -0
  11. package/dist/Panel.svelte.d.ts.map +1 -0
  12. package/dist/PanelGroup.svelte +159 -0
  13. package/dist/PanelGroup.svelte.d.ts +18 -0
  14. package/dist/PanelGroup.svelte.d.ts.map +1 -0
  15. package/dist/PanelResizer.svelte +171 -0
  16. package/dist/PanelResizer.svelte.d.ts +8 -0
  17. package/dist/PanelResizer.svelte.d.ts.map +1 -0
  18. package/dist/index.d.ts +4 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +3 -0
  21. package/eslint.config.js +24 -0
  22. package/package.json +92 -0
  23. package/src/SvelteWindowSplitter.test.svelte.ts +283 -0
  24. package/src/__snapshots__/SvelteWindowSplitter.test.svelte.ts.snap +56 -0
  25. package/src/__snapshots__/SvelteWindowSplitter.test.ts.snap +54 -0
  26. package/src/lib/Panel.svelte +178 -0
  27. package/src/lib/PanelGroup.svelte +159 -0
  28. package/src/lib/PanelResizer.svelte +171 -0
  29. package/src/lib/index.ts +3 -0
  30. package/src/stories/Autosave.svelte +52 -0
  31. package/src/stories/AutosaveCollapsible.svelte +65 -0
  32. package/src/stories/AutosaveCookie.svelte +63 -0
  33. package/src/stories/Collapsible.svelte +112 -0
  34. package/src/stories/Conditional.svelte +70 -0
  35. package/src/stories/DynamicConstraints.svelte +73 -0
  36. package/src/stories/Simple.svelte +48 -0
  37. package/src/stories/SvelteWindowSplitter.stories.svelte +468 -0
  38. package/src/stories/VerticalLayout.svelte +58 -0
  39. package/svelte.config.js +7 -0
  40. package/tsconfig.json +6 -0
  41. package/vite.config.js +7 -0
  42. package/vitest.config.js +19 -0
@@ -0,0 +1,33 @@
1
+ import type { StorybookConfig } from "@storybook/svelte-vite";
2
+
3
+ import { join, dirname } from "path";
4
+
5
+ /**
6
+ * This function is used to resolve the absolute path of a package.
7
+ * It is needed in projects that use Yarn PnP or are set up within a monorepo.
8
+ */
9
+ function getAbsolutePath(value: string) {
10
+ return dirname(require.resolve(join(value, "package.json")));
11
+ }
12
+ const config: StorybookConfig = {
13
+ stories: [
14
+ "../stories/**/*.mdx",
15
+ "../src/**/*.stories.@(js|ts|svelte)",
16
+ "../stories/**/*.stories.@(js|ts|svelte)",
17
+ ],
18
+ addons: [
19
+ {
20
+ name: getAbsolutePath("@storybook/addon-essentials"),
21
+ options: {
22
+ docs: false,
23
+ },
24
+ },
25
+ getAbsolutePath("@storybook/addon-svelte-csf"),
26
+ getAbsolutePath("@storybook/addon-interactions"),
27
+ ],
28
+ framework: {
29
+ name: getAbsolutePath("@storybook/svelte-vite"),
30
+ options: {},
31
+ },
32
+ };
33
+ export default config;
@@ -0,0 +1,14 @@
1
+ import type { Preview } from '@storybook/svelte'
2
+
3
+ const preview: Preview = {
4
+ parameters: {
5
+ controls: {
6
+ matchers: {
7
+ color: /(background|color)$/i,
8
+ date: /Date$/i,
9
+ },
10
+ },
11
+ },
12
+ };
13
+
14
+ export default preview;
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../tsconfig.json",
3
+ "compilerOptions": {
4
+ "rootDir": "../src",
5
+ "module": "nodenext",
6
+ "moduleResolution": "nodenext"
7
+ }
8
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "extends": "./build.json",
3
+ "include": [
4
+ "../src/**/*.ts",
5
+ "../src/**/*.cts",
6
+ "../src/**/*.tsx",
7
+ "../src/**/*.json"
8
+ ],
9
+ "exclude": [
10
+ "../node_modules",
11
+ "../src/**/*.test.svelte.ts",
12
+ "../**/*stories*",
13
+ "../src/**/*.mts",
14
+ "../src/package.json"
15
+ ],
16
+ "compilerOptions": {
17
+ "outDir": "../.tshy-build/commonjs"
18
+ }
19
+ }
package/.tshy/esm.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "extends": "./build.json",
3
+ "include": [
4
+ "../src/**/*.ts",
5
+ "../src/**/*.mts",
6
+ "../src/**/*.tsx",
7
+ "../src/**/*.json"
8
+ ],
9
+ "exclude": [
10
+ "../node_modules",
11
+ "../src/**/*.test.svelte.ts",
12
+ "../**/*stories*",
13
+ "../src/package.json"
14
+ ],
15
+ "compilerOptions": {
16
+ "outDir": "../.tshy-build/esm"
17
+ }
18
+ }
@@ -0,0 +1,6 @@
1
+
2
+ 
3
+ > @window-splitter/svelte@0.8.3 build /Users/andrew/Documents/react-window-splitter/packages/svelte
4
+ > svelte-package
5
+
6
+ src/lib -> dist
package/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
1
+ # v0.8.4 (Sat May 03 2025)
2
+
3
+ #### 🐛 Bug Fix
4
+
5
+ - Svelte Adapter [#55](https://github.com/hipstersmoothie/window-splitter/pull/55) ([@hipstersmoothie](https://github.com/hipstersmoothie))
6
+ - fix test ([@hipstersmoothie](https://github.com/hipstersmoothie))
7
+ - builds ([@hipstersmoothie](https://github.com/hipstersmoothie))
8
+ - test it ([@hipstersmoothie](https://github.com/hipstersmoothie))
9
+ - fix warning ([@hipstersmoothie](https://github.com/hipstersmoothie))
10
+ - fix types ([@hipstersmoothie](https://github.com/hipstersmoothie))
11
+ - clean up type errors ([@hipstersmoothie](https://github.com/hipstersmoothie))
12
+ - constraints and imperative api ([@hipstersmoothie](https://github.com/hipstersmoothie))
13
+ - conditional ([@hipstersmoothie](https://github.com/hipstersmoothie))
14
+ - collapse ([@hipstersmoothie](https://github.com/hipstersmoothie))
15
+ - working ([@hipstersmoothie](https://github.com/hipstersmoothie))
16
+ - startin ([@hipstersmoothie](https://github.com/hipstersmoothie))
17
+
18
+ #### Authors: 1
19
+
20
+ - Andrew Lisowski ([@hipstersmoothie](https://github.com/hipstersmoothie))
package/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # @window-splitter/svelte
2
+
3
+ A full featured window splitter for Svelte.
4
+
5
+ - Support for the full [window splitter](https://www.w3.org/WAI/ARIA/apg/patterns/windowsplitter/) ARIA spec
6
+ - Support for percentage and pixel based constraints
7
+ - Collapsible panels
8
+ - Controlled panels
9
+ - Layout Persistance - LocalStorage and Cookie
10
+
11
+ [Read the full docs](https://react-window-splitter-six.vercel.app)
12
+
13
+ > NOTE: Docs are currently on react but it's the same API. Refer the the stories for usage examples.
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install @window-splitter/svelte
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ ```svelte
24
+ <script>
25
+ import { PanelGroup } from '@window-splitter/svelte';
26
+ </script>
27
+
28
+ <PanelGroup>
29
+ <Panel min="130px" max="400px" />
30
+ <PanelResizer />
31
+ <Panel min="130px" />
32
+ </PanelGroup>
33
+ ```
34
+
35
+ ## Features
36
+
37
+ - WAI-ARIA compliant
38
+ - Keyboard accessible
39
+ - Touch friendly
40
+ - Customizable
41
+ - TypeScript support
42
+
43
+ ## Server Side Rendering + Strict Mode
44
+
45
+ While not required for the simple case, for anything more complex you will
46
+ need to add an `id` prop to your panels and handles.
47
+ This is so that the component can tell all of the components apart during layout and rendering.
48
+
49
+ Features that require `id`:
50
+
51
+ - Conditional Panels
52
+ - Server Side Rendering
53
+
54
+ ## Prior Art
55
+
56
+ This library is heavily inspired by the following libraries:
57
+
58
+ - [react-resizable-panels](https://github.com/bvaughn/react-resizable-panels)
@@ -0,0 +1,178 @@
1
+ <script lang="ts">
2
+ import type { SharedPanelProps } from "@window-splitter/interface";
3
+ import { getContext } from "svelte";
4
+ import {
5
+ initializePanel,
6
+ isPanelData,
7
+ haveConstraintsChangedForPanel,
8
+ getPanelPercentageSize,
9
+ getPanelPixelSize,
10
+ } from "@window-splitter/state";
11
+ import type {
12
+ PanelData,
13
+ GroupMachineContextValue,
14
+ SendFn,
15
+ Unit,
16
+ } from "@window-splitter/state";
17
+ import { getPanelDomAttributes } from "@window-splitter/interface";
18
+ import type { HTMLAttributes } from "svelte/elements";
19
+
20
+ export { PanelHandle } from "@window-splitter/interface";
21
+
22
+ interface Props
23
+ extends SharedPanelProps<boolean>,
24
+ HTMLAttributes<HTMLDivElement> {}
25
+
26
+ let {
27
+ children,
28
+ min,
29
+ max,
30
+ id: _id,
31
+ collapsible,
32
+ collapsed,
33
+ collapsedSize,
34
+ onCollapseChange,
35
+ collapseAnimation,
36
+ onResize,
37
+ defaultCollapsed,
38
+ default: defaultSize,
39
+ isStaticAtRest,
40
+ ...attrs
41
+ }: Props = $props();
42
+
43
+ const defaultId = $props.id();
44
+ const id = _id || defaultId;
45
+ const send = getContext<SendFn>("send");
46
+ const state = getContext<GroupMachineContextValue>("state");
47
+
48
+ const initPanel = (): PanelData =>
49
+ initializePanel({
50
+ id,
51
+ min,
52
+ max,
53
+ collapsible,
54
+ collapsed,
55
+ collapsedSize,
56
+ onCollapseChange: { current: onCollapseChange },
57
+ collapseAnimation,
58
+ onResize: { current: onResize },
59
+ defaultCollapsed,
60
+ default: defaultSize,
61
+ isStaticAtRest,
62
+ });
63
+
64
+ const isPrerender = getContext<{ current: boolean }>("isPrerender");
65
+ const panelData = () => {
66
+ const item = state?.items.find((i) => i.id === id);
67
+ if (!item || !isPanelData(item)) return undefined;
68
+ return item;
69
+ };
70
+
71
+ let dynamicPanelIsMounting = false;
72
+
73
+ if (panelData()) {
74
+ send({
75
+ type: "rebindPanelCallbacks",
76
+ data: {
77
+ id,
78
+ onCollapseChange: { current: onCollapseChange },
79
+ onResize: { current: onResize },
80
+ },
81
+ });
82
+ } else {
83
+ if (isPrerender.current) {
84
+ send({ type: "registerPanel", data: initPanel() });
85
+ } else {
86
+ dynamicPanelIsMounting = true;
87
+ }
88
+ }
89
+
90
+ const contraintChanged = $derived(
91
+ !dynamicPanelIsMounting &&
92
+ haveConstraintsChangedForPanel(initPanel(), panelData())
93
+ );
94
+
95
+ $effect(() => {
96
+ if (!contraintChanged) return;
97
+ send({ type: "updateConstraints", data: initPanel() });
98
+ });
99
+
100
+ const dynamicPanelData = initPanel();
101
+
102
+ $effect(() => {
103
+ if (!dynamicPanelIsMounting) return;
104
+
105
+ const groupElement = document.getElementById(state.groupId);
106
+ if (!groupElement) return;
107
+
108
+ const panelElement = document.getElementById(id);
109
+ if (!panelElement) return;
110
+
111
+ const order = Array.from(groupElement.children).indexOf(panelElement);
112
+ if (typeof order !== "number") return;
113
+
114
+ send({
115
+ type: "registerDynamicPanel",
116
+ data: { ...dynamicPanelData, order },
117
+ });
118
+ dynamicPanelIsMounting = false;
119
+ });
120
+
121
+ $effect(() => () => {
122
+ requestAnimationFrame(() => {
123
+ send({ type: "unregisterPanel", id });
124
+ });
125
+ });
126
+
127
+ const isControlledCollapse = $derived(panelData()?.collapseIsControlled);
128
+
129
+ $effect(() => {
130
+ if (!isControlledCollapse) return;
131
+
132
+ if (collapsed) {
133
+ send?.({ type: "collapsePanel", panelId: id, controlled: true });
134
+ } else {
135
+ send?.({ type: "expandPanel", panelId: id, controlled: true });
136
+ }
137
+ });
138
+
139
+ const domAttributes = () => {
140
+ const currentPanel = panelData();
141
+
142
+ return getPanelDomAttributes({
143
+ groupId: state.groupId,
144
+ id,
145
+ collapsible: currentPanel?.collapsible,
146
+ collapsed: currentPanel?.collapsed,
147
+ });
148
+ };
149
+
150
+ export const getId = () => id;
151
+ export const collapse = () => {
152
+ if (!panelData()?.collapsible) return;
153
+ send({ type: "collapsePanel", panelId: id, controlled: true });
154
+ };
155
+ export const isCollapsed = () =>
156
+ Boolean(panelData()?.collapsible && panelData()?.collapsed);
157
+ export const expand = () => {
158
+ if (!panelData()?.collapsible) return;
159
+ send({ type: "expandPanel", panelId: id, controlled: true });
160
+ };
161
+ export const isExpanded = () =>
162
+ Boolean(panelData()?.collapsible && !panelData()?.collapsed);
163
+ export const getPixelSize = () => getPanelPixelSize(state, id);
164
+ export const setSize = (size: Unit) => {
165
+ send({ type: "setPanelPixelSize", panelId: id, size });
166
+ };
167
+ export const getPercentageSize = () => getPanelPercentageSize(state, id);
168
+ </script>
169
+
170
+ <div
171
+ {...attrs}
172
+ {...domAttributes()}
173
+ style:min-width={0}
174
+ style:min-height={0}
175
+ style:overflow="hidden"
176
+ >
177
+ {@render children?.()}
178
+ </div>
@@ -0,0 +1,19 @@
1
+ import type { SharedPanelProps } from "@window-splitter/interface";
2
+ import type { Unit } from "@window-splitter/state";
3
+ import type { HTMLAttributes } from "svelte/elements";
4
+ interface Props extends SharedPanelProps<boolean>, HTMLAttributes<HTMLDivElement> {
5
+ }
6
+ declare const Panel: import("svelte").Component<Props, {
7
+ PanelHandle: typeof PanelHandle;
8
+ getId: () => string;
9
+ collapse: () => void;
10
+ isCollapsed: () => boolean;
11
+ expand: () => void;
12
+ isExpanded: () => boolean;
13
+ getPixelSize: () => number;
14
+ setSize: (size: Unit) => void;
15
+ getPercentageSize: () => number;
16
+ }, "">;
17
+ type Panel = ReturnType<typeof Panel>;
18
+ export default Panel;
19
+ //# sourceMappingURL=Panel.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Panel.svelte.d.ts","sourceRoot":"","sources":["../src/lib/Panel.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AASnE,OAAO,KAAK,EAIR,IAAI,EACL,MAAM,wBAAwB,CAAC;AAElC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGpD,UAAU,KACR,SAAQ,gBAAgB,CAAC,OAAO,CAAC,EAC/B,cAAc,CAAC,cAAc,CAAC;CAAG;AAkKvC,QAAA,MAAM,KAAK;iBADsQ,OAAO,WAAW;;;;;;;oBAXzQ,IAAI;;MAYqB,CAAC;AACpD,KAAK,KAAK,GAAG,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC;AACtC,eAAe,KAAK,CAAC"}
@@ -0,0 +1,159 @@
1
+ <script lang="ts">
2
+ import type { SharedPanelGroupProps } from "@window-splitter/interface";
3
+ import { measureGroupChildren } from "@window-splitter/interface";
4
+ import {
5
+ buildTemplate,
6
+ groupMachine,
7
+ prepareSnapshot,
8
+ prepareItems,
9
+ getPanelGroupPixelSizes,
10
+ getPanelGroupPercentageSizes,
11
+ isPanelData,
12
+ } from "@window-splitter/state";
13
+ import type { GroupMachineContextValue, Unit } from "@window-splitter/state";
14
+ import { setContext } from "svelte";
15
+ import type { HTMLAttributes } from "svelte/elements";
16
+
17
+ export { PanelGroupHandle } from "@window-splitter/interface";
18
+
19
+ interface Props
20
+ extends SharedPanelGroupProps,
21
+ HTMLAttributes<HTMLDivElement> {
22
+ id: string;
23
+ }
24
+
25
+ let {
26
+ children,
27
+ orientation = "horizontal",
28
+ autosaveStrategy = "localStorage",
29
+ autosaveId,
30
+ snapshot,
31
+ ...attrs
32
+ }: Props = $props();
33
+
34
+ if (
35
+ !snapshot &&
36
+ typeof window !== "undefined" &&
37
+ autosaveId &&
38
+ autosaveStrategy === "localStorage"
39
+ ) {
40
+ const localSnapshot = localStorage.getItem(autosaveId);
41
+
42
+ if (localSnapshot) {
43
+ snapshot = JSON.parse(localSnapshot) as GroupMachineContextValue;
44
+ }
45
+ }
46
+
47
+ const defaultId = $props.id();
48
+ const id = autosaveId || defaultId;
49
+
50
+ const [initialState, send, machineState] = groupMachine(
51
+ {
52
+ orientation,
53
+ groupId: id,
54
+ autosaveStrategy,
55
+ ...(snapshot ? prepareSnapshot(snapshot) : undefined),
56
+ },
57
+ (s) => {
58
+ if (!context) return;
59
+ updateContext(s);
60
+ }
61
+ );
62
+
63
+ const context = $state<GroupMachineContextValue>(initialState);
64
+ function updateContext(s: GroupMachineContextValue) {
65
+ for (const key in s) {
66
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
67
+ (context as any)[key] = (s as any)[key];
68
+ }
69
+ }
70
+
71
+ setContext("send", send);
72
+ setContext("state", context);
73
+ setContext("id", machineState);
74
+
75
+ const isPrerender = $state({ current: true });
76
+ setContext("isPrerender", isPrerender);
77
+ $effect(() => {
78
+ isPrerender.current = false;
79
+ });
80
+
81
+ let elementRef = $state<HTMLDivElement>();
82
+
83
+ $effect(() => {
84
+ const observer = new ResizeObserver(([entry]) => {
85
+ if (!entry) return;
86
+ if (entry.contentRect.width > 0 && entry.contentRect.height > 0) {
87
+ send({ type: "setSize", size: entry.contentRect });
88
+ }
89
+ });
90
+
91
+ if (elementRef) {
92
+ observer.observe(elementRef);
93
+ }
94
+
95
+ return () => observer.disconnect();
96
+ });
97
+
98
+ let childIds = $derived(context.items.map((i) => i.id).join(","));
99
+
100
+ $effect(() => {
101
+ // re-render when the childIds change
102
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
103
+ childIds;
104
+ measureGroupChildren(id, (childrenSizes) => {
105
+ send({ type: "setActualItemsSize", childrenSizes });
106
+ });
107
+ });
108
+
109
+ let template = $derived(buildTemplate(context));
110
+
111
+ $effect(() => {
112
+ send({ type: "unlockGroup" });
113
+ return () => send({ type: "lockGroup" });
114
+ });
115
+
116
+ export const getId = () => id;
117
+ export const getPixelSizes = () => getPanelGroupPixelSizes(context);
118
+ export const getPercentageSizes = () => getPanelGroupPercentageSizes(context);
119
+ export const getTemplate = () =>
120
+ buildTemplate({ ...context, items: prepareItems(context) });
121
+ export const getState = () =>
122
+ machineState.current === "idle" ? "idle" : "dragging";
123
+ export const setSizes = (updates: Array<Unit>) => {
124
+ for (let index = 0; index < updates.length; index++) {
125
+ const item = context.items[index];
126
+ const update = updates[index];
127
+
128
+ if (item && isPanelData(item) && update) {
129
+ send({
130
+ type: "setPanelPixelSize",
131
+ panelId: item.id,
132
+ size: update,
133
+ });
134
+ }
135
+ }
136
+ };
137
+ </script>
138
+
139
+ <div
140
+ {...attrs}
141
+ data-panel-group-wrapper
142
+ bind:this={elementRef}
143
+ style:display="grid"
144
+ style:grid-template-columns={context.orientation === "horizontal"
145
+ ? template
146
+ : undefined}
147
+ style:grid-template-rows={context.orientation === "vertical"
148
+ ? template
149
+ : undefined}
150
+ {id}
151
+ >
152
+ {@render children?.()}
153
+ </div>
154
+
155
+ <style>
156
+ :where([data-panel-group-wrapper]) {
157
+ height: 100%;
158
+ }
159
+ </style>
@@ -0,0 +1,18 @@
1
+ import type { SharedPanelGroupProps } from "@window-splitter/interface";
2
+ import type { Unit } from "@window-splitter/state";
3
+ import type { HTMLAttributes } from "svelte/elements";
4
+ interface Props extends SharedPanelGroupProps, HTMLAttributes<HTMLDivElement> {
5
+ id: string;
6
+ }
7
+ declare const PanelGroup: import("svelte").Component<Props, {
8
+ PanelGroupHandle: typeof PanelGroupHandle;
9
+ getId: () => string;
10
+ getPixelSizes: () => number[];
11
+ getPercentageSizes: () => number[];
12
+ getTemplate: () => string;
13
+ getState: () => "idle" | "dragging";
14
+ setSizes: (updates: Array<Unit>) => void;
15
+ }, "">;
16
+ type PanelGroup = ReturnType<typeof PanelGroup>;
17
+ export default PanelGroup;
18
+ //# sourceMappingURL=PanelGroup.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PanelGroup.svelte.d.ts","sourceRoot":"","sources":["../src/lib/PanelGroup.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAWxE,OAAO,KAAK,EAA4B,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAE7E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGpD,UAAU,KACR,SAAQ,qBAAqB,EAC3B,cAAc,CAAC,cAAc,CAAC;IAChC,EAAE,EAAE,MAAM,CAAC;CACZ;AA0IH,QAAA,MAAM,UAAU;sBADgP,OAAO,gBAAgB;;;;;;wBA3BzP,KAAK,CAAC,IAAI,CAAC;MA4Be,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}