southpaw 0.2.7
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 +51 -0
- package/api/client.ts +83 -0
- package/api/helpers/fs.ts +98 -0
- package/api/helpers/stream.ts +6 -0
- package/api/index.ts +50 -0
- package/api/server.ts +566 -0
- package/constants/fonts.ts +1583 -0
- package/constants/icons.ts +2124 -0
- package/constants/langs.ts +2592 -0
- package/constants/ssr-loading.tsx +17 -0
- package/contexts/dev.tsx +124 -0
- package/hooks/useDebounceCb.ts +21 -0
- package/modules/app.tsx +102 -0
- package/modules/behaviors.ts +161 -0
- package/modules/component.tsx +483 -0
- package/modules/config/index.ts +8 -0
- package/modules/database.ts +25 -0
- package/modules/document.tsx +23 -0
- package/modules/error.tsx +16 -0
- package/modules/page/autozoom.tsx +92 -0
- package/modules/page/browser.tsx +166 -0
- package/modules/page/index.tsx +327 -0
- package/modules/page/insert-modal.tsx +225 -0
- package/modules/page/inspect/index.tsx +237 -0
- package/modules/page/inspect/wrapper.tsx +88 -0
- package/modules/page/knobs/action.tsx +58 -0
- package/modules/page/knobs/color.tsx +78 -0
- package/modules/page/knobs/helpers.ts +71 -0
- package/modules/page/knobs/icon.tsx +85 -0
- package/modules/page/knobs/linker.tsx +188 -0
- package/modules/page/knobs/list-json.tsx +149 -0
- package/modules/page/knobs/list-text.tsx +129 -0
- package/modules/page/knobs/number.tsx +84 -0
- package/modules/page/knobs/segmented.tsx +81 -0
- package/modules/page/knobs/select.tsx +89 -0
- package/modules/page/knobs/size.tsx +106 -0
- package/modules/page/knobs/switch.tsx +75 -0
- package/modules/page/knobs/text.tsx +93 -0
- package/modules/page/left-tabs.tsx +36 -0
- package/modules/page/right-tabs.tsx +31 -0
- package/modules/page/seo.tsx +69 -0
- package/modules/page/tree.tsx +860 -0
- package/modules/state.ts +787 -0
- package/modules/store.ts +37 -0
- package/modules/theme.ts +105 -0
- package/modules/translations.ts +15 -0
- package/package.json +68 -0
- package/scripts/bin.js +607 -0
- package/scripts/test.js +145 -0
- package/themes/base.tsx +8962 -0
- package/themes/index.ts +4 -0
- package/themes/m2.ts +39 -0
- package/themes/newspaper.ts +39 -0
- package/types/api.ts +57 -0
- package/types/fiber_node.ts +69 -0
- package/types/react_element.ts +14 -0
- package/ui/accordion.tsx +70 -0
- package/ui/actionicon.tsx +41 -0
- package/ui/affix.tsx +14 -0
- package/ui/alert.tsx +37 -0
- package/ui/anchor.tsx +52 -0
- package/ui/arrow.tsx +116 -0
- package/ui/aspectratio.tsx +21 -0
- package/ui/autocomplete.tsx +34 -0
- package/ui/avatar.tsx +36 -0
- package/ui/backgroundimage.tsx +16 -0
- package/ui/badge.tsx +28 -0
- package/ui/blockquote.tsx +15 -0
- package/ui/blocks/faq.tsx +145 -0
- package/ui/blocks/features.tsx +167 -0
- package/ui/blocks/footer.tsx +182 -0
- package/ui/blocks/herobanner.tsx +391 -0
- package/ui/blocks/index.ts +44 -0
- package/ui/blocks/leftnav.tsx +141 -0
- package/ui/blocks/pricing.tsx +210 -0
- package/ui/blocks/testimonials.tsx +149 -0
- package/ui/blocks/uidocs.tsx +1429 -0
- package/ui/box.tsx +27 -0
- package/ui/breadcrumbs.tsx +33 -0
- package/ui/burger.tsx +23 -0
- package/ui/button.tsx +85 -0
- package/ui/card.tsx +28 -0
- package/ui/center.tsx +26 -0
- package/ui/checkbox.tsx +35 -0
- package/ui/chip.tsx +32 -0
- package/ui/closebutton.tsx +15 -0
- package/ui/code.tsx +104 -0
- package/ui/collapse.tsx +16 -0
- package/ui/colorinput.tsx +32 -0
- package/ui/colorpicker.tsx +26 -0
- package/ui/colorswatch.tsx +17 -0
- package/ui/combobox.tsx +80 -0
- package/ui/container.tsx +27 -0
- package/ui/dialog.tsx +15 -0
- package/ui/divider.tsx +58 -0
- package/ui/drawer.tsx +28 -0
- package/ui/fieldset.tsx +15 -0
- package/ui/filebutton.tsx +23 -0
- package/ui/fileinput.tsx +36 -0
- package/ui/flex.tsx +139 -0
- package/ui/focustrap.tsx +15 -0
- package/ui/form.tsx +26 -0
- package/ui/grid.tsx +69 -0
- package/ui/highlight.tsx +15 -0
- package/ui/hoverable.tsx +66 -0
- package/ui/hovercard.tsx +45 -0
- package/ui/icon.tsx +76 -0
- package/ui/image.tsx +32 -0
- package/ui/index.ts +1355 -0
- package/ui/indicator.tsx +42 -0
- package/ui/infobox.tsx +200 -0
- package/ui/input.tsx +18 -0
- package/ui/inviewport.tsx +23 -0
- package/ui/jsoninput.tsx +35 -0
- package/ui/kbd.tsx +20 -0
- package/ui/list.tsx +33 -0
- package/ui/loader.tsx +26 -0
- package/ui/loadingoverlay.tsx +22 -0
- package/ui/mark.tsx +15 -0
- package/ui/menu.tsx +89 -0
- package/ui/modal.tsx +27 -0
- package/ui/multiselect.tsx +42 -0
- package/ui/nativeselect.tsx +17 -0
- package/ui/navlink.tsx +40 -0
- package/ui/notification.tsx +25 -0
- package/ui/numberformatter.tsx +15 -0
- package/ui/numberinput.tsx +37 -0
- package/ui/overlay.tsx +15 -0
- package/ui/pagination.tsx +21 -0
- package/ui/paper.tsx +33 -0
- package/ui/passwordinput.tsx +36 -0
- package/ui/pill.tsx +17 -0
- package/ui/pillsinput.tsx +37 -0
- package/ui/pininput.tsx +30 -0
- package/ui/popover.tsx +43 -0
- package/ui/portal.tsx +15 -0
- package/ui/progress.tsx +49 -0
- package/ui/props.ts +91 -0
- package/ui/propsDef.ts +38 -0
- package/ui/radio.tsx +30 -0
- package/ui/rating.tsx +25 -0
- package/ui/ringprogress.tsx +17 -0
- package/ui/scrollarea.tsx +27 -0
- package/ui/segmentedcontrol.tsx +38 -0
- package/ui/select.tsx +43 -0
- package/ui/simplegrid.tsx +23 -0
- package/ui/skeleton.tsx +15 -0
- package/ui/slider.tsx +31 -0
- package/ui/space.tsx +19 -0
- package/ui/spoiler.tsx +23 -0
- package/ui/stepper.tsx +63 -0
- package/ui/switch.tsx +29 -0
- package/ui/table.tsx +93 -0
- package/ui/tabs.tsx +80 -0
- package/ui/tagsinput.tsx +19 -0
- package/ui/terminal.tsx +44 -0
- package/ui/text.tsx +168 -0
- package/ui/textarea.tsx +36 -0
- package/ui/textinput.tsx +34 -0
- package/ui/timeline.tsx +30 -0
- package/ui/title.tsx +42 -0
- package/ui/tooltip.tsx +32 -0
- package/ui/transition.tsx +21 -0
- package/ui/tree.tsx +42 -0
- package/ui/typographystylesprovider.tsx +20 -0
- package/ui/unstyledbutton.tsx +18 -0
- package/ui/video.tsx +16 -0
- package/ui/visuallyhidden.tsx +15 -0
- package/utils/index.ts +76 -0
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
/* TODO: Refactor typings */
|
|
2
|
+
import {
|
|
3
|
+
FunctionComponent,
|
|
4
|
+
RefObject,
|
|
5
|
+
ReactNode,
|
|
6
|
+
ReactElement,
|
|
7
|
+
useEffect,
|
|
8
|
+
useMemo,
|
|
9
|
+
useReducer,
|
|
10
|
+
useRef,
|
|
11
|
+
useState,
|
|
12
|
+
createRef,
|
|
13
|
+
memo,
|
|
14
|
+
cloneElement,
|
|
15
|
+
} from "react";
|
|
16
|
+
import ObservableSlim from "./state";
|
|
17
|
+
import isEqual from "react-fast-compare";
|
|
18
|
+
import { _ErrorBoundary } from "./error";
|
|
19
|
+
import { NextRouter, useRouter } from "next/router";
|
|
20
|
+
import { ComboboxData, SegmentedControlItem } from "@mantine/core";
|
|
21
|
+
import { CommonProps, MOUNT_ANIMATIONS } from "../ui/props";
|
|
22
|
+
|
|
23
|
+
type PropsType<T = Record<string, unknown>> = CommonProps & T;
|
|
24
|
+
type StateType = Record<string, unknown>;
|
|
25
|
+
type RefsType = Record<string, unknown>;
|
|
26
|
+
type PropDef = {
|
|
27
|
+
label: string;
|
|
28
|
+
} & (
|
|
29
|
+
| {
|
|
30
|
+
type: "color";
|
|
31
|
+
}
|
|
32
|
+
| {
|
|
33
|
+
type: "number";
|
|
34
|
+
min?: number;
|
|
35
|
+
max?: number;
|
|
36
|
+
step?: number;
|
|
37
|
+
}
|
|
38
|
+
| {
|
|
39
|
+
type: "size";
|
|
40
|
+
min?: number;
|
|
41
|
+
max?: number;
|
|
42
|
+
step?: number;
|
|
43
|
+
}
|
|
44
|
+
| {
|
|
45
|
+
type: "icon";
|
|
46
|
+
}
|
|
47
|
+
| {
|
|
48
|
+
type: "segmented";
|
|
49
|
+
options: SegmentedControlItem[];
|
|
50
|
+
}
|
|
51
|
+
| {
|
|
52
|
+
type: "select";
|
|
53
|
+
options: ComboboxData;
|
|
54
|
+
}
|
|
55
|
+
| {
|
|
56
|
+
type: "switch";
|
|
57
|
+
}
|
|
58
|
+
| {
|
|
59
|
+
type: "text";
|
|
60
|
+
multiline?: boolean;
|
|
61
|
+
}
|
|
62
|
+
| {
|
|
63
|
+
type: "list-text";
|
|
64
|
+
}
|
|
65
|
+
| {
|
|
66
|
+
type: "list-json";
|
|
67
|
+
}
|
|
68
|
+
| {
|
|
69
|
+
type: "action";
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
export type PropsDef = Record<string, PropDef>;
|
|
73
|
+
|
|
74
|
+
type ComputedType<P, S> = Record<
|
|
75
|
+
string,
|
|
76
|
+
(
|
|
77
|
+
this: {
|
|
78
|
+
computed: any;
|
|
79
|
+
state: S;
|
|
80
|
+
props: P;
|
|
81
|
+
router: NextRouter;
|
|
82
|
+
el: HTMLElement | null;
|
|
83
|
+
},
|
|
84
|
+
...args: unknown[]
|
|
85
|
+
) => unknown
|
|
86
|
+
>;
|
|
87
|
+
|
|
88
|
+
type MethodsType<P, S, C extends ComputedType<P, S>, R> = Record<
|
|
89
|
+
string,
|
|
90
|
+
(
|
|
91
|
+
this: {
|
|
92
|
+
state: S;
|
|
93
|
+
props: P;
|
|
94
|
+
computed: {
|
|
95
|
+
[KC in keyof C]: ReturnType<C[KC]>;
|
|
96
|
+
};
|
|
97
|
+
refs: {
|
|
98
|
+
[K in keyof R]: {
|
|
99
|
+
current: R[K];
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
methods: Record<string, (...args: any[]) => any>;
|
|
103
|
+
router: NextRouter;
|
|
104
|
+
devError: (error: ReactElement) => void;
|
|
105
|
+
el: HTMLElement | null;
|
|
106
|
+
},
|
|
107
|
+
...args: any[]
|
|
108
|
+
) => any
|
|
109
|
+
>;
|
|
110
|
+
|
|
111
|
+
type RenderThisType<
|
|
112
|
+
P,
|
|
113
|
+
S,
|
|
114
|
+
C extends ComputedType<P, S>,
|
|
115
|
+
R,
|
|
116
|
+
M extends MethodsType<P, S, C, R>,
|
|
117
|
+
> = {
|
|
118
|
+
methods: { [K in keyof M]: (...args: Parameters<M[K]>) => ReturnType<M[K]> };
|
|
119
|
+
state: S;
|
|
120
|
+
props: P & { mount_animation?: any };
|
|
121
|
+
refs: { [K in keyof R]: RefObject<R[K]> };
|
|
122
|
+
router: NextRouter;
|
|
123
|
+
computed: {
|
|
124
|
+
[KC in keyof C]: ReturnType<C[KC]>;
|
|
125
|
+
};
|
|
126
|
+
devError: (error: ReactElement) => void;
|
|
127
|
+
el: HTMLElement | null;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export interface ComponentParams<
|
|
131
|
+
P = PropsType,
|
|
132
|
+
S = StateType,
|
|
133
|
+
C extends ComputedType<P, S> = ComputedType<P, S>,
|
|
134
|
+
R = RefsType,
|
|
135
|
+
M extends MethodsType<P, S, C, R> = MethodsType<P, S, C, R>,
|
|
136
|
+
> {
|
|
137
|
+
name: string;
|
|
138
|
+
description: string;
|
|
139
|
+
props?: P;
|
|
140
|
+
propsDef?: PropsDef;
|
|
141
|
+
state?: S;
|
|
142
|
+
refs?: R;
|
|
143
|
+
methods?: M;
|
|
144
|
+
computed?: C;
|
|
145
|
+
lifecycle?: {
|
|
146
|
+
onMount?: (this: RenderThisType<P, S, C, R, M>) => void;
|
|
147
|
+
onDomMutation?: (
|
|
148
|
+
this: RenderThisType<P, S, C, R, M>,
|
|
149
|
+
mutation: MutationRecord,
|
|
150
|
+
) => void;
|
|
151
|
+
onUnmount?: (this: RenderThisType<P, S, C, R, M>) => void;
|
|
152
|
+
onRender?: (this: RenderThisType<P, S, C, R, M>) => void;
|
|
153
|
+
onMountAnimationEnd?: (this: RenderThisType<P, S, C, R, M>) => void;
|
|
154
|
+
onRouterReady?: (this: RenderThisType<P, S, C, R, M>) => void;
|
|
155
|
+
onPropChange?: (
|
|
156
|
+
this: RenderThisType<P, S, C, R, M>,
|
|
157
|
+
propName: keyof P,
|
|
158
|
+
previousProp: unknown,
|
|
159
|
+
currentProp: unknown,
|
|
160
|
+
) => void;
|
|
161
|
+
};
|
|
162
|
+
render: (this: RenderThisType<P, S, C, R, M>) => ReactNode;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export type ComponentReturnType<P> = FunctionComponent<P> & {
|
|
166
|
+
// Optional metadata used by the Southpaw dev tools
|
|
167
|
+
params?: ComponentParams<P, any, any, any, any>;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
export const create_component = <
|
|
171
|
+
P = PropsType,
|
|
172
|
+
S = StateType,
|
|
173
|
+
C extends ComputedType<P, S> = ComputedType<P, S>,
|
|
174
|
+
R = RefsType,
|
|
175
|
+
M extends MethodsType<P, S, C, R> = MethodsType<P, S, C, R>,
|
|
176
|
+
>(
|
|
177
|
+
_params: ComponentParams<P, S, C, R, M>,
|
|
178
|
+
): ComponentReturnType<P> => {
|
|
179
|
+
const params = _params;
|
|
180
|
+
params.propsDef = params.propsDef || {};
|
|
181
|
+
const defaultMountAnimation = {
|
|
182
|
+
mount_animation: MOUNT_ANIMATIONS.fade,
|
|
183
|
+
};
|
|
184
|
+
const Fn = memo((_props: P) => {
|
|
185
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
186
|
+
const router = useRouter();
|
|
187
|
+
const [errorHtml, setErrorHtml] = useState("");
|
|
188
|
+
const defaultProps = (params.props || {}) as P;
|
|
189
|
+
const el = ref.current;
|
|
190
|
+
|
|
191
|
+
const props = {
|
|
192
|
+
...defaultMountAnimation,
|
|
193
|
+
...defaultProps,
|
|
194
|
+
...(_props || {}),
|
|
195
|
+
} as P;
|
|
196
|
+
|
|
197
|
+
const refs = useMemo(
|
|
198
|
+
() =>
|
|
199
|
+
Object.keys(params.refs || {}).reduce(
|
|
200
|
+
(acc, refName) => {
|
|
201
|
+
acc[refName] = createRef();
|
|
202
|
+
return acc;
|
|
203
|
+
},
|
|
204
|
+
{} as Record<string, ReturnType<typeof createRef>>,
|
|
205
|
+
),
|
|
206
|
+
[],
|
|
207
|
+
);
|
|
208
|
+
const propsRef = useRef<Record<string, unknown>>(
|
|
209
|
+
props as unknown as Record<string, unknown>,
|
|
210
|
+
);
|
|
211
|
+
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
|
212
|
+
const state = useMemo(() => {
|
|
213
|
+
return ObservableSlim.create(
|
|
214
|
+
JSON.parse(JSON.stringify(params.state || {})),
|
|
215
|
+
true,
|
|
216
|
+
function () {
|
|
217
|
+
forceUpdate();
|
|
218
|
+
},
|
|
219
|
+
);
|
|
220
|
+
}, []);
|
|
221
|
+
|
|
222
|
+
const devError = (error: ReactElement) => {
|
|
223
|
+
import("react-dom/server").then(({ renderToString }) => {
|
|
224
|
+
setErrorHtml(renderToString(error));
|
|
225
|
+
});
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const computed = Object.keys(
|
|
229
|
+
(params.computed || {}) as Record<string, unknown>,
|
|
230
|
+
).reduce(
|
|
231
|
+
(acc, computedName) => {
|
|
232
|
+
const computedFn = (
|
|
233
|
+
params.computed as Record<
|
|
234
|
+
string,
|
|
235
|
+
(this: {
|
|
236
|
+
state: S;
|
|
237
|
+
props: P;
|
|
238
|
+
router: NextRouter;
|
|
239
|
+
el: HTMLElement | null;
|
|
240
|
+
computed: any;
|
|
241
|
+
}) => unknown
|
|
242
|
+
>
|
|
243
|
+
)[computedName];
|
|
244
|
+
|
|
245
|
+
if (typeof computedFn === "function") {
|
|
246
|
+
// Runtime binding – types are validated at the edge of ComponentParams
|
|
247
|
+
acc[computedName as keyof C] = computedFn.bind({
|
|
248
|
+
state: state as unknown as S,
|
|
249
|
+
props,
|
|
250
|
+
router,
|
|
251
|
+
el,
|
|
252
|
+
computed: acc,
|
|
253
|
+
})() as ReturnType<C[keyof C]>;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return acc;
|
|
257
|
+
},
|
|
258
|
+
{} as { [KC in keyof C]: ReturnType<C[KC]> },
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
const methods = Object.keys(
|
|
262
|
+
(params.methods || {}) as Record<string, unknown>,
|
|
263
|
+
).reduce(
|
|
264
|
+
(acc, methodName) => {
|
|
265
|
+
const method = (
|
|
266
|
+
params.methods as Record<string, (...args: any[]) => any>
|
|
267
|
+
)[methodName];
|
|
268
|
+
|
|
269
|
+
if (typeof method === "function") {
|
|
270
|
+
// Runtime binding – types are validated at the edge of ComponentParams
|
|
271
|
+
(acc as Record<string, (...args: any[]) => any>)[methodName] =
|
|
272
|
+
method.bind({
|
|
273
|
+
state,
|
|
274
|
+
props,
|
|
275
|
+
computed,
|
|
276
|
+
refs,
|
|
277
|
+
router,
|
|
278
|
+
methods: acc,
|
|
279
|
+
devError,
|
|
280
|
+
el,
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return acc;
|
|
285
|
+
},
|
|
286
|
+
{} as Record<string, (...args: any[]) => any>,
|
|
287
|
+
) as {
|
|
288
|
+
[K in keyof M]: (...args: Parameters<M[K]>) => ReturnType<M[K]>;
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
useEffect(() => {
|
|
292
|
+
const lifecycleBindings = {
|
|
293
|
+
methods,
|
|
294
|
+
state,
|
|
295
|
+
computed,
|
|
296
|
+
props,
|
|
297
|
+
refs,
|
|
298
|
+
router,
|
|
299
|
+
el,
|
|
300
|
+
};
|
|
301
|
+
(
|
|
302
|
+
params.lifecycle?.onMount as
|
|
303
|
+
| ((this: typeof lifecycleBindings) => void)
|
|
304
|
+
| undefined
|
|
305
|
+
)?.bind(lifecycleBindings)();
|
|
306
|
+
return () => {
|
|
307
|
+
(
|
|
308
|
+
params.lifecycle?.onUnmount as
|
|
309
|
+
| ((this: typeof lifecycleBindings) => void)
|
|
310
|
+
| undefined
|
|
311
|
+
)?.bind(lifecycleBindings)();
|
|
312
|
+
};
|
|
313
|
+
}, []);
|
|
314
|
+
|
|
315
|
+
useEffect(() => {
|
|
316
|
+
const previousProps = propsRef.current;
|
|
317
|
+
const currentProps = props as unknown as Record<string, unknown>;
|
|
318
|
+
for (const propName of Object.keys(currentProps)) {
|
|
319
|
+
const previousProp = previousProps[propName];
|
|
320
|
+
const currentProp = currentProps[propName];
|
|
321
|
+
if (!isEqual(previousProp, currentProp)) {
|
|
322
|
+
const lifecycleBindings = {
|
|
323
|
+
methods,
|
|
324
|
+
state,
|
|
325
|
+
computed,
|
|
326
|
+
props,
|
|
327
|
+
refs,
|
|
328
|
+
router,
|
|
329
|
+
devError,
|
|
330
|
+
el,
|
|
331
|
+
};
|
|
332
|
+
(
|
|
333
|
+
params.lifecycle?.onPropChange as
|
|
334
|
+
| ((
|
|
335
|
+
this: typeof lifecycleBindings,
|
|
336
|
+
propName: string,
|
|
337
|
+
previousProp: unknown,
|
|
338
|
+
currentProp: unknown,
|
|
339
|
+
) => void)
|
|
340
|
+
| undefined
|
|
341
|
+
)?.bind(lifecycleBindings)(propName, previousProp, currentProp);
|
|
342
|
+
propsRef.current[propName] = currentProp;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}, [props]);
|
|
346
|
+
|
|
347
|
+
useEffect(() => {
|
|
348
|
+
setTimeout(() => {
|
|
349
|
+
const lifecycleBindings = {
|
|
350
|
+
methods,
|
|
351
|
+
state,
|
|
352
|
+
computed,
|
|
353
|
+
props,
|
|
354
|
+
el,
|
|
355
|
+
};
|
|
356
|
+
(
|
|
357
|
+
params.lifecycle?.onRender as
|
|
358
|
+
| ((this: typeof lifecycleBindings) => void)
|
|
359
|
+
| undefined
|
|
360
|
+
)?.bind(lifecycleBindings)();
|
|
361
|
+
}, 25);
|
|
362
|
+
}, []);
|
|
363
|
+
|
|
364
|
+
useEffect(() => {
|
|
365
|
+
if (router.isReady) {
|
|
366
|
+
const lifecycleBindings = {
|
|
367
|
+
methods,
|
|
368
|
+
state,
|
|
369
|
+
computed,
|
|
370
|
+
props,
|
|
371
|
+
el,
|
|
372
|
+
};
|
|
373
|
+
(
|
|
374
|
+
params.lifecycle?.onRouterReady as
|
|
375
|
+
| ((this: typeof lifecycleBindings) => void)
|
|
376
|
+
| undefined
|
|
377
|
+
)?.bind(lifecycleBindings)();
|
|
378
|
+
}
|
|
379
|
+
}, [router.isReady]);
|
|
380
|
+
|
|
381
|
+
const mutationCallback: MutationCallback = (mutationList) => {
|
|
382
|
+
const lifecycleBindings = {
|
|
383
|
+
methods,
|
|
384
|
+
state,
|
|
385
|
+
computed,
|
|
386
|
+
props,
|
|
387
|
+
refs,
|
|
388
|
+
router,
|
|
389
|
+
el,
|
|
390
|
+
};
|
|
391
|
+
for (const mutation of mutationList) {
|
|
392
|
+
(
|
|
393
|
+
params.lifecycle?.onDomMutation as
|
|
394
|
+
| ((
|
|
395
|
+
this: typeof lifecycleBindings,
|
|
396
|
+
mutation: MutationRecord,
|
|
397
|
+
) => void)
|
|
398
|
+
| undefined
|
|
399
|
+
)?.bind(lifecycleBindings)(mutation);
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
useEffect(() => {
|
|
404
|
+
const observer = new MutationObserver(mutationCallback);
|
|
405
|
+
if (ref.current instanceof HTMLElement) {
|
|
406
|
+
observer.observe(ref.current, {
|
|
407
|
+
attributes: true,
|
|
408
|
+
childList: true,
|
|
409
|
+
subtree: true,
|
|
410
|
+
});
|
|
411
|
+
const propsWithAnimation = props as P & CommonProps;
|
|
412
|
+
if (propsWithAnimation.mount_animation) {
|
|
413
|
+
let animation: Animation;
|
|
414
|
+
switch (typeof propsWithAnimation.mount_animation) {
|
|
415
|
+
case "string": {
|
|
416
|
+
const def = MOUNT_ANIMATIONS[propsWithAnimation.mount_animation];
|
|
417
|
+
animation = ref.current.animate(
|
|
418
|
+
def.keyframes as unknown as PropertyIndexedKeyframes,
|
|
419
|
+
def.options,
|
|
420
|
+
);
|
|
421
|
+
break;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
default:
|
|
425
|
+
animation = ref.current.animate(
|
|
426
|
+
propsWithAnimation.mount_animation.keyframes,
|
|
427
|
+
propsWithAnimation.mount_animation.options,
|
|
428
|
+
);
|
|
429
|
+
break;
|
|
430
|
+
}
|
|
431
|
+
animation.onfinish = () => {
|
|
432
|
+
const lifecycleBindings = {
|
|
433
|
+
methods,
|
|
434
|
+
state,
|
|
435
|
+
computed,
|
|
436
|
+
props,
|
|
437
|
+
refs,
|
|
438
|
+
router,
|
|
439
|
+
el,
|
|
440
|
+
};
|
|
441
|
+
(
|
|
442
|
+
params.lifecycle?.onMountAnimationEnd as
|
|
443
|
+
| ((this: typeof lifecycleBindings) => void)
|
|
444
|
+
| undefined
|
|
445
|
+
)?.bind(lifecycleBindings)();
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return () => {
|
|
450
|
+
observer.disconnect();
|
|
451
|
+
};
|
|
452
|
+
}, [ref]);
|
|
453
|
+
|
|
454
|
+
const bindings = {
|
|
455
|
+
methods,
|
|
456
|
+
state,
|
|
457
|
+
computed,
|
|
458
|
+
props,
|
|
459
|
+
refs,
|
|
460
|
+
router,
|
|
461
|
+
devError,
|
|
462
|
+
el,
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
const render = params.render.bind(
|
|
466
|
+
bindings as RenderThisType<P, S, C, R, M>,
|
|
467
|
+
) as FunctionComponent<P>;
|
|
468
|
+
if (errorHtml) {
|
|
469
|
+
return <_ErrorBoundary errorHtml={errorHtml} />;
|
|
470
|
+
}
|
|
471
|
+
const jsx = render(props);
|
|
472
|
+
if (!jsx) {
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
return jsx;
|
|
476
|
+
return cloneElement(jsx as ReactElement, {
|
|
477
|
+
ref,
|
|
478
|
+
});
|
|
479
|
+
}) as ComponentReturnType<P>;
|
|
480
|
+
Fn.displayName = params.name;
|
|
481
|
+
Fn.params = params;
|
|
482
|
+
return Fn;
|
|
483
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import Dexie, { Table } from "dexie";
|
|
2
|
+
|
|
3
|
+
type StoresType = Record<string, Record<string, unknown>>;
|
|
4
|
+
type Params<S extends StoresType> = {
|
|
5
|
+
name: string;
|
|
6
|
+
version: number;
|
|
7
|
+
stores: {
|
|
8
|
+
[K in keyof S]: Array<keyof S[K]>;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
export const create_database = <S extends StoresType>(params: Params<S>) => {
|
|
12
|
+
const db = new Dexie(params.name) as Dexie & { [K in keyof S]: Table<S[K]> };
|
|
13
|
+
const stores = Object.keys(params.stores).reduce(
|
|
14
|
+
(acc, storeName) => {
|
|
15
|
+
acc[storeName as keyof S] = ["++id", ...params.stores[storeName]].join(
|
|
16
|
+
","
|
|
17
|
+
);
|
|
18
|
+
return acc;
|
|
19
|
+
},
|
|
20
|
+
{} as Record<keyof S, string>
|
|
21
|
+
);
|
|
22
|
+
db.version(params.version).stores(stores);
|
|
23
|
+
db.open();
|
|
24
|
+
return db;
|
|
25
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/* eslint-disable @next/next/no-page-custom-font */
|
|
2
|
+
/* eslint-disable @next/next/google-font-display */
|
|
3
|
+
import { ColorSchemeScript } from "@mantine/core";
|
|
4
|
+
// eslint-disable-next-line @next/next/no-document-import-in-page
|
|
5
|
+
import { Html, Head, Main, NextScript } from "next/document";
|
|
6
|
+
|
|
7
|
+
export const create_document = () => {
|
|
8
|
+
const Fn = () => {
|
|
9
|
+
return (
|
|
10
|
+
<Html lang="en">
|
|
11
|
+
<Head>
|
|
12
|
+
<ColorSchemeScript defaultColorScheme="auto" />
|
|
13
|
+
</Head>
|
|
14
|
+
<body>
|
|
15
|
+
<Main />
|
|
16
|
+
<NextScript />
|
|
17
|
+
</body>
|
|
18
|
+
</Html>
|
|
19
|
+
);
|
|
20
|
+
};
|
|
21
|
+
Fn.displayName = "Document";
|
|
22
|
+
return Fn;
|
|
23
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const _ErrorBoundary = ({ errorHtml }: { errorHtml: string }) => {
|
|
2
|
+
return (
|
|
3
|
+
<div
|
|
4
|
+
style={{
|
|
5
|
+
width: "100%",
|
|
6
|
+
height: "100%",
|
|
7
|
+
padding: 20,
|
|
8
|
+
boxShadow: "inset 0px 0px 0px 5px rgba(255,0,0,0.5)",
|
|
9
|
+
background: "rgba(255,0,0,0.1)",
|
|
10
|
+
fontFamily: "monospace",
|
|
11
|
+
fontWeight: "bolder",
|
|
12
|
+
}}
|
|
13
|
+
dangerouslySetInnerHTML={{ __html: errorHtml }}
|
|
14
|
+
/>
|
|
15
|
+
);
|
|
16
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import React, { useRef, useState, useEffect, ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
interface AutoZoomContainerProps {
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
initialScale?: number;
|
|
6
|
+
maxScale?: number;
|
|
7
|
+
step?: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const AutoZoomContainer: React.FC<AutoZoomContainerProps> = ({
|
|
11
|
+
children,
|
|
12
|
+
initialScale = 0.05,
|
|
13
|
+
maxScale = 1,
|
|
14
|
+
step = 0.05,
|
|
15
|
+
}) => {
|
|
16
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
17
|
+
const childRef = useRef<HTMLDivElement>(null);
|
|
18
|
+
const [scale, setScale] = useState<number>(initialScale);
|
|
19
|
+
|
|
20
|
+
const checkFitAndAdjust = () => {
|
|
21
|
+
const container = containerRef.current;
|
|
22
|
+
const child = childRef.current;
|
|
23
|
+
|
|
24
|
+
if (!container || !child) return;
|
|
25
|
+
|
|
26
|
+
const containerRect = container.getBoundingClientRect();
|
|
27
|
+
const childRect = child.getBoundingClientRect();
|
|
28
|
+
|
|
29
|
+
// Add tolerance to prevent oscillation
|
|
30
|
+
const tolerance = 2; // pixels
|
|
31
|
+
const widthFits = childRect.width <= containerRect.width + tolerance;
|
|
32
|
+
const heightFits = childRect.height <= containerRect.height + tolerance;
|
|
33
|
+
const fits = widthFits && heightFits;
|
|
34
|
+
|
|
35
|
+
// Calculate how much we're overflowing (if at all)
|
|
36
|
+
const widthRatio = containerRect.width / childRect.width;
|
|
37
|
+
const heightRatio = containerRect.height / childRect.height;
|
|
38
|
+
const minRatio = Math.min(widthRatio, heightRatio);
|
|
39
|
+
|
|
40
|
+
if (!fits && scale > initialScale) {
|
|
41
|
+
// Content doesn't fit, zoom out - but use calculated ratio for better convergence
|
|
42
|
+
const targetScale = Math.max(scale * minRatio * 0.95, initialScale); // 0.95 for safety margin
|
|
43
|
+
setScale(targetScale);
|
|
44
|
+
} else if (fits && scale < maxScale) {
|
|
45
|
+
// Content fits, try to zoom in more gradually
|
|
46
|
+
const nextScale = Math.min(scale + step, maxScale);
|
|
47
|
+
// But don't zoom in if we're very close to not fitting
|
|
48
|
+
if (minRatio > 1.1) {
|
|
49
|
+
// Only zoom in if we have comfortable margin
|
|
50
|
+
setScale(nextScale);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
// Initial check after mount
|
|
57
|
+
const timeoutId = setTimeout(checkFitAndAdjust, 0);
|
|
58
|
+
return () => clearTimeout(timeoutId);
|
|
59
|
+
}, []);
|
|
60
|
+
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
// React to scale change
|
|
63
|
+
const id = requestAnimationFrame(checkFitAndAdjust);
|
|
64
|
+
return () => cancelAnimationFrame(id);
|
|
65
|
+
}, [scale]);
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<div
|
|
69
|
+
ref={containerRef}
|
|
70
|
+
style={{
|
|
71
|
+
width: "100%",
|
|
72
|
+
height: "100%",
|
|
73
|
+
overflow: "hidden",
|
|
74
|
+
position: "relative",
|
|
75
|
+
display: "flex",
|
|
76
|
+
alignItems: "center",
|
|
77
|
+
justifyContent: "center",
|
|
78
|
+
}}
|
|
79
|
+
>
|
|
80
|
+
<div
|
|
81
|
+
ref={childRef}
|
|
82
|
+
style={{
|
|
83
|
+
zoom: scale,
|
|
84
|
+
transformOrigin: "top left",
|
|
85
|
+
display: "inline-block",
|
|
86
|
+
}}
|
|
87
|
+
>
|
|
88
|
+
{children}
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
};
|