@hybridly/vue 0.10.0-beta.3 → 0.10.0-beta.30
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/THIRD-PARTY-LICENSES.md +20 -0
- package/dist/_chunks/chunk.mjs +17 -15
- package/dist/index.d.mts +1062 -527
- package/dist/index.mjs +1799 -1093
- package/package.json +59 -63
package/dist/index.mjs
CHANGED
|
@@ -1,174 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
1
|
+
import { n as __reExport, t as __exportAll } from "./_chunks/chunk.mjs";
|
|
2
|
+
import { debug, merge, random, showViewComponentErrorModal, wrap } from "@hybridly/utils";
|
|
3
|
+
import { computed, createApp, defineComponent, getCurrentInstance, h, isRef, nextTick, onMounted, onUnmounted, reactive, readonly, ref, shallowRef, toRaw, toValue, triggerRef, unref, watch } from "vue";
|
|
4
|
+
import { createRouter, definePlugin, makeUrl, parseQueryString, registerHook as registerHook$1, route, route as route$1, router, router as router$1, stringifyQueryString } from "@hybridly/core";
|
|
5
|
+
import { get, set, unset } from "es-toolkit/compat";
|
|
6
|
+
import { debounce } from "es-toolkit/function";
|
|
7
|
+
import { clone, cloneDeep } from "es-toolkit/object";
|
|
8
|
+
import { isEqual } from "es-toolkit/predicate";
|
|
6
9
|
import { setupDevtoolsPlugin } from "@vue/devtools-api";
|
|
7
|
-
import
|
|
8
|
-
import { getByPath
|
|
9
|
-
import isEqual from "lodash.isequal";
|
|
10
|
-
|
|
11
|
-
//#region src/plugins/progress.ts
|
|
12
|
-
/**
|
|
13
|
-
* A plugin to display a progress bar.
|
|
14
|
-
*/
|
|
15
|
-
function progress(options) {
|
|
16
|
-
const resolved = {
|
|
17
|
-
delay: 250,
|
|
18
|
-
color: "#29d",
|
|
19
|
-
includeCSS: true,
|
|
20
|
-
spinner: false,
|
|
21
|
-
...options
|
|
22
|
-
};
|
|
23
|
-
let timeout;
|
|
24
|
-
function startProgress() {
|
|
25
|
-
nprogress.done();
|
|
26
|
-
nprogress.remove();
|
|
27
|
-
nprogress.start();
|
|
28
|
-
}
|
|
29
|
-
function finishProgress() {
|
|
30
|
-
if (nprogress.isStarted()) {
|
|
31
|
-
nprogress.done(true);
|
|
32
|
-
setTimeout(() => nprogress.remove(), 1e3);
|
|
33
|
-
}
|
|
34
|
-
clearTimeout(timeout);
|
|
35
|
-
}
|
|
36
|
-
return definePlugin({
|
|
37
|
-
name: "hybridly:progress",
|
|
38
|
-
initialized() {
|
|
39
|
-
nprogress.configure({ showSpinner: resolved.spinner });
|
|
40
|
-
if (resolved.includeCSS) injectCSS(resolved.color);
|
|
41
|
-
},
|
|
42
|
-
start: (context) => {
|
|
43
|
-
if (context.pendingNavigation?.options.progress === false) return;
|
|
44
|
-
clearTimeout(timeout);
|
|
45
|
-
timeout = setTimeout(() => startProgress(), resolved.delay);
|
|
46
|
-
},
|
|
47
|
-
progress: (progress) => {
|
|
48
|
-
if (nprogress.isStarted() && progress.percentage) nprogress.set(Math.max(nprogress.status, progress.percentage / 100 * .9));
|
|
49
|
-
},
|
|
50
|
-
after: () => finishProgress()
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
function injectCSS(color) {
|
|
54
|
-
const element = document.createElement("style");
|
|
55
|
-
element.textContent = `
|
|
56
|
-
#nprogress {
|
|
57
|
-
pointer-events: none;
|
|
58
|
-
--progress-color: ${color};
|
|
59
|
-
}
|
|
60
|
-
#nprogress .bar {
|
|
61
|
-
background: var(--progress-color);
|
|
62
|
-
position: fixed;
|
|
63
|
-
z-index: 1031;
|
|
64
|
-
top: 0;
|
|
65
|
-
left: 0;
|
|
66
|
-
width: 100%;
|
|
67
|
-
height: 2px;
|
|
68
|
-
}
|
|
69
|
-
#nprogress .peg {
|
|
70
|
-
display: block;
|
|
71
|
-
position: absolute;
|
|
72
|
-
right: 0px;
|
|
73
|
-
width: 100px;
|
|
74
|
-
height: 100%;
|
|
75
|
-
box-shadow: 0 0 10px var(--progress-color), 0 0 5px var(--progress-color);
|
|
76
|
-
opacity: 1.0;
|
|
77
|
-
-webkit-transform: rotate(3deg) translate(0px, -4px);
|
|
78
|
-
-ms-transform: rotate(3deg) translate(0px, -4px);
|
|
79
|
-
transform: rotate(3deg) translate(0px, -4px);
|
|
80
|
-
}
|
|
81
|
-
#nprogress .spinner {
|
|
82
|
-
display: block;
|
|
83
|
-
position: fixed;
|
|
84
|
-
z-index: 1031;
|
|
85
|
-
top: 15px;
|
|
86
|
-
right: 15px;
|
|
87
|
-
}
|
|
88
|
-
#nprogress .spinner-icon {
|
|
89
|
-
width: 18px;
|
|
90
|
-
height: 18px;
|
|
91
|
-
box-sizing: border-box;
|
|
92
|
-
border: solid 2px transparent;
|
|
93
|
-
border-top-color: var(--progress-color);
|
|
94
|
-
border-left-color: var(--progress-color);
|
|
95
|
-
border-radius: 50%;
|
|
96
|
-
-webkit-animation: nprogress-spinner 400ms linear infinite;
|
|
97
|
-
animation: nprogress-spinner 400ms linear infinite;
|
|
98
|
-
}
|
|
99
|
-
.nprogress-custom-parent {
|
|
100
|
-
overflow: hidden;
|
|
101
|
-
position: relative;
|
|
102
|
-
}
|
|
103
|
-
.nprogress-custom-parent #nprogress .spinner,
|
|
104
|
-
.nprogress-custom-parent #nprogress .bar {
|
|
105
|
-
position: absolute;
|
|
106
|
-
}
|
|
107
|
-
@-webkit-keyframes nprogress-spinner {
|
|
108
|
-
0% { -webkit-transform: rotate(0deg); }
|
|
109
|
-
100% { -webkit-transform: rotate(360deg); }
|
|
110
|
-
}
|
|
111
|
-
@keyframes nprogress-spinner {
|
|
112
|
-
0% { transform: rotate(0deg); }
|
|
113
|
-
100% { transform: rotate(360deg); }
|
|
114
|
-
}
|
|
115
|
-
`;
|
|
116
|
-
document.head.appendChild(element);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
//#endregion
|
|
120
|
-
//#region src/stores/dialog.ts
|
|
121
|
-
const DEBUG_KEY = "vue:state:dialog";
|
|
122
|
-
const dialogStore = {
|
|
123
|
-
state: {
|
|
124
|
-
component: shallowRef(),
|
|
125
|
-
properties: ref(),
|
|
126
|
-
key: ref(),
|
|
127
|
-
show: ref()
|
|
128
|
-
},
|
|
129
|
-
removeComponent() {
|
|
130
|
-
if (dialogStore.state.component.value) {
|
|
131
|
-
debug.adapter(DEBUG_KEY, "Removing dialog.");
|
|
132
|
-
dialogStore.state.component.value = void 0;
|
|
133
|
-
}
|
|
134
|
-
},
|
|
135
|
-
setComponent(component) {
|
|
136
|
-
debug.adapter(DEBUG_KEY, "Setting dialog component:", component);
|
|
137
|
-
dialogStore.state.component.value = component;
|
|
138
|
-
},
|
|
139
|
-
setProperties(properties) {
|
|
140
|
-
debug.adapter(DEBUG_KEY, "Setting dialog properties:", properties);
|
|
141
|
-
dialogStore.state.properties.value = unref(properties);
|
|
142
|
-
},
|
|
143
|
-
setKey(key) {
|
|
144
|
-
debug.adapter(DEBUG_KEY, "Setting dialog key:", {
|
|
145
|
-
new: key,
|
|
146
|
-
previous: dialogStore.state.key.value
|
|
147
|
-
});
|
|
148
|
-
dialogStore.state.key.value = unref(key);
|
|
149
|
-
},
|
|
150
|
-
show() {
|
|
151
|
-
if (!dialogStore.state.show.value) {
|
|
152
|
-
debug.adapter(DEBUG_KEY, "Showing the dialog.");
|
|
153
|
-
dialogStore.state.show.value = true;
|
|
154
|
-
}
|
|
155
|
-
},
|
|
156
|
-
hide() {
|
|
157
|
-
if (dialogStore.state.show.value) {
|
|
158
|
-
debug.adapter(DEBUG_KEY, "Hiding the dialog.");
|
|
159
|
-
dialogStore.state.show.value = false;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
//#endregion
|
|
165
|
-
//#region src/stores/mount.ts
|
|
166
|
-
/**
|
|
167
|
-
* These callbacks are run on the first stick after the view component is mounted.
|
|
168
|
-
*/
|
|
169
|
-
const onMountedCallbacks = [];
|
|
170
|
-
|
|
171
|
-
//#endregion
|
|
10
|
+
import nprogress from "nprogress";
|
|
11
|
+
import { getByPath } from "@clickbar/dot-diver";
|
|
172
12
|
//#region src/stores/state.ts
|
|
173
13
|
const state = {
|
|
174
14
|
context: shallowRef(),
|
|
@@ -193,204 +33,71 @@ const state = {
|
|
|
193
33
|
state.viewKey.value = unref(key);
|
|
194
34
|
}
|
|
195
35
|
};
|
|
196
|
-
|
|
197
36
|
//#endregion
|
|
198
|
-
//#region src/components/
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
nextTick(() => {
|
|
226
|
-
debug.adapter(`vue:render:${type}`, "Calling mounted callbacks.");
|
|
227
|
-
while (onMountedCallbacks.length) onMountedCallbacks.shift()?.();
|
|
228
|
-
});
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
function renderView() {
|
|
232
|
-
debug.adapter("vue:render:view", "Rendering view.");
|
|
233
|
-
state.view.value.inheritAttrs = !!state.view.value.inheritAttrs;
|
|
234
|
-
hijackOnMounted(state.view.value, "view");
|
|
235
|
-
return h(state.view.value, {
|
|
236
|
-
...state.properties.value,
|
|
237
|
-
key: state.viewKey.value
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
function renderDialog() {
|
|
241
|
-
if (dialogStore.state.component.value && dialogStore.state.properties.value) {
|
|
242
|
-
debug.adapter("vue:render:dialog", "Rendering dialog.");
|
|
243
|
-
hijackOnMounted(dialogStore.state.component.value, "dialog");
|
|
244
|
-
return h(dialogStore.state.component.value, {
|
|
245
|
-
...dialogStore.state.properties.value,
|
|
246
|
-
key: dialogStore.state.key.value
|
|
247
|
-
});
|
|
37
|
+
//#region src/components/load-state.ts
|
|
38
|
+
function keysAreBeingReloaded(only, except, keys) {
|
|
39
|
+
only = wrap(only);
|
|
40
|
+
except = wrap(except);
|
|
41
|
+
if (only.length > 0) return keys.some((key) => only.includes(key));
|
|
42
|
+
if (except.length > 0) return !keys.some((key) => except.includes(key));
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
function useLoadState(getKeys) {
|
|
46
|
+
const hasLoadedOnce = ref(getKeys().every((key) => get(state.properties.value, key) !== void 0));
|
|
47
|
+
const reloading = ref(false);
|
|
48
|
+
const activeReloads = /* @__PURE__ */ new Set();
|
|
49
|
+
let removeStartListener = null;
|
|
50
|
+
let removeFinishListener = null;
|
|
51
|
+
onMounted(() => {
|
|
52
|
+
removeStartListener = registerHook$1("start", (request) => {
|
|
53
|
+
if (hasLoadedOnce.value === false) return;
|
|
54
|
+
if (request.options.preserveState !== true) return;
|
|
55
|
+
const keys = getKeys();
|
|
56
|
+
if (!keysAreBeingReloaded(request.options.only, request.options.except, keys)) return;
|
|
57
|
+
activeReloads.add(request.id);
|
|
58
|
+
reloading.value = true;
|
|
59
|
+
});
|
|
60
|
+
removeFinishListener = registerHook$1("after", (request) => {
|
|
61
|
+
if (activeReloads.has(request.id)) {
|
|
62
|
+
activeReloads.delete(request.id);
|
|
63
|
+
reloading.value = activeReloads.size > 0;
|
|
248
64
|
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
onUnmounted(() => {
|
|
68
|
+
removeStartListener?.();
|
|
69
|
+
removeFinishListener?.();
|
|
70
|
+
activeReloads.clear();
|
|
71
|
+
});
|
|
72
|
+
function getSlotProps() {
|
|
73
|
+
const loaded = getKeys().every((key) => get(state.properties.value, key) !== void 0);
|
|
74
|
+
if (loaded) hasLoadedOnce.value = true;
|
|
75
|
+
return {
|
|
76
|
+
reloading: reloading.value,
|
|
77
|
+
loading: !loaded || reloading.value,
|
|
78
|
+
loaded
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return { getSlotProps };
|
|
82
|
+
}
|
|
83
|
+
//#endregion
|
|
84
|
+
//#region src/components/deferred.ts
|
|
85
|
+
const Deferred = defineComponent({
|
|
86
|
+
name: "Deferred",
|
|
87
|
+
props: { data: {
|
|
88
|
+
type: [String, Array],
|
|
89
|
+
required: true
|
|
90
|
+
} },
|
|
91
|
+
slots: Object,
|
|
92
|
+
setup(props, { slots }) {
|
|
93
|
+
const { getSlotProps } = useLoadState(() => wrap(props.data));
|
|
94
|
+
return () => {
|
|
95
|
+
const slotProps = getSlotProps();
|
|
96
|
+
if (!slotProps.loaded && !!slots.fallback) return slots.fallback(slotProps);
|
|
97
|
+
return slots.default?.(slotProps);
|
|
256
98
|
};
|
|
257
99
|
}
|
|
258
100
|
});
|
|
259
|
-
|
|
260
|
-
//#endregion
|
|
261
|
-
//#region src/devtools.ts
|
|
262
|
-
const hybridlyStateType = "hybridly";
|
|
263
|
-
const hybridlyEventsTimelineLayerId = "Hybridly";
|
|
264
|
-
function setupDevtools(app) {
|
|
265
|
-
setupDevtoolsPlugin({
|
|
266
|
-
id: "hybridly",
|
|
267
|
-
label: "Hybridly",
|
|
268
|
-
packageName: "@hybridly/vue",
|
|
269
|
-
homepage: "https://github.com/hybridly",
|
|
270
|
-
app,
|
|
271
|
-
enableEarlyProxy: true,
|
|
272
|
-
componentStateTypes: [hybridlyStateType]
|
|
273
|
-
}, (api) => {
|
|
274
|
-
api.on.inspectComponent((payload) => {
|
|
275
|
-
payload.instanceData.state.push({
|
|
276
|
-
type: hybridlyStateType,
|
|
277
|
-
key: "properties",
|
|
278
|
-
value: state.context.value?.view.properties,
|
|
279
|
-
editable: true
|
|
280
|
-
});
|
|
281
|
-
payload.instanceData.state.push({
|
|
282
|
-
type: hybridlyStateType,
|
|
283
|
-
key: "component",
|
|
284
|
-
value: state.context.value?.view.component
|
|
285
|
-
});
|
|
286
|
-
payload.instanceData.state.push({
|
|
287
|
-
type: hybridlyStateType,
|
|
288
|
-
key: "deferred",
|
|
289
|
-
value: state.context.value?.view.deferred
|
|
290
|
-
});
|
|
291
|
-
payload.instanceData.state.push({
|
|
292
|
-
type: hybridlyStateType,
|
|
293
|
-
key: "dialog",
|
|
294
|
-
value: state.context.value?.dialog
|
|
295
|
-
});
|
|
296
|
-
payload.instanceData.state.push({
|
|
297
|
-
type: hybridlyStateType,
|
|
298
|
-
key: "version",
|
|
299
|
-
value: state.context.value?.version
|
|
300
|
-
});
|
|
301
|
-
payload.instanceData.state.push({
|
|
302
|
-
type: hybridlyStateType,
|
|
303
|
-
key: "url",
|
|
304
|
-
value: state.context.value?.url
|
|
305
|
-
});
|
|
306
|
-
payload.instanceData.state.push({
|
|
307
|
-
type: hybridlyStateType,
|
|
308
|
-
key: "routing",
|
|
309
|
-
value: state.context.value?.routing
|
|
310
|
-
});
|
|
311
|
-
});
|
|
312
|
-
api.on.editComponentState((payload) => {
|
|
313
|
-
if (payload.type === hybridlyStateType) payload.set(state.context.value?.view);
|
|
314
|
-
});
|
|
315
|
-
api.addTimelineLayer({
|
|
316
|
-
id: hybridlyEventsTimelineLayerId,
|
|
317
|
-
color: 16501221,
|
|
318
|
-
label: "Hybridly"
|
|
319
|
-
});
|
|
320
|
-
const listen = [
|
|
321
|
-
"start",
|
|
322
|
-
"ready",
|
|
323
|
-
"data",
|
|
324
|
-
"navigating",
|
|
325
|
-
"navigated",
|
|
326
|
-
"progress",
|
|
327
|
-
"error",
|
|
328
|
-
"abort",
|
|
329
|
-
"success",
|
|
330
|
-
"invalid",
|
|
331
|
-
"exception",
|
|
332
|
-
"fail",
|
|
333
|
-
"after",
|
|
334
|
-
"backForward",
|
|
335
|
-
"success"
|
|
336
|
-
];
|
|
337
|
-
registerHook$1("before", (options) => {
|
|
338
|
-
const groupId = (Math.random() + 1).toString(36).substring(7);
|
|
339
|
-
api.addTimelineEvent({
|
|
340
|
-
layerId: hybridlyEventsTimelineLayerId,
|
|
341
|
-
event: {
|
|
342
|
-
groupId,
|
|
343
|
-
title: "before",
|
|
344
|
-
time: api.now(),
|
|
345
|
-
data: options
|
|
346
|
-
}
|
|
347
|
-
});
|
|
348
|
-
listen.forEach((event) => registerHook$1(event, (data) => {
|
|
349
|
-
api.addTimelineEvent({
|
|
350
|
-
layerId: hybridlyEventsTimelineLayerId,
|
|
351
|
-
event: {
|
|
352
|
-
groupId,
|
|
353
|
-
title: event,
|
|
354
|
-
time: api.now(),
|
|
355
|
-
data
|
|
356
|
-
}
|
|
357
|
-
});
|
|
358
|
-
if (event === "after") setTimeout(() => {
|
|
359
|
-
api.notifyComponentUpdate();
|
|
360
|
-
}, 100);
|
|
361
|
-
}, { once: true }));
|
|
362
|
-
});
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
const devtools = { install(app) {
|
|
366
|
-
if (process.env.NODE_ENV === "development" || __VUE_PROD_DEVTOOLS__) setupDevtools(app);
|
|
367
|
-
} };
|
|
368
|
-
|
|
369
|
-
//#endregion
|
|
370
|
-
//#region src/plugins/view-transition.ts
|
|
371
|
-
function viewTransition() {
|
|
372
|
-
if (!document.startViewTransition) return { name: "view-transition" };
|
|
373
|
-
let domUpdated;
|
|
374
|
-
return {
|
|
375
|
-
name: "view-transition",
|
|
376
|
-
navigating: async ({ type, hasDialog }) => {
|
|
377
|
-
if (type === "initial" || hasDialog) return;
|
|
378
|
-
return new Promise((confirmTransitionStarted) => document.startViewTransition(() => {
|
|
379
|
-
confirmTransitionStarted(true);
|
|
380
|
-
return new Promise((resolve) => domUpdated = resolve);
|
|
381
|
-
}));
|
|
382
|
-
},
|
|
383
|
-
mounted: () => {
|
|
384
|
-
domUpdated?.();
|
|
385
|
-
domUpdated = void 0;
|
|
386
|
-
},
|
|
387
|
-
navigated: () => {
|
|
388
|
-
domUpdated?.();
|
|
389
|
-
domUpdated = void 0;
|
|
390
|
-
}
|
|
391
|
-
};
|
|
392
|
-
}
|
|
393
|
-
|
|
394
101
|
//#endregion
|
|
395
102
|
//#region src/stores/form.ts
|
|
396
103
|
const formStore = {
|
|
@@ -402,230 +109,1489 @@ const formStore = {
|
|
|
402
109
|
return clone(formStore.defaultConfig);
|
|
403
110
|
}
|
|
404
111
|
};
|
|
405
|
-
|
|
406
112
|
//#endregion
|
|
407
|
-
//#region src/
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
*/
|
|
411
|
-
async function initializeHybridly(options = {}) {
|
|
412
|
-
const resolved = options;
|
|
413
|
-
const { element, payload, resolve } = prepare(resolved);
|
|
414
|
-
if (!element) throw new Error("Could not find an HTML element to initialize Vue on.");
|
|
415
|
-
state.setContext(await createRouter({
|
|
416
|
-
axios: resolved.axios,
|
|
417
|
-
plugins: resolved.plugins,
|
|
418
|
-
serializer: resolved.serializer,
|
|
419
|
-
responseErrorModals: resolved.responseErrorModals ?? process.env.NODE_ENV === "development",
|
|
420
|
-
routing: resolved.routing,
|
|
421
|
-
adapter: {
|
|
422
|
-
resolveComponent: resolve,
|
|
423
|
-
executeOnMounted: (callback) => {
|
|
424
|
-
onMountedCallbacks.push(callback);
|
|
425
|
-
},
|
|
426
|
-
onDialogClose: async () => {
|
|
427
|
-
dialogStore.hide();
|
|
428
|
-
},
|
|
429
|
-
onContextUpdate: (context) => {
|
|
430
|
-
state.setContext(context);
|
|
431
|
-
},
|
|
432
|
-
onViewSwap: async (options) => {
|
|
433
|
-
if (options.component) {
|
|
434
|
-
onMountedCallbacks.push(() => options.onMounted?.({ isDialog: false }));
|
|
435
|
-
state.setView(options.component);
|
|
436
|
-
}
|
|
437
|
-
state.setProperties(options.properties);
|
|
438
|
-
if (!options.preserveState && !options.dialog) state.setViewKey(random());
|
|
439
|
-
if (options.dialog) {
|
|
440
|
-
onMountedCallbacks.push(() => options.onMounted?.({ isDialog: true }));
|
|
441
|
-
dialogStore.setComponent(await resolve(options.dialog.component));
|
|
442
|
-
dialogStore.setProperties(options.dialog.properties);
|
|
443
|
-
dialogStore.setKey(options.dialog.key);
|
|
444
|
-
dialogStore.show();
|
|
445
|
-
} else dialogStore.hide();
|
|
446
|
-
}
|
|
447
|
-
},
|
|
448
|
-
payload
|
|
449
|
-
}));
|
|
450
|
-
const render = () => h(wrapper);
|
|
451
|
-
if (options.setup) return await options.setup({
|
|
452
|
-
element,
|
|
453
|
-
wrapper,
|
|
454
|
-
render,
|
|
455
|
-
hybridly: devtools,
|
|
456
|
-
props: { context: state.context.value },
|
|
457
|
-
payload
|
|
458
|
-
});
|
|
459
|
-
const app = createApp({ render });
|
|
460
|
-
if (resolved.devtools !== false) app.use(devtools);
|
|
461
|
-
await options.enhanceVue?.(app, payload);
|
|
462
|
-
return app.mount(element);
|
|
113
|
+
//#region src/composables/form.ts
|
|
114
|
+
function deepCloneRaw(obj) {
|
|
115
|
+
return cloneDeep(toRaw(obj));
|
|
463
116
|
}
|
|
464
|
-
function
|
|
465
|
-
|
|
466
|
-
const
|
|
467
|
-
const
|
|
468
|
-
const
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
if (!payload) throw new Error("No payload found. Are you using the `@hybridly` directive?");
|
|
472
|
-
if (options.cleanup !== false) delete element.dataset.payload;
|
|
473
|
-
debug.adapter("vue", "Resolved:", {
|
|
474
|
-
isServer,
|
|
475
|
-
element,
|
|
476
|
-
payload
|
|
477
|
-
});
|
|
478
|
-
const resolve = async (name) => {
|
|
479
|
-
debug.adapter("vue", "Resolving component", name);
|
|
480
|
-
if (!options.imported) throw new Error("No component loaded. Did you initialize Hybridly? Does `php artisan hybridly:config` return an error?");
|
|
481
|
-
return await resolveViewComponent(name, options);
|
|
482
|
-
};
|
|
483
|
-
options.plugins ??= [];
|
|
484
|
-
if (options.progress !== false) options.plugins.push(progress(typeof options.progress === "object" ? options.progress : {}));
|
|
485
|
-
if (options.viewTransition !== false) options.plugins.push(viewTransition());
|
|
486
|
-
if (options.defaultFormOptions) formStore.setDefaultConfig(options.defaultFormOptions);
|
|
487
|
-
return {
|
|
488
|
-
isServer,
|
|
489
|
-
element,
|
|
490
|
-
payload,
|
|
491
|
-
resolve
|
|
117
|
+
function useForm(options) {
|
|
118
|
+
const shouldRemember = !!options.key;
|
|
119
|
+
const historyKey = options.key ?? "form:default";
|
|
120
|
+
const historyData = shouldRemember ? router$1.history.get(historyKey) : void 0;
|
|
121
|
+
const timeoutIds = {
|
|
122
|
+
recentlyFailed: void 0,
|
|
123
|
+
recentlySuccessful: void 0
|
|
492
124
|
};
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
const
|
|
499
|
-
|
|
500
|
-
const
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
125
|
+
/** Fields that were initially set up. */
|
|
126
|
+
const defaults = ref(deepCloneRaw(options.fields));
|
|
127
|
+
/** Fields as they were when loaded. */
|
|
128
|
+
const loaded = deepCloneRaw(historyData?.fields ?? options.fields);
|
|
129
|
+
/** Current fields. */
|
|
130
|
+
const fields = reactive(deepCloneRaw(loaded));
|
|
131
|
+
/** Validation errors for each field. */
|
|
132
|
+
const errors = ref(historyData?.errors ?? {});
|
|
133
|
+
/** Whether the form is dirty. */
|
|
134
|
+
const isDirty = ref(false);
|
|
135
|
+
/** Whether the submission was recently successful. */
|
|
136
|
+
const recentlySuccessful = ref(false);
|
|
137
|
+
/** Whether the submission is successful. */
|
|
138
|
+
const successful = ref(false);
|
|
139
|
+
/** Whether the submission was recently failed. */
|
|
140
|
+
const recentlyFailed = ref(false);
|
|
141
|
+
/** Whether the submission is failed. */
|
|
142
|
+
const failed = ref(false);
|
|
143
|
+
/** Whether the submission is being processed. */
|
|
144
|
+
const processing = ref(false);
|
|
145
|
+
/** The current request's progress. */
|
|
146
|
+
const progress = ref();
|
|
147
|
+
/** The current request. */
|
|
148
|
+
const request = shallowRef();
|
|
149
|
+
/** Abort controller for the current request. */
|
|
150
|
+
let abortController;
|
|
151
|
+
/**
|
|
152
|
+
* Sets new default values for the form, so subsequent resets will use these values.
|
|
153
|
+
*/
|
|
154
|
+
function setDefault(newDefault) {
|
|
155
|
+
Object.entries(newDefault).forEach(([key, value]) => {
|
|
156
|
+
set(defaults.value, key, deepCloneRaw(value));
|
|
157
|
+
});
|
|
505
158
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
const
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
159
|
+
function resolveFieldBehaviorKeys(option, fallback) {
|
|
160
|
+
if (option === false) return;
|
|
161
|
+
if (Array.isArray(option)) return option.length > 0 ? option : void 0;
|
|
162
|
+
if (option === true) return Object.keys(fields);
|
|
163
|
+
return fallback ? Object.keys(fields) : void 0;
|
|
164
|
+
}
|
|
165
|
+
function setDefaultFromFields(option) {
|
|
166
|
+
const keys = resolveFieldBehaviorKeys(option, false);
|
|
167
|
+
if (!keys) return;
|
|
168
|
+
keys.forEach((key) => {
|
|
169
|
+
set(defaults.value, key, deepCloneRaw(get(fields, key)));
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
function resetFieldsFromBehavior(option, fallback) {
|
|
173
|
+
const keys = resolveFieldBehaviorKeys(option, fallback);
|
|
174
|
+
if (!keys) return;
|
|
175
|
+
resetFields(...keys);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Resets the form's failed and successful flags.
|
|
179
|
+
*/
|
|
180
|
+
function resetSubmissionState() {
|
|
181
|
+
successful.value = false;
|
|
182
|
+
failed.value = false;
|
|
183
|
+
recentlyFailed.value = false;
|
|
184
|
+
recentlySuccessful.value = false;
|
|
185
|
+
clearTimeout(timeoutIds.recentlySuccessful);
|
|
186
|
+
clearTimeout(timeoutIds.recentlyFailed);
|
|
187
|
+
progress.value = void 0;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Resets the fields, errors and submission state.
|
|
191
|
+
*/
|
|
192
|
+
function reset() {
|
|
193
|
+
resetSubmissionState();
|
|
194
|
+
clearErrors();
|
|
195
|
+
resetFields();
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Resets the fields to their default values.
|
|
199
|
+
*/
|
|
200
|
+
function resetFields(...keys) {
|
|
201
|
+
if (keys.length === 0) keys = Object.keys(fields);
|
|
202
|
+
keys.forEach((key) => {
|
|
203
|
+
set(fields, key, deepCloneRaw(get(defaults.value, key)));
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Clear the form fields.
|
|
208
|
+
*/
|
|
209
|
+
function clear(...keys) {
|
|
210
|
+
if (keys.length === 0) keys = Object.keys(fields);
|
|
211
|
+
keys.forEach((key) => {
|
|
212
|
+
delete fields[key];
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Submits the form.
|
|
217
|
+
*/
|
|
218
|
+
function submit(optionsOverrides) {
|
|
219
|
+
const { fields: _f, key: _k, automaticallySubmit: _as, ...optionsWithoutFields } = options;
|
|
220
|
+
const resolvedOptions = optionsOverrides ? merge(optionsWithoutFields, optionsOverrides, { mergePlainObjects: true }) : optionsWithoutFields;
|
|
221
|
+
const { timeout, resetOnSuccess, resetOnError, setDefaultOnSuccess, transform, ...requestOptions } = merge(formStore.getDefaultConfig(), resolvedOptions, { mergePlainObjects: true });
|
|
222
|
+
const url = typeof requestOptions.url === "function" ? requestOptions.url() : requestOptions.url;
|
|
223
|
+
const data = typeof transform === "function" ? transform(fields) : fields;
|
|
224
|
+
const preserveState = requestOptions.preserveState ?? requestOptions.method !== "GET";
|
|
225
|
+
const hooks = requestOptions.hooks ?? {};
|
|
226
|
+
abortController = requestOptions.abortController ?? new AbortController();
|
|
227
|
+
return router$1.navigate({
|
|
228
|
+
...requestOptions,
|
|
229
|
+
abortController,
|
|
230
|
+
url: url ?? state.context.value?.url,
|
|
231
|
+
method: requestOptions.method ?? "POST",
|
|
232
|
+
data: deepCloneRaw(data),
|
|
233
|
+
preserveState,
|
|
234
|
+
hooks: {
|
|
235
|
+
before: (_request, context) => {
|
|
236
|
+
request.value = _request;
|
|
237
|
+
resetSubmissionState();
|
|
238
|
+
return hooks.before?.(_request, context);
|
|
239
|
+
},
|
|
240
|
+
start: (request, context) => {
|
|
241
|
+
processing.value = true;
|
|
242
|
+
return hooks.start?.(request, context);
|
|
243
|
+
},
|
|
244
|
+
progress: (incoming, request, context) => {
|
|
245
|
+
progress.value = {
|
|
246
|
+
event: incoming,
|
|
247
|
+
percentage: incoming.percentage
|
|
248
|
+
};
|
|
249
|
+
return hooks.progress?.(incoming, request, context);
|
|
250
|
+
},
|
|
251
|
+
"validation-error": (incoming, request, context) => {
|
|
252
|
+
setErrors(incoming);
|
|
253
|
+
resetFieldsFromBehavior(resetOnError, false);
|
|
254
|
+
failed.value = true;
|
|
255
|
+
recentlyFailed.value = true;
|
|
256
|
+
timeoutIds.recentlyFailed = setTimeout(() => recentlyFailed.value = false, timeout ?? 5e3);
|
|
257
|
+
return hooks["validation-error"]?.(incoming, request, context);
|
|
258
|
+
},
|
|
259
|
+
success: (payload, request, response, context) => {
|
|
260
|
+
clearErrors();
|
|
261
|
+
setDefaultFromFields(setDefaultOnSuccess);
|
|
262
|
+
resetFieldsFromBehavior(resetOnSuccess, true);
|
|
263
|
+
successful.value = true;
|
|
264
|
+
recentlySuccessful.value = true;
|
|
265
|
+
timeoutIds.recentlySuccessful = setTimeout(() => recentlySuccessful.value = false, timeout ?? 5e3);
|
|
266
|
+
return hooks.success?.(payload, request, response, context);
|
|
267
|
+
},
|
|
268
|
+
after: (_request, context) => {
|
|
269
|
+
request.value = void 0;
|
|
270
|
+
progress.value = void 0;
|
|
271
|
+
processing.value = false;
|
|
272
|
+
return hooks.after?.(_request, context);
|
|
571
273
|
}
|
|
572
|
-
}
|
|
573
|
-
};
|
|
574
|
-
}
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
if (options.automaticallySubmit) {
|
|
278
|
+
const submitOptions = options.automaticallySubmit === true ? void 0 : (() => {
|
|
279
|
+
const { debounce: _debounce, ...submitOptions } = options.automaticallySubmit;
|
|
280
|
+
return submitOptions;
|
|
281
|
+
})();
|
|
282
|
+
const automaticallySubmitOptions = options.automaticallySubmit === true ? {
|
|
283
|
+
debounce: 100,
|
|
284
|
+
immediate: true
|
|
285
|
+
} : options.automaticallySubmit;
|
|
286
|
+
watch(() => fields, debounce(() => {
|
|
287
|
+
if (!isDirty.value) return;
|
|
288
|
+
submit(submitOptions);
|
|
289
|
+
}, automaticallySubmitOptions.debounce ?? 100), {
|
|
290
|
+
deep: true,
|
|
291
|
+
immediate: automaticallySubmitOptions.immediate ?? true
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Clears all errors.
|
|
296
|
+
*/
|
|
297
|
+
function clearErrors(...keys) {
|
|
298
|
+
if (keys.length === 0) keys = Object.keys(fields);
|
|
299
|
+
keys.forEach((key) => {
|
|
300
|
+
clearError(key);
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Checks if the given keys are dirty in the form.
|
|
305
|
+
*/
|
|
306
|
+
function hasDirty(...keys) {
|
|
307
|
+
if (keys.length === 0) return isDirty.value;
|
|
308
|
+
return keys.some((key) => !isEqual(toRaw(get(fields, key)), toRaw(get(defaults.value, key))));
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Clears the given field's error.
|
|
312
|
+
*/
|
|
313
|
+
function clearError(key) {
|
|
314
|
+
unset(errors.value, key);
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Sets current errors.
|
|
318
|
+
*/
|
|
319
|
+
function setErrors(incoming) {
|
|
320
|
+
clearErrors();
|
|
321
|
+
Object.entries(incoming).forEach(([path, value]) => {
|
|
322
|
+
set(errors.value, path, value);
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Aborts the submission.
|
|
327
|
+
*/
|
|
328
|
+
function abort() {
|
|
329
|
+
abortController?.abort();
|
|
330
|
+
}
|
|
331
|
+
watch([
|
|
332
|
+
fields,
|
|
333
|
+
processing,
|
|
334
|
+
errors
|
|
335
|
+
], () => {
|
|
336
|
+
isDirty.value = !isEqual(toRaw(defaults.value), toRaw(fields));
|
|
337
|
+
if (shouldRemember) router$1.history.remember(historyKey, {
|
|
338
|
+
fields: toRaw(fields),
|
|
339
|
+
errors: toRaw(errors.value)
|
|
340
|
+
});
|
|
341
|
+
}, {
|
|
342
|
+
deep: true,
|
|
343
|
+
immediate: true
|
|
344
|
+
});
|
|
345
|
+
return reactive({
|
|
346
|
+
resetFields,
|
|
347
|
+
reset,
|
|
348
|
+
resetSubmissionState,
|
|
349
|
+
clear,
|
|
350
|
+
fields,
|
|
351
|
+
abort,
|
|
352
|
+
setErrors,
|
|
353
|
+
clearErrors,
|
|
354
|
+
clearError,
|
|
355
|
+
setDefault,
|
|
356
|
+
hasDirty,
|
|
357
|
+
submit,
|
|
358
|
+
hasErrors: computed(() => Object.values(errors.value ?? {}).length > 0),
|
|
359
|
+
defaults,
|
|
360
|
+
loaded,
|
|
361
|
+
progress,
|
|
362
|
+
isDirty,
|
|
363
|
+
errors,
|
|
364
|
+
processing,
|
|
365
|
+
successful,
|
|
366
|
+
failed,
|
|
367
|
+
recentlySuccessful,
|
|
368
|
+
recentlyFailed
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
//#endregion
|
|
372
|
+
//#region src/components/form.ts
|
|
373
|
+
function isSubmitterElement(value) {
|
|
374
|
+
if (value instanceof HTMLButtonElement) return true;
|
|
375
|
+
if (value instanceof HTMLInputElement) return value.type === "submit" || value.type === "image";
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
378
|
+
function normalizeFieldName(name) {
|
|
379
|
+
const appendToArray = name.endsWith("[]");
|
|
380
|
+
return {
|
|
381
|
+
path: (appendToArray ? name.slice(0, -2) : name).replace(/\[([^\]]+)\]/g, ".$1").replace(/^\./, ""),
|
|
382
|
+
appendToArray
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
function appendFieldValue(target, path, value, forceArray) {
|
|
386
|
+
const existing = get(target, path);
|
|
387
|
+
if (existing === void 0) {
|
|
388
|
+
set(target, path, forceArray ? [value] : value);
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
if (Array.isArray(existing)) {
|
|
392
|
+
existing.push(value);
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
set(target, path, [existing, value]);
|
|
396
|
+
}
|
|
397
|
+
function isFormControl(control) {
|
|
398
|
+
return control instanceof HTMLInputElement || control instanceof HTMLTextAreaElement || control instanceof HTMLSelectElement;
|
|
399
|
+
}
|
|
400
|
+
function getControlFieldInfo(control) {
|
|
401
|
+
if (!isFormControl(control) || !control.name) return;
|
|
402
|
+
const { path, appendToArray } = normalizeFieldName(control.name);
|
|
403
|
+
if (!path.length) return;
|
|
404
|
+
return {
|
|
405
|
+
control,
|
|
406
|
+
path,
|
|
407
|
+
appendToArray
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
function setCurrentValuesAsDefaults(form, keys) {
|
|
411
|
+
for (const element of Array.from(form.elements)) {
|
|
412
|
+
const field = getControlFieldInfo(element);
|
|
413
|
+
if (keys && (!field || !keys.has(field.path))) continue;
|
|
414
|
+
if (element instanceof HTMLInputElement) {
|
|
415
|
+
if (element.type === "checkbox" || element.type === "radio") {
|
|
416
|
+
element.defaultChecked = element.checked;
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
if (element.type === "file") continue;
|
|
420
|
+
element.defaultValue = element.value;
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
if (element instanceof HTMLTextAreaElement) {
|
|
424
|
+
element.defaultValue = element.value;
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
if (element instanceof HTMLSelectElement) for (const option of Array.from(element.options)) option.defaultSelected = option.selected;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
function resetControlToDefault(control) {
|
|
431
|
+
if (control instanceof HTMLInputElement) {
|
|
432
|
+
if (control.type === "checkbox" || control.type === "radio") {
|
|
433
|
+
control.checked = control.defaultChecked;
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
if (control.type === "file") {
|
|
437
|
+
control.value = "";
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
control.value = control.defaultValue;
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
if (control instanceof HTMLTextAreaElement) {
|
|
444
|
+
control.value = control.defaultValue;
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
if (control instanceof HTMLSelectElement) for (const option of Array.from(control.options)) option.selected = option.defaultSelected;
|
|
448
|
+
}
|
|
449
|
+
function applyFieldValueToControl(control, fieldValue) {
|
|
450
|
+
if (control instanceof HTMLInputElement) {
|
|
451
|
+
if (control.type === "file") return;
|
|
452
|
+
if (control.type === "checkbox") {
|
|
453
|
+
if (Array.isArray(fieldValue)) {
|
|
454
|
+
control.checked = fieldValue.map(String).includes(control.value);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
if (typeof fieldValue === "boolean") {
|
|
458
|
+
control.checked = fieldValue;
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
control.checked = String(fieldValue ?? "") === control.value;
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
if (control.type === "radio") {
|
|
465
|
+
control.checked = String(fieldValue ?? "") === control.value;
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
control.value = fieldValue == null ? "" : String(fieldValue);
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
if (control instanceof HTMLTextAreaElement) {
|
|
472
|
+
control.value = fieldValue == null ? "" : String(fieldValue);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
if (control instanceof HTMLSelectElement) {
|
|
476
|
+
if (control.multiple && Array.isArray(fieldValue)) {
|
|
477
|
+
const values = new Set(fieldValue.map((value) => String(value)));
|
|
478
|
+
for (const option of Array.from(control.options)) option.selected = values.has(option.value);
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
control.value = fieldValue == null ? "" : String(fieldValue);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
function syncControlsFromFields(form, fields) {
|
|
485
|
+
const pathIndexes = /* @__PURE__ */ new Map();
|
|
486
|
+
for (const control of Array.from(form.elements)) {
|
|
487
|
+
const field = getControlFieldInfo(control);
|
|
488
|
+
if (!field) continue;
|
|
489
|
+
const value = get(fields, field.path);
|
|
490
|
+
if (field.appendToArray && Array.isArray(value)) {
|
|
491
|
+
const currentIndex = pathIndexes.get(field.path) ?? 0;
|
|
492
|
+
applyFieldValueToControl(control, value[currentIndex]);
|
|
493
|
+
pathIndexes.set(field.path, currentIndex + 1);
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
applyFieldValueToControl(control, value);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
const Form = defineComponent({
|
|
500
|
+
name: "Form",
|
|
501
|
+
inheritAttrs: false,
|
|
575
502
|
props: {
|
|
576
|
-
|
|
503
|
+
action: {
|
|
577
504
|
type: String,
|
|
578
505
|
required: false,
|
|
579
506
|
default: void 0
|
|
580
507
|
},
|
|
581
|
-
as: {
|
|
582
|
-
type: [String, Object],
|
|
583
|
-
default: "a"
|
|
584
|
-
},
|
|
585
508
|
method: {
|
|
586
509
|
type: String,
|
|
587
|
-
default: "
|
|
588
|
-
},
|
|
589
|
-
data: {
|
|
590
|
-
type: Object,
|
|
591
|
-
default: () => ({})
|
|
592
|
-
},
|
|
593
|
-
external: {
|
|
594
|
-
type: Boolean,
|
|
595
|
-
default: false
|
|
596
|
-
},
|
|
597
|
-
disabled: {
|
|
598
|
-
type: Boolean,
|
|
599
|
-
default: false
|
|
510
|
+
default: "POST"
|
|
600
511
|
},
|
|
601
512
|
options: {
|
|
602
513
|
type: Object,
|
|
603
514
|
default: () => ({})
|
|
604
515
|
},
|
|
605
|
-
|
|
516
|
+
errorBag: {
|
|
606
517
|
type: String,
|
|
607
518
|
required: false,
|
|
608
519
|
default: void 0
|
|
609
520
|
},
|
|
610
|
-
|
|
611
|
-
type: [Boolean, String],
|
|
612
|
-
default: false
|
|
613
|
-
},
|
|
614
|
-
preserveScroll: {
|
|
521
|
+
showProgress: {
|
|
615
522
|
type: Boolean,
|
|
523
|
+
required: false,
|
|
616
524
|
default: void 0
|
|
617
525
|
},
|
|
618
|
-
|
|
526
|
+
disableWhileProcessing: {
|
|
619
527
|
type: Boolean,
|
|
620
|
-
default:
|
|
528
|
+
default: false
|
|
529
|
+
},
|
|
530
|
+
resetOnSuccess: {
|
|
531
|
+
type: [Boolean, Array],
|
|
532
|
+
default: true
|
|
533
|
+
},
|
|
534
|
+
resetOnError: {
|
|
535
|
+
type: [Boolean, Array],
|
|
536
|
+
default: false
|
|
537
|
+
},
|
|
538
|
+
setDefaultOnSuccess: {
|
|
539
|
+
type: [Boolean, Array],
|
|
540
|
+
default: false
|
|
541
|
+
}
|
|
542
|
+
},
|
|
543
|
+
slots: Object,
|
|
544
|
+
setup(props, { attrs, slots }) {
|
|
545
|
+
const element = ref();
|
|
546
|
+
const form = useForm({
|
|
547
|
+
fields: {},
|
|
548
|
+
url: () => props.action ?? element.value?.action ?? window.location.href,
|
|
549
|
+
method: props.method,
|
|
550
|
+
errorBag: props.errorBag,
|
|
551
|
+
progress: props.showProgress,
|
|
552
|
+
resetOnSuccess: props.resetOnSuccess,
|
|
553
|
+
resetOnError: props.resetOnError,
|
|
554
|
+
setDefaultOnSuccess: props.setDefaultOnSuccess
|
|
555
|
+
});
|
|
556
|
+
function collectFormFields(submitter) {
|
|
557
|
+
if (!element.value) return {};
|
|
558
|
+
const formData = submitter ? new FormData(element.value, submitter) : new FormData(element.value);
|
|
559
|
+
const fields = {};
|
|
560
|
+
for (const [name, value] of formData.entries()) {
|
|
561
|
+
const { path, appendToArray } = normalizeFieldName(name);
|
|
562
|
+
if (!path.length) continue;
|
|
563
|
+
appendFieldValue(fields, path, value, appendToArray);
|
|
564
|
+
}
|
|
565
|
+
return fields;
|
|
566
|
+
}
|
|
567
|
+
function collectDefaultFormFields() {
|
|
568
|
+
if (!element.value) return {};
|
|
569
|
+
const fields = {};
|
|
570
|
+
for (const control of Array.from(element.value.elements)) {
|
|
571
|
+
const field = getControlFieldInfo(control);
|
|
572
|
+
if (!field) continue;
|
|
573
|
+
if (control instanceof HTMLInputElement) {
|
|
574
|
+
if (control.type === "file") continue;
|
|
575
|
+
if (control.type === "checkbox" || control.type === "radio") {
|
|
576
|
+
if (control.defaultChecked) appendFieldValue(fields, field.path, control.value, field.appendToArray);
|
|
577
|
+
continue;
|
|
578
|
+
}
|
|
579
|
+
appendFieldValue(fields, field.path, control.defaultValue, field.appendToArray);
|
|
580
|
+
continue;
|
|
581
|
+
}
|
|
582
|
+
if (control instanceof HTMLTextAreaElement) {
|
|
583
|
+
appendFieldValue(fields, field.path, control.defaultValue, field.appendToArray);
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
if (control instanceof HTMLSelectElement) {
|
|
587
|
+
if (control.multiple) {
|
|
588
|
+
for (const option of Array.from(control.options)) if (option.defaultSelected) appendFieldValue(fields, field.path, option.value, field.appendToArray);
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
const defaultOption = Array.from(control.options).find((option) => option.defaultSelected) ?? control.options.item(0);
|
|
592
|
+
if (defaultOption) appendFieldValue(fields, field.path, defaultOption.value, field.appendToArray);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
return fields;
|
|
596
|
+
}
|
|
597
|
+
function syncDefaultsFromNativeControls() {
|
|
598
|
+
if (!element.value) return;
|
|
599
|
+
const defaultFields = collectDefaultFormFields();
|
|
600
|
+
const nextDefaults = {};
|
|
601
|
+
for (const control of Array.from(element.value.elements)) {
|
|
602
|
+
const field = getControlFieldInfo(control);
|
|
603
|
+
if (!field) continue;
|
|
604
|
+
nextDefaults[field.path] = get(defaultFields, field.path);
|
|
605
|
+
}
|
|
606
|
+
form.setDefault(nextDefaults);
|
|
607
|
+
}
|
|
608
|
+
function syncFields(fields) {
|
|
609
|
+
const target = form.fields;
|
|
610
|
+
Object.keys(target).forEach((key) => {
|
|
611
|
+
delete target[key];
|
|
612
|
+
});
|
|
613
|
+
Object.entries(fields).forEach(([key, value]) => {
|
|
614
|
+
target[key] = value;
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
function resetFields(...keys) {
|
|
618
|
+
if (!element.value) return;
|
|
619
|
+
if (keys.length === 0) element.value.reset();
|
|
620
|
+
else {
|
|
621
|
+
const keySet = new Set(keys);
|
|
622
|
+
for (const control of Array.from(element.value.elements)) {
|
|
623
|
+
const field = getControlFieldInfo(control);
|
|
624
|
+
if (!field || !keySet.has(field.path)) continue;
|
|
625
|
+
resetControlToDefault(control);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
syncFields(collectFormFields());
|
|
629
|
+
}
|
|
630
|
+
function reset() {
|
|
631
|
+
form.resetSubmissionState();
|
|
632
|
+
form.clearErrors();
|
|
633
|
+
resetFields();
|
|
634
|
+
}
|
|
635
|
+
function submit(options, submitter) {
|
|
636
|
+
syncFields(collectFormFields(submitter));
|
|
637
|
+
syncDefaultsFromNativeControls();
|
|
638
|
+
const { hooks, ...rest } = merge({
|
|
639
|
+
errorBag: props.errorBag,
|
|
640
|
+
progress: props.showProgress,
|
|
641
|
+
resetOnSuccess: props.resetOnSuccess,
|
|
642
|
+
resetOnError: props.resetOnError,
|
|
643
|
+
setDefaultOnSuccess: props.setDefaultOnSuccess
|
|
644
|
+
}, {
|
|
645
|
+
...props.options,
|
|
646
|
+
...options
|
|
647
|
+
}, { mergePlainObjects: true });
|
|
648
|
+
const submitOptions = {
|
|
649
|
+
...rest,
|
|
650
|
+
hooks: {
|
|
651
|
+
...hooks,
|
|
652
|
+
"validation-error": (incoming, request, context) => {
|
|
653
|
+
nextTick(() => {
|
|
654
|
+
if (!element.value) return;
|
|
655
|
+
syncControlsFromFields(element.value, form.fields);
|
|
656
|
+
});
|
|
657
|
+
return hooks?.["validation-error"]?.(incoming, request, context);
|
|
658
|
+
},
|
|
659
|
+
success: (payload, request, response, context) => {
|
|
660
|
+
if (element.value && rest.setDefaultOnSuccess !== false) {
|
|
661
|
+
const selectedKeys = Array.isArray(rest.setDefaultOnSuccess) ? new Set(rest.setDefaultOnSuccess) : void 0;
|
|
662
|
+
setCurrentValuesAsDefaults(element.value, selectedKeys);
|
|
663
|
+
syncDefaultsFromNativeControls();
|
|
664
|
+
}
|
|
665
|
+
nextTick(() => {
|
|
666
|
+
if (!element.value) return;
|
|
667
|
+
syncControlsFromFields(element.value, form.fields);
|
|
668
|
+
});
|
|
669
|
+
return hooks?.success?.(payload, request, response, context);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
return form.submit(submitOptions);
|
|
674
|
+
}
|
|
675
|
+
function onSubmit(event) {
|
|
676
|
+
event.preventDefault();
|
|
677
|
+
return submit(void 0, isSubmitterElement(event.submitter) ? event.submitter : void 0);
|
|
678
|
+
}
|
|
679
|
+
return () => h("form", {
|
|
680
|
+
...attrs,
|
|
681
|
+
ref: element,
|
|
682
|
+
action: props.action,
|
|
683
|
+
method: props.method,
|
|
684
|
+
inert: props.disableWhileProcessing && form.processing ? "" : void 0,
|
|
685
|
+
onSubmit
|
|
686
|
+
}, slots.default?.({
|
|
687
|
+
...form,
|
|
688
|
+
errors: form.errors,
|
|
689
|
+
getError: (key) => get(form.errors, key),
|
|
690
|
+
submit,
|
|
691
|
+
resetFields,
|
|
692
|
+
reset
|
|
693
|
+
}));
|
|
694
|
+
}
|
|
695
|
+
});
|
|
696
|
+
//#endregion
|
|
697
|
+
//#region src/components/link.ts
|
|
698
|
+
const RouterLink = defineComponent({
|
|
699
|
+
name: "RouterLink",
|
|
700
|
+
setup(_, { slots, attrs }) {
|
|
701
|
+
return (props) => {
|
|
702
|
+
let data = props.data ?? {};
|
|
703
|
+
const mode = props.mode;
|
|
704
|
+
const preserveScroll = props.preserveScroll;
|
|
705
|
+
const preserveState = props.preserveState;
|
|
706
|
+
const url = makeUrl(props.href ?? "");
|
|
707
|
+
const method = props.method?.toUpperCase() ?? "GET";
|
|
708
|
+
const as = typeof props.as === "object" ? props.as : props.as?.toLowerCase() ?? "a";
|
|
709
|
+
if (method === "GET") {
|
|
710
|
+
debug.adapter("vue", "Moving data object to URL parameters.");
|
|
711
|
+
url.search = stringifyQueryString(merge(data, parseQueryString(url.search)), { arrayFormat: "indices" });
|
|
712
|
+
data = {};
|
|
713
|
+
}
|
|
714
|
+
if (as === "a" && method !== "GET") debug.adapter("vue", `Creating POST/PUT/PATCH/DELETE <a> links is discouraged as it causes "Open Link in New Tab/Window" accessibility issues.\n\nPlease specify a more appropriate element using the "as" attribute. For example:\n\n<RouterLink href="${url}" method="${method}" as="button">...</RouterLink>`);
|
|
715
|
+
return h(props.as, {
|
|
716
|
+
...attrs,
|
|
717
|
+
...as === "a" ? { href: url } : {},
|
|
718
|
+
...props.disabled ? { disabled: props.disabled } : {},
|
|
719
|
+
onAuxclick: (event) => {
|
|
720
|
+
if (props.disabled) event.preventDefault();
|
|
721
|
+
},
|
|
722
|
+
onClick: (event) => {
|
|
723
|
+
if (props.disabled) {
|
|
724
|
+
event.preventDefault();
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
if (props.external) return;
|
|
728
|
+
if (!shouldIntercept(event)) return;
|
|
729
|
+
event.preventDefault();
|
|
730
|
+
router$1.navigate({
|
|
731
|
+
url,
|
|
732
|
+
data,
|
|
733
|
+
method,
|
|
734
|
+
mode,
|
|
735
|
+
preserveState: preserveState ?? (method !== "GET" ? true : void 0),
|
|
736
|
+
preserveScroll,
|
|
737
|
+
...props.options
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
}, slots.default ? slots : props.text);
|
|
741
|
+
};
|
|
742
|
+
},
|
|
743
|
+
props: {
|
|
744
|
+
href: {
|
|
745
|
+
type: String,
|
|
746
|
+
required: false,
|
|
747
|
+
default: void 0
|
|
748
|
+
},
|
|
749
|
+
as: {
|
|
750
|
+
type: [String, Object],
|
|
751
|
+
default: "a"
|
|
752
|
+
},
|
|
753
|
+
method: {
|
|
754
|
+
type: String,
|
|
755
|
+
default: "GET"
|
|
756
|
+
},
|
|
757
|
+
data: {
|
|
758
|
+
type: Object,
|
|
759
|
+
default: () => ({})
|
|
760
|
+
},
|
|
761
|
+
external: {
|
|
762
|
+
type: Boolean,
|
|
763
|
+
default: false
|
|
764
|
+
},
|
|
765
|
+
disabled: {
|
|
766
|
+
type: Boolean,
|
|
767
|
+
default: false
|
|
768
|
+
},
|
|
769
|
+
options: {
|
|
770
|
+
type: Object,
|
|
771
|
+
default: () => ({})
|
|
772
|
+
},
|
|
773
|
+
text: {
|
|
774
|
+
type: String,
|
|
775
|
+
required: false,
|
|
776
|
+
default: void 0
|
|
777
|
+
},
|
|
778
|
+
preload: {
|
|
779
|
+
type: [Boolean, String],
|
|
780
|
+
default: false
|
|
781
|
+
},
|
|
782
|
+
preserveScroll: {
|
|
783
|
+
type: Boolean,
|
|
784
|
+
default: void 0
|
|
785
|
+
},
|
|
786
|
+
preserveState: {
|
|
787
|
+
type: Boolean,
|
|
788
|
+
default: void 0
|
|
789
|
+
},
|
|
790
|
+
mode: {
|
|
791
|
+
type: String,
|
|
792
|
+
default: void 0
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
});
|
|
796
|
+
function shouldIntercept(event) {
|
|
797
|
+
const isLink = event.currentTarget.tagName.toLowerCase() === "a";
|
|
798
|
+
return !(event.target && (event?.target).isContentEditable || event.defaultPrevented || isLink && event.which > 1 || isLink && event.altKey || isLink && event.ctrlKey || isLink && event.metaKey || isLink && event.shiftKey);
|
|
799
|
+
}
|
|
800
|
+
//#endregion
|
|
801
|
+
//#region src/components/when-visible.ts
|
|
802
|
+
const WhenVisible = defineComponent({
|
|
803
|
+
name: "WhenVisible",
|
|
804
|
+
emits: { loading: (_state) => true },
|
|
805
|
+
props: {
|
|
806
|
+
as: {
|
|
807
|
+
type: String,
|
|
808
|
+
default: "div"
|
|
809
|
+
},
|
|
810
|
+
data: {
|
|
811
|
+
type: [String, Array],
|
|
812
|
+
required: true
|
|
813
|
+
},
|
|
814
|
+
buffer: {
|
|
815
|
+
type: Number,
|
|
816
|
+
default: 0
|
|
817
|
+
},
|
|
818
|
+
root: {
|
|
819
|
+
type: [String, Object],
|
|
820
|
+
default: null
|
|
821
|
+
},
|
|
822
|
+
once: {
|
|
823
|
+
type: Boolean,
|
|
824
|
+
default: true
|
|
825
|
+
},
|
|
826
|
+
fallbackOnHidden: {
|
|
827
|
+
type: Boolean,
|
|
828
|
+
default: false
|
|
829
|
+
},
|
|
830
|
+
options: {
|
|
831
|
+
type: Object,
|
|
832
|
+
default: () => ({})
|
|
833
|
+
}
|
|
834
|
+
},
|
|
835
|
+
slots: Object,
|
|
836
|
+
setup(properties, { emit, slots }) {
|
|
837
|
+
const isVisible = ref(false);
|
|
838
|
+
const element = ref(null);
|
|
839
|
+
const hasRan = ref(false);
|
|
840
|
+
let observer;
|
|
841
|
+
const { getSlotProps } = useLoadState(() => wrap(properties.data));
|
|
842
|
+
function registerObserver() {
|
|
843
|
+
observer?.disconnect();
|
|
844
|
+
const root = typeof properties.root === "string" ? document.querySelector(properties.root) : properties.root;
|
|
845
|
+
observer = new IntersectionObserver(([entry]) => isVisible.value = entry.isIntersecting, {
|
|
846
|
+
root,
|
|
847
|
+
rootMargin: `${properties.buffer}px 0px ${properties.buffer}px 0px`
|
|
848
|
+
});
|
|
849
|
+
if (element.value) observer.observe(element.value);
|
|
850
|
+
}
|
|
851
|
+
onMounted(() => {
|
|
852
|
+
registerObserver();
|
|
853
|
+
watch(() => [properties.buffer, properties.root], () => {
|
|
854
|
+
registerObserver();
|
|
855
|
+
});
|
|
856
|
+
});
|
|
857
|
+
onUnmounted(() => {
|
|
858
|
+
observer?.disconnect();
|
|
859
|
+
});
|
|
860
|
+
watch(isVisible, (visible) => {
|
|
861
|
+
if (hasRan.value && properties.once) return;
|
|
862
|
+
if (visible) {
|
|
863
|
+
emit("loading", getSlotProps());
|
|
864
|
+
router$1.reload({
|
|
865
|
+
...properties.options,
|
|
866
|
+
only: properties.data
|
|
867
|
+
});
|
|
868
|
+
hasRan.value = true;
|
|
869
|
+
}
|
|
870
|
+
});
|
|
871
|
+
return () => {
|
|
872
|
+
const slotProperties = getSlotProps();
|
|
873
|
+
const shouldRender = slotProperties.loaded && (isVisible.value || !properties.fallbackOnHidden);
|
|
874
|
+
return h(properties.as, { ref: element }, [shouldRender ? slots.default?.(slotProperties) : slots.fallback?.(slotProperties)]);
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
});
|
|
878
|
+
//#endregion
|
|
879
|
+
//#region src/stores/dialog.ts
|
|
880
|
+
const DEBUG_KEY = "vue:state:dialog";
|
|
881
|
+
const dialogStore = {
|
|
882
|
+
state: {
|
|
883
|
+
component: shallowRef(),
|
|
884
|
+
properties: ref(),
|
|
885
|
+
key: ref(),
|
|
886
|
+
show: ref()
|
|
887
|
+
},
|
|
888
|
+
removeComponent() {
|
|
889
|
+
if (dialogStore.state.component.value) {
|
|
890
|
+
debug.adapter(DEBUG_KEY, "Removing dialog.");
|
|
891
|
+
dialogStore.state.component.value = void 0;
|
|
892
|
+
}
|
|
893
|
+
},
|
|
894
|
+
setComponent(component) {
|
|
895
|
+
debug.adapter(DEBUG_KEY, "Setting dialog component:", component);
|
|
896
|
+
dialogStore.state.component.value = component;
|
|
897
|
+
},
|
|
898
|
+
setProperties(properties) {
|
|
899
|
+
debug.adapter(DEBUG_KEY, "Setting dialog properties:", properties);
|
|
900
|
+
dialogStore.state.properties.value = unref(properties);
|
|
901
|
+
},
|
|
902
|
+
setKey(key) {
|
|
903
|
+
debug.adapter(DEBUG_KEY, "Setting dialog key:", {
|
|
904
|
+
new: key,
|
|
905
|
+
previous: dialogStore.state.key.value
|
|
906
|
+
});
|
|
907
|
+
dialogStore.state.key.value = unref(key);
|
|
908
|
+
},
|
|
909
|
+
show() {
|
|
910
|
+
if (!dialogStore.state.show.value) {
|
|
911
|
+
debug.adapter(DEBUG_KEY, "Showing the dialog.");
|
|
912
|
+
dialogStore.state.show.value = true;
|
|
913
|
+
}
|
|
914
|
+
},
|
|
915
|
+
hide() {
|
|
916
|
+
if (dialogStore.state.show.value) {
|
|
917
|
+
debug.adapter(DEBUG_KEY, "Hiding the dialog.");
|
|
918
|
+
dialogStore.state.show.value = false;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
};
|
|
922
|
+
//#endregion
|
|
923
|
+
//#region src/stores/mount.ts
|
|
924
|
+
/**
|
|
925
|
+
* These callbacks are run on the first stick after the view component is mounted.
|
|
926
|
+
*/
|
|
927
|
+
const onMountedCallbacks = [];
|
|
928
|
+
//#endregion
|
|
929
|
+
//#region src/components/wrapper.ts
|
|
930
|
+
const wrapper = defineComponent({
|
|
931
|
+
name: "Hybridly",
|
|
932
|
+
setup() {
|
|
933
|
+
function renderLayout(view) {
|
|
934
|
+
debug.adapter("vue:render:layout", "Rendering layout.");
|
|
935
|
+
if (typeof state.view.value?.layout === "function") return state.view.value.layout(h, view, renderDialog(), {
|
|
936
|
+
...state.view.value?.properties ?? {},
|
|
937
|
+
...state.properties.value
|
|
938
|
+
});
|
|
939
|
+
if (Array.isArray(state.view.value?.layout)) return [state.view.value.layout.concat(view).reverse().reduce((child, layout) => {
|
|
940
|
+
layout.inheritAttrs = !!layout.inheritAttrs;
|
|
941
|
+
return h(layout, {
|
|
942
|
+
...state.view.value?.properties ?? {},
|
|
943
|
+
...state.properties.value
|
|
944
|
+
}, () => child);
|
|
945
|
+
}), renderDialog()];
|
|
946
|
+
return [h(state.view.value?.layout, {
|
|
947
|
+
...state.view.value?.properties ?? {},
|
|
948
|
+
...state.properties.value
|
|
949
|
+
}, () => view), renderDialog()];
|
|
950
|
+
}
|
|
951
|
+
function hijackOnMounted(component, type) {
|
|
952
|
+
if (!component) return;
|
|
953
|
+
const actual = component?.mounted;
|
|
954
|
+
component.mounted = () => {
|
|
955
|
+
actual?.();
|
|
956
|
+
nextTick(() => {
|
|
957
|
+
debug.adapter(`vue:render:${type}`, "Calling mounted callbacks.");
|
|
958
|
+
while (onMountedCallbacks.length) onMountedCallbacks.shift()?.();
|
|
959
|
+
});
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
function renderView() {
|
|
963
|
+
debug.adapter("vue:render:view", "Rendering view.");
|
|
964
|
+
state.view.value.inheritAttrs = !!state.view.value.inheritAttrs;
|
|
965
|
+
hijackOnMounted(state.view.value, "view");
|
|
966
|
+
return h(state.view.value, {
|
|
967
|
+
...state.properties.value,
|
|
968
|
+
key: state.viewKey.value
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
function renderDialog() {
|
|
972
|
+
if (dialogStore.state.component.value && dialogStore.state.properties.value) {
|
|
973
|
+
debug.adapter("vue:render:dialog", "Rendering dialog.");
|
|
974
|
+
hijackOnMounted(dialogStore.state.component.value, "dialog");
|
|
975
|
+
return h(dialogStore.state.component.value, {
|
|
976
|
+
...dialogStore.state.properties.value,
|
|
977
|
+
key: dialogStore.state.key.value
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
return (...a) => {
|
|
982
|
+
if (!state.view.value) return;
|
|
983
|
+
debug.adapter("vue:render:wrapper", "Rendering wrapper component.", a.map(toRaw));
|
|
984
|
+
const view = renderView();
|
|
985
|
+
if (state.view.value.layout) return renderLayout(view);
|
|
986
|
+
return [view, renderDialog()];
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
});
|
|
990
|
+
//#endregion
|
|
991
|
+
//#region src/default-layout.ts
|
|
992
|
+
function resolveDefaultLayout(layout) {
|
|
993
|
+
if (!layout) return;
|
|
994
|
+
if (Array.isArray(layout)) return layout;
|
|
995
|
+
if (typeof layout === "function" && layout.length === 0) return layout();
|
|
996
|
+
return layout;
|
|
997
|
+
}
|
|
998
|
+
function applyDefaultLayout(component, layout) {
|
|
999
|
+
if (!layout) return component;
|
|
1000
|
+
const resolved = component;
|
|
1001
|
+
if (resolved.layout === void 0) resolved.layout = layout;
|
|
1002
|
+
return component;
|
|
1003
|
+
}
|
|
1004
|
+
//#endregion
|
|
1005
|
+
//#region src/devtools.ts
|
|
1006
|
+
const hybridlyStateType = "hybridly";
|
|
1007
|
+
const hybridlyEventsTimelineLayerId = "Hybridly";
|
|
1008
|
+
function setupDevtools(app) {
|
|
1009
|
+
setupDevtoolsPlugin({
|
|
1010
|
+
id: "hybridly",
|
|
1011
|
+
label: "Hybridly",
|
|
1012
|
+
packageName: "@hybridly/vue",
|
|
1013
|
+
homepage: "https://github.com/hybridly",
|
|
1014
|
+
app,
|
|
1015
|
+
enableEarlyProxy: true,
|
|
1016
|
+
componentStateTypes: [hybridlyStateType]
|
|
1017
|
+
}, (api) => {
|
|
1018
|
+
api.on.inspectComponent((payload) => {
|
|
1019
|
+
payload.instanceData.state.push({
|
|
1020
|
+
type: hybridlyStateType,
|
|
1021
|
+
key: "properties",
|
|
1022
|
+
value: state.context.value?.view.properties,
|
|
1023
|
+
editable: true
|
|
1024
|
+
});
|
|
1025
|
+
payload.instanceData.state.push({
|
|
1026
|
+
type: hybridlyStateType,
|
|
1027
|
+
key: "validation",
|
|
1028
|
+
value: state.context.value?.validation
|
|
1029
|
+
});
|
|
1030
|
+
payload.instanceData.state.push({
|
|
1031
|
+
type: hybridlyStateType,
|
|
1032
|
+
key: "component",
|
|
1033
|
+
value: state.context.value?.view.component
|
|
1034
|
+
});
|
|
1035
|
+
payload.instanceData.state.push({
|
|
1036
|
+
type: hybridlyStateType,
|
|
1037
|
+
key: "deferred",
|
|
1038
|
+
value: state.context.value?.view.deferred
|
|
1039
|
+
});
|
|
1040
|
+
payload.instanceData.state.push({
|
|
1041
|
+
type: hybridlyStateType,
|
|
1042
|
+
key: "dialog",
|
|
1043
|
+
value: state.context.value?.dialog
|
|
1044
|
+
});
|
|
1045
|
+
payload.instanceData.state.push({
|
|
1046
|
+
type: hybridlyStateType,
|
|
1047
|
+
key: "version",
|
|
1048
|
+
value: state.context.value?.version
|
|
1049
|
+
});
|
|
1050
|
+
payload.instanceData.state.push({
|
|
1051
|
+
type: hybridlyStateType,
|
|
1052
|
+
key: "url",
|
|
1053
|
+
value: state.context.value?.url
|
|
1054
|
+
});
|
|
1055
|
+
payload.instanceData.state.push({
|
|
1056
|
+
type: hybridlyStateType,
|
|
1057
|
+
key: "routing",
|
|
1058
|
+
value: state.context.value?.routing
|
|
1059
|
+
});
|
|
1060
|
+
});
|
|
1061
|
+
api.on.editComponentState((payload) => {
|
|
1062
|
+
if (payload.type === hybridlyStateType) payload.set(state.context.value?.view);
|
|
1063
|
+
});
|
|
1064
|
+
api.addTimelineLayer({
|
|
1065
|
+
id: hybridlyEventsTimelineLayerId,
|
|
1066
|
+
color: 16501221,
|
|
1067
|
+
label: "Hybridly"
|
|
1068
|
+
});
|
|
1069
|
+
const listen = [
|
|
1070
|
+
"start",
|
|
1071
|
+
"ready",
|
|
1072
|
+
"data",
|
|
1073
|
+
"navigating",
|
|
1074
|
+
"navigated",
|
|
1075
|
+
"progress",
|
|
1076
|
+
"validation-error",
|
|
1077
|
+
"abort",
|
|
1078
|
+
"success",
|
|
1079
|
+
"invalid",
|
|
1080
|
+
"exception",
|
|
1081
|
+
"fail",
|
|
1082
|
+
"after",
|
|
1083
|
+
"backForward",
|
|
1084
|
+
"success"
|
|
1085
|
+
];
|
|
1086
|
+
registerHook$1("before", (options) => {
|
|
1087
|
+
const groupId = (Math.random() + 1).toString(36).substring(7);
|
|
1088
|
+
api.addTimelineEvent({
|
|
1089
|
+
layerId: hybridlyEventsTimelineLayerId,
|
|
1090
|
+
event: {
|
|
1091
|
+
groupId,
|
|
1092
|
+
title: "before",
|
|
1093
|
+
time: api.now(),
|
|
1094
|
+
data: options
|
|
1095
|
+
}
|
|
1096
|
+
});
|
|
1097
|
+
listen.forEach((event) => registerHook$1(event, (data) => {
|
|
1098
|
+
api.addTimelineEvent({
|
|
1099
|
+
layerId: hybridlyEventsTimelineLayerId,
|
|
1100
|
+
event: {
|
|
1101
|
+
groupId,
|
|
1102
|
+
title: event,
|
|
1103
|
+
time: api.now(),
|
|
1104
|
+
data
|
|
1105
|
+
}
|
|
1106
|
+
});
|
|
1107
|
+
if (event === "after") setTimeout(() => {
|
|
1108
|
+
api.notifyComponentUpdate();
|
|
1109
|
+
}, 100);
|
|
1110
|
+
}, { once: true }));
|
|
1111
|
+
});
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
const devtools = { install(app) {
|
|
1115
|
+
if (process.env.NODE_ENV === "development" || __VUE_PROD_DEVTOOLS__) setupDevtools(app);
|
|
1116
|
+
} };
|
|
1117
|
+
//#endregion
|
|
1118
|
+
//#region src/plugins/progress.ts
|
|
1119
|
+
/**
|
|
1120
|
+
* A plugin to display a progress bar.
|
|
1121
|
+
*/
|
|
1122
|
+
function progress(options) {
|
|
1123
|
+
const resolved = {
|
|
1124
|
+
delay: 250,
|
|
1125
|
+
color: "#29d",
|
|
1126
|
+
includeCSS: true,
|
|
1127
|
+
spinner: false,
|
|
1128
|
+
...options
|
|
1129
|
+
};
|
|
1130
|
+
let timeout;
|
|
1131
|
+
function startProgress() {
|
|
1132
|
+
nprogress.done();
|
|
1133
|
+
nprogress.remove();
|
|
1134
|
+
nprogress.start();
|
|
1135
|
+
}
|
|
1136
|
+
function finishProgress() {
|
|
1137
|
+
if (nprogress.isStarted()) {
|
|
1138
|
+
nprogress.done(true);
|
|
1139
|
+
setTimeout(() => nprogress.remove(), 1e3);
|
|
1140
|
+
}
|
|
1141
|
+
clearTimeout(timeout);
|
|
1142
|
+
}
|
|
1143
|
+
return definePlugin({
|
|
1144
|
+
name: "hybridly:progress",
|
|
1145
|
+
initialized() {
|
|
1146
|
+
nprogress.configure({ showSpinner: resolved.spinner });
|
|
1147
|
+
if (resolved.includeCSS) injectCSS(resolved.color);
|
|
1148
|
+
},
|
|
1149
|
+
start: (request) => {
|
|
1150
|
+
if (request.options.progress === false) return;
|
|
1151
|
+
clearTimeout(timeout);
|
|
1152
|
+
timeout = setTimeout(() => startProgress(), resolved.delay);
|
|
1153
|
+
},
|
|
1154
|
+
progress: (progress) => {
|
|
1155
|
+
if (nprogress.isStarted() && progress.percentage) nprogress.set(Math.max(nprogress.status, progress.percentage / 100 * .9));
|
|
1156
|
+
},
|
|
1157
|
+
after: () => finishProgress()
|
|
1158
|
+
});
|
|
1159
|
+
}
|
|
1160
|
+
function injectCSS(color) {
|
|
1161
|
+
const element = document.createElement("style");
|
|
1162
|
+
element.textContent = `
|
|
1163
|
+
#nprogress {
|
|
1164
|
+
pointer-events: none;
|
|
1165
|
+
--progress-color: ${color};
|
|
1166
|
+
}
|
|
1167
|
+
#nprogress .bar {
|
|
1168
|
+
background: var(--progress-color);
|
|
1169
|
+
position: fixed;
|
|
1170
|
+
z-index: 1031;
|
|
1171
|
+
top: 0;
|
|
1172
|
+
left: 0;
|
|
1173
|
+
width: 100%;
|
|
1174
|
+
height: 2px;
|
|
1175
|
+
}
|
|
1176
|
+
#nprogress .peg {
|
|
1177
|
+
display: block;
|
|
1178
|
+
position: absolute;
|
|
1179
|
+
right: 0px;
|
|
1180
|
+
width: 100px;
|
|
1181
|
+
height: 100%;
|
|
1182
|
+
box-shadow: 0 0 10px var(--progress-color), 0 0 5px var(--progress-color);
|
|
1183
|
+
opacity: 1.0;
|
|
1184
|
+
-webkit-transform: rotate(3deg) translate(0px, -4px);
|
|
1185
|
+
-ms-transform: rotate(3deg) translate(0px, -4px);
|
|
1186
|
+
transform: rotate(3deg) translate(0px, -4px);
|
|
1187
|
+
}
|
|
1188
|
+
#nprogress .spinner {
|
|
1189
|
+
display: block;
|
|
1190
|
+
position: fixed;
|
|
1191
|
+
z-index: 1031;
|
|
1192
|
+
top: 15px;
|
|
1193
|
+
right: 15px;
|
|
1194
|
+
}
|
|
1195
|
+
#nprogress .spinner-icon {
|
|
1196
|
+
width: 18px;
|
|
1197
|
+
height: 18px;
|
|
1198
|
+
box-sizing: border-box;
|
|
1199
|
+
border: solid 2px transparent;
|
|
1200
|
+
border-top-color: var(--progress-color);
|
|
1201
|
+
border-left-color: var(--progress-color);
|
|
1202
|
+
border-radius: 50%;
|
|
1203
|
+
-webkit-animation: nprogress-spinner 400ms linear infinite;
|
|
1204
|
+
animation: nprogress-spinner 400ms linear infinite;
|
|
1205
|
+
}
|
|
1206
|
+
.nprogress-custom-parent {
|
|
1207
|
+
overflow: hidden;
|
|
1208
|
+
position: relative;
|
|
1209
|
+
}
|
|
1210
|
+
.nprogress-custom-parent #nprogress .spinner,
|
|
1211
|
+
.nprogress-custom-parent #nprogress .bar {
|
|
1212
|
+
position: absolute;
|
|
1213
|
+
}
|
|
1214
|
+
@-webkit-keyframes nprogress-spinner {
|
|
1215
|
+
0% { -webkit-transform: rotate(0deg); }
|
|
1216
|
+
100% { -webkit-transform: rotate(360deg); }
|
|
1217
|
+
}
|
|
1218
|
+
@keyframes nprogress-spinner {
|
|
1219
|
+
0% { transform: rotate(0deg); }
|
|
1220
|
+
100% { transform: rotate(360deg); }
|
|
1221
|
+
}
|
|
1222
|
+
`;
|
|
1223
|
+
document.head.appendChild(element);
|
|
1224
|
+
}
|
|
1225
|
+
//#endregion
|
|
1226
|
+
//#region src/plugins/view-transition.ts
|
|
1227
|
+
function viewTransition() {
|
|
1228
|
+
if (!document.startViewTransition) return { name: "view-transition" };
|
|
1229
|
+
let domUpdated;
|
|
1230
|
+
return {
|
|
1231
|
+
name: "view-transition",
|
|
1232
|
+
navigating: async ({ type, hasDialog, viewTransition }) => {
|
|
1233
|
+
if (type === "initial" || hasDialog || viewTransition === false) return;
|
|
1234
|
+
return new Promise((confirmTransitionStarted) => document.startViewTransition({
|
|
1235
|
+
update: () => {
|
|
1236
|
+
confirmTransitionStarted(true);
|
|
1237
|
+
return new Promise((resolve) => domUpdated = resolve);
|
|
1238
|
+
},
|
|
1239
|
+
types: getViewTransitionType(viewTransition)
|
|
1240
|
+
}));
|
|
1241
|
+
},
|
|
1242
|
+
mounted: () => {
|
|
1243
|
+
domUpdated?.();
|
|
1244
|
+
domUpdated = void 0;
|
|
1245
|
+
},
|
|
1246
|
+
navigated: () => {
|
|
1247
|
+
domUpdated?.();
|
|
1248
|
+
domUpdated = void 0;
|
|
1249
|
+
}
|
|
1250
|
+
};
|
|
1251
|
+
}
|
|
1252
|
+
function getViewTransitionType(viewTransition) {
|
|
1253
|
+
if (viewTransition === false || viewTransition === void 0 || viewTransition === true) return null;
|
|
1254
|
+
if (Array.isArray(viewTransition)) return viewTransition;
|
|
1255
|
+
return [viewTransition];
|
|
1256
|
+
}
|
|
1257
|
+
//#endregion
|
|
1258
|
+
//#region src/initialize.ts
|
|
1259
|
+
/**
|
|
1260
|
+
* Initializes Hybridly's router and context.
|
|
1261
|
+
*/
|
|
1262
|
+
async function initializeHybridly(options = {}) {
|
|
1263
|
+
const resolved = options;
|
|
1264
|
+
const { element, payload, resolve } = prepare(resolved);
|
|
1265
|
+
if (!element) throw new Error("Could not find an HTML element to initialize Vue on.");
|
|
1266
|
+
state.setContext(await createRouter({
|
|
1267
|
+
http: resolved.http,
|
|
1268
|
+
plugins: resolved.plugins,
|
|
1269
|
+
serializer: resolved.serializer,
|
|
1270
|
+
responseErrorModals: resolved.responseErrorModals ?? process.env.NODE_ENV === "development",
|
|
1271
|
+
routing: resolved.routing,
|
|
1272
|
+
adapter: {
|
|
1273
|
+
resolveComponent: resolve,
|
|
1274
|
+
executeOnMounted: (callback) => {
|
|
1275
|
+
onMountedCallbacks.push(callback);
|
|
1276
|
+
},
|
|
1277
|
+
onDialogClose: async () => {
|
|
1278
|
+
dialogStore.hide();
|
|
1279
|
+
},
|
|
1280
|
+
onContextUpdate: (context) => {
|
|
1281
|
+
state.setContext(context);
|
|
1282
|
+
},
|
|
1283
|
+
onPropertiesUpdate: (properties) => {
|
|
1284
|
+
state.setProperties(properties);
|
|
1285
|
+
},
|
|
1286
|
+
onViewSwap: async (options) => {
|
|
1287
|
+
if (options.component) {
|
|
1288
|
+
onMountedCallbacks.push(() => options.onMounted?.({ isDialog: false }));
|
|
1289
|
+
state.setView(options.component);
|
|
1290
|
+
}
|
|
1291
|
+
state.setProperties(options.properties);
|
|
1292
|
+
if (!options.preserveState && !options.dialog) state.setViewKey(random());
|
|
1293
|
+
if (options.dialog) {
|
|
1294
|
+
onMountedCallbacks.push(() => options.onMounted?.({ isDialog: true }));
|
|
1295
|
+
dialogStore.setComponent(await resolve(options.dialog.component));
|
|
1296
|
+
dialogStore.setProperties(options.dialog.properties);
|
|
1297
|
+
dialogStore.setKey(options.dialog.key);
|
|
1298
|
+
dialogStore.show();
|
|
1299
|
+
} else dialogStore.hide();
|
|
1300
|
+
}
|
|
1301
|
+
},
|
|
1302
|
+
payload
|
|
1303
|
+
}));
|
|
1304
|
+
const render = () => h(wrapper);
|
|
1305
|
+
if (options.setup) return await options.setup({
|
|
1306
|
+
element,
|
|
1307
|
+
wrapper,
|
|
1308
|
+
render,
|
|
1309
|
+
hybridly: devtools,
|
|
1310
|
+
props: { context: state.context.value },
|
|
1311
|
+
payload
|
|
1312
|
+
});
|
|
1313
|
+
const app = createApp({ render });
|
|
1314
|
+
if (resolved.devtools !== false) app.use(devtools);
|
|
1315
|
+
await options.enhanceVue?.(app, payload);
|
|
1316
|
+
return app.mount(element);
|
|
1317
|
+
}
|
|
1318
|
+
function prepare(options) {
|
|
1319
|
+
debug.adapter("vue", "Preparing Hybridly with options:", options);
|
|
1320
|
+
const isServer = typeof window === "undefined";
|
|
1321
|
+
const id = options.id ?? "root";
|
|
1322
|
+
const element = document?.getElementById(id) ?? void 0;
|
|
1323
|
+
debug.adapter("vue", `Element "${id}" is:`, element);
|
|
1324
|
+
const payload = element?.dataset.payload ? JSON.parse(element.dataset.payload) : void 0;
|
|
1325
|
+
if (!payload) throw new Error("No payload found. Are you using the `@hybridly` directive?");
|
|
1326
|
+
if (options.cleanup !== false) delete element.dataset.payload;
|
|
1327
|
+
debug.adapter("vue", "Resolved:", {
|
|
1328
|
+
isServer,
|
|
1329
|
+
element,
|
|
1330
|
+
payload
|
|
1331
|
+
});
|
|
1332
|
+
const resolve = async (name) => {
|
|
1333
|
+
debug.adapter("vue", "Resolving component", name);
|
|
1334
|
+
if (!options.imported) throw new Error("No component loaded. Did you initialize Hybridly? Does `php artisan hybridly:config` return an error?");
|
|
1335
|
+
return await resolveViewComponent(name, options);
|
|
1336
|
+
};
|
|
1337
|
+
options.plugins ??= [];
|
|
1338
|
+
if (options.progress !== false) options.plugins.push(progress(typeof options.progress === "object" ? options.progress : {}));
|
|
1339
|
+
if (options.viewTransition !== false) options.plugins.push(viewTransition());
|
|
1340
|
+
if (options.defaultFormOptions) formStore.setDefaultConfig(options.defaultFormOptions);
|
|
1341
|
+
options.layout = resolveDefaultLayout(options.layout);
|
|
1342
|
+
return {
|
|
1343
|
+
isServer,
|
|
1344
|
+
element,
|
|
1345
|
+
payload,
|
|
1346
|
+
resolve
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
/**
|
|
1350
|
+
* Resolves a view by its name.
|
|
1351
|
+
*/
|
|
1352
|
+
async function resolveViewComponent(name, options) {
|
|
1353
|
+
const components = options.imported;
|
|
1354
|
+
const result = options.components.views.find((view) => name === view.identifier);
|
|
1355
|
+
const path = Object.keys(components).sort((a, b) => a.length - b.length).find((path) => result ? path.endsWith(result?.path) : false);
|
|
1356
|
+
if (!result || !path) {
|
|
1357
|
+
console.warn(`View component [${name}] not found. Available components: `, options.components.views.map(({ identifier }) => identifier));
|
|
1358
|
+
showViewComponentErrorModal(name);
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
1361
|
+
let component = typeof components[path] === "function" ? await components[path]() : components[path];
|
|
1362
|
+
component = component.default ?? component;
|
|
1363
|
+
component = applyDefaultLayout(component, options.layout);
|
|
1364
|
+
return component;
|
|
1365
|
+
}
|
|
1366
|
+
//#endregion
|
|
1367
|
+
//#region src/composables/back-forward.ts
|
|
1368
|
+
function useBackForward(options) {
|
|
1369
|
+
const callbacks = [];
|
|
1370
|
+
registerHook$1("navigated", (options) => {
|
|
1371
|
+
if (options.type === "back-forward") {
|
|
1372
|
+
callbacks.forEach((fn) => fn(state.context.value));
|
|
1373
|
+
callbacks.splice(0, callbacks.length);
|
|
1374
|
+
}
|
|
1375
|
+
});
|
|
1376
|
+
/**
|
|
1377
|
+
* Applies the given callback on back-forward navigations.
|
|
1378
|
+
*/
|
|
1379
|
+
function onBackForward(fn) {
|
|
1380
|
+
callbacks.push(fn);
|
|
1381
|
+
}
|
|
1382
|
+
/**
|
|
1383
|
+
* Reloads the page on back-forward navigations.
|
|
1384
|
+
*/
|
|
1385
|
+
function reloadOnBackForward(options) {
|
|
1386
|
+
onBackForward(() => router$1.reload(options));
|
|
1387
|
+
}
|
|
1388
|
+
if (options?.reload) reloadOnBackForward(options.reload === true ? void 0 : options.reload);
|
|
1389
|
+
return {
|
|
1390
|
+
onBackForward,
|
|
1391
|
+
reloadOnBackForward
|
|
1392
|
+
};
|
|
1393
|
+
}
|
|
1394
|
+
//#endregion
|
|
1395
|
+
//#region src/composables/bulk-select.ts
|
|
1396
|
+
/**
|
|
1397
|
+
* Returns the inclusive range between two records in the given order.
|
|
1398
|
+
*
|
|
1399
|
+
* This is useful for implementing shift-click bulk selection: keep track of the last selected anchor,
|
|
1400
|
+
* then pass the currently visible record identifiers and the clicked target identifier to this helper.
|
|
1401
|
+
* If the target is not in the list, an empty range is returned. If the anchor is missing, only the target is returned.
|
|
1402
|
+
*/
|
|
1403
|
+
function getBulkSelectionRange(records, anchor, target) {
|
|
1404
|
+
const targetIndex = records.indexOf(target);
|
|
1405
|
+
if (targetIndex === -1) return [];
|
|
1406
|
+
if (anchor === void 0) return [target];
|
|
1407
|
+
const anchorIndex = records.indexOf(anchor);
|
|
1408
|
+
if (anchorIndex === -1) return [target];
|
|
1409
|
+
return records.slice(Math.min(anchorIndex, targetIndex), Math.max(anchorIndex, targetIndex) + 1);
|
|
1410
|
+
}
|
|
1411
|
+
function useBulkSelect() {
|
|
1412
|
+
const selection = ref({
|
|
1413
|
+
all: false,
|
|
1414
|
+
only: /* @__PURE__ */ new Set(),
|
|
1415
|
+
except: /* @__PURE__ */ new Set()
|
|
1416
|
+
});
|
|
1417
|
+
/**
|
|
1418
|
+
* Toggles selection for all records.
|
|
1419
|
+
*/
|
|
1420
|
+
function toggleAll(force) {
|
|
1421
|
+
if (!selection.value.all || force === true) selectAll();
|
|
1422
|
+
else deselectAll();
|
|
1423
|
+
}
|
|
1424
|
+
/**
|
|
1425
|
+
* Selects all records.
|
|
1426
|
+
*/
|
|
1427
|
+
function selectAll() {
|
|
1428
|
+
selection.value.all = true;
|
|
1429
|
+
selection.value.only.clear();
|
|
1430
|
+
selection.value.except.clear();
|
|
1431
|
+
}
|
|
1432
|
+
/**
|
|
1433
|
+
* Deselects all records.
|
|
1434
|
+
*/
|
|
1435
|
+
function deselectAll() {
|
|
1436
|
+
selection.value.all = false;
|
|
1437
|
+
selection.value.only.clear();
|
|
1438
|
+
selection.value.except.clear();
|
|
1439
|
+
}
|
|
1440
|
+
/**
|
|
1441
|
+
* Selects the given records.
|
|
1442
|
+
*/
|
|
1443
|
+
function select(...records) {
|
|
1444
|
+
records.forEach((record) => selection.value.except.delete(record));
|
|
1445
|
+
records.forEach((record) => selection.value.only.add(record));
|
|
1446
|
+
}
|
|
1447
|
+
/**
|
|
1448
|
+
* Deselects the given records.
|
|
1449
|
+
*/
|
|
1450
|
+
function deselect(...records) {
|
|
1451
|
+
records.forEach((record) => selection.value.except.add(record));
|
|
1452
|
+
records.forEach((record) => selection.value.only.delete(record));
|
|
1453
|
+
}
|
|
1454
|
+
/**
|
|
1455
|
+
* Toggles selection for the given records.
|
|
1456
|
+
*/
|
|
1457
|
+
function toggle(record, force) {
|
|
1458
|
+
if (selected(record) || force === false) return deselect(record);
|
|
1459
|
+
if (!selected(record) || force === true) return select(record);
|
|
1460
|
+
}
|
|
1461
|
+
/**
|
|
1462
|
+
* Checks whether the given record is selected.
|
|
1463
|
+
*/
|
|
1464
|
+
function selected(record) {
|
|
1465
|
+
if (selection.value.all) return !selection.value.except.has(record);
|
|
1466
|
+
return selection.value.only.has(record);
|
|
1467
|
+
}
|
|
1468
|
+
/**
|
|
1469
|
+
* Checks whether all records are selected.
|
|
1470
|
+
*/
|
|
1471
|
+
const allSelected = computed(() => {
|
|
1472
|
+
return selection.value.all && selection.value.except.size === 0;
|
|
1473
|
+
});
|
|
1474
|
+
/**
|
|
1475
|
+
* Checks whether any record is selected.
|
|
1476
|
+
*/
|
|
1477
|
+
const anySelected = computed(() => {
|
|
1478
|
+
return selection.value.all || selection.value.only.size > 0;
|
|
1479
|
+
});
|
|
1480
|
+
/**
|
|
1481
|
+
* Binds a checkbox's properties.
|
|
1482
|
+
*/
|
|
1483
|
+
function bindCheckbox(key) {
|
|
1484
|
+
return {
|
|
1485
|
+
onChange: (event) => {
|
|
1486
|
+
const target = event.target;
|
|
1487
|
+
if (target.checked) select(target.value);
|
|
1488
|
+
else deselect(target.value);
|
|
1489
|
+
},
|
|
1490
|
+
checked: selected(key),
|
|
1491
|
+
value: key
|
|
1492
|
+
};
|
|
1493
|
+
}
|
|
1494
|
+
return {
|
|
1495
|
+
allSelected,
|
|
1496
|
+
anySelected,
|
|
1497
|
+
toggleAll,
|
|
1498
|
+
selectAll,
|
|
1499
|
+
deselectAll,
|
|
1500
|
+
select,
|
|
1501
|
+
deselect,
|
|
1502
|
+
toggle,
|
|
1503
|
+
selected,
|
|
1504
|
+
selection,
|
|
1505
|
+
bindCheckbox
|
|
1506
|
+
};
|
|
1507
|
+
}
|
|
1508
|
+
//#endregion
|
|
1509
|
+
//#region src/composables/register-hook.ts
|
|
1510
|
+
/**
|
|
1511
|
+
* Registers a global hook.
|
|
1512
|
+
* If called inside a component, unregisters after the component is unmounted.
|
|
1513
|
+
*/
|
|
1514
|
+
const registerHook = (hook, fn, options) => {
|
|
1515
|
+
const unregister = registerHook$1(hook, fn, options);
|
|
1516
|
+
if (getCurrentInstance()) onUnmounted(() => unregister());
|
|
1517
|
+
return unregister;
|
|
1518
|
+
};
|
|
1519
|
+
//#endregion
|
|
1520
|
+
//#region src/composables/current-route.ts
|
|
1521
|
+
const isNavigating = ref(false);
|
|
1522
|
+
function useRoute() {
|
|
1523
|
+
const current = ref(router$1.current());
|
|
1524
|
+
function matches(name, parameters) {
|
|
1525
|
+
return router$1.matches(toValue(name), parameters);
|
|
1526
|
+
}
|
|
1527
|
+
registerHook("before", () => isNavigating.value = true);
|
|
1528
|
+
registerHook("after", () => isNavigating.value = false);
|
|
1529
|
+
registerHook("navigated", () => {
|
|
1530
|
+
current.value = router$1.current();
|
|
1531
|
+
});
|
|
1532
|
+
return {
|
|
1533
|
+
isNavigating: readonly(isNavigating),
|
|
1534
|
+
current: readonly(current),
|
|
1535
|
+
matches
|
|
1536
|
+
};
|
|
1537
|
+
}
|
|
1538
|
+
//#endregion
|
|
1539
|
+
//#region src/composables/dialog.ts
|
|
1540
|
+
/**
|
|
1541
|
+
* Exposes utilities related to the dialogs.
|
|
1542
|
+
*/
|
|
1543
|
+
function useDialog() {
|
|
1544
|
+
return {
|
|
1545
|
+
close: () => router$1.dialog.close(),
|
|
1546
|
+
closeLocally: () => router$1.dialog.close({ local: true }),
|
|
1547
|
+
unmount: () => dialogStore.removeComponent(),
|
|
1548
|
+
show: computed({
|
|
1549
|
+
get: () => dialogStore.state.show.value,
|
|
1550
|
+
set: (v) => !v ? router$1.dialog.close({ local: true }) : null
|
|
1551
|
+
}),
|
|
1552
|
+
properties: computed(() => state.context.value?.dialog?.properties)
|
|
1553
|
+
};
|
|
1554
|
+
}
|
|
1555
|
+
//#endregion
|
|
1556
|
+
//#region src/composables/history.ts
|
|
1557
|
+
/**
|
|
1558
|
+
* Returns a ref with a value saved in the history state through Hybridly.
|
|
1559
|
+
* The state is linked to a specific browser history entry.
|
|
1560
|
+
*/
|
|
1561
|
+
function useHistoryState(key, initial) {
|
|
1562
|
+
const value = ref(router$1.history.get(key) ?? initial);
|
|
1563
|
+
watch(value, (value) => {
|
|
1564
|
+
router$1.history.remember(key, toRaw(value));
|
|
1565
|
+
}, {
|
|
1566
|
+
immediate: true,
|
|
1567
|
+
deep: true
|
|
1568
|
+
});
|
|
1569
|
+
return value;
|
|
1570
|
+
}
|
|
1571
|
+
//#endregion
|
|
1572
|
+
//#region ../hybridly/src/index.ts
|
|
1573
|
+
var src_exports = /* @__PURE__ */ __exportAll({});
|
|
1574
|
+
import * as import__hybridly_core from "@hybridly/core";
|
|
1575
|
+
__reExport(src_exports, import__hybridly_core);
|
|
1576
|
+
//#endregion
|
|
1577
|
+
//#region src/composables/paginator.ts
|
|
1578
|
+
function createPaginator(paginator, defaultOptions) {
|
|
1579
|
+
const isPaginator = (p) => {
|
|
1580
|
+
return "links" in p && "meta" in p && "last_page" in p.meta;
|
|
1581
|
+
};
|
|
1582
|
+
if (isPaginator(paginator)) return {
|
|
1583
|
+
...paginator,
|
|
1584
|
+
to: (page, options) => {
|
|
1585
|
+
const link = paginator.links.find((l) => l.page === page);
|
|
1586
|
+
if (link?.url) return src_exports.router.get(link.url, {
|
|
1587
|
+
preserveState: true,
|
|
1588
|
+
...defaultOptions,
|
|
1589
|
+
...options
|
|
1590
|
+
});
|
|
621
1591
|
}
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
function shouldIntercept(event) {
|
|
625
|
-
const isLink = event.currentTarget.tagName.toLowerCase() === "a";
|
|
626
|
-
return !(event.target && (event?.target).isContentEditable || event.defaultPrevented || isLink && event.which > 1 || isLink && event.altKey || isLink && event.ctrlKey || isLink && event.metaKey || isLink && event.shiftKey);
|
|
1592
|
+
};
|
|
1593
|
+
return paginator;
|
|
627
1594
|
}
|
|
628
|
-
|
|
629
1595
|
//#endregion
|
|
630
1596
|
//#region src/utils.ts
|
|
631
1597
|
/**
|
|
@@ -662,7 +1628,6 @@ function toReactive(objectRef) {
|
|
|
662
1628
|
}
|
|
663
1629
|
}));
|
|
664
1630
|
}
|
|
665
|
-
|
|
666
1631
|
//#endregion
|
|
667
1632
|
//#region src/composables/property.ts
|
|
668
1633
|
/** Accesses all current properties. */
|
|
@@ -671,7 +1636,7 @@ function useProperties() {
|
|
|
671
1636
|
}
|
|
672
1637
|
/** Accesses a property with a dot notation. */
|
|
673
1638
|
function useProperty(path) {
|
|
674
|
-
return computed(() =>
|
|
1639
|
+
return computed(() => get(state.properties.value, path));
|
|
675
1640
|
}
|
|
676
1641
|
/**
|
|
677
1642
|
* Sets the property at the given path to the given value.
|
|
@@ -679,338 +1644,144 @@ function useProperty(path) {
|
|
|
679
1644
|
*/
|
|
680
1645
|
function setProperty(path, value) {
|
|
681
1646
|
if (!state.properties.value) return;
|
|
682
|
-
|
|
683
|
-
if (state.context.value?.view.properties)
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
//#endregion
|
|
687
|
-
//#region src/composables/form.ts
|
|
688
|
-
function safeClone(obj) {
|
|
689
|
-
return clone(toRaw(obj));
|
|
690
|
-
}
|
|
691
|
-
function useForm(options) {
|
|
692
|
-
const shouldRemember = !!options.key;
|
|
693
|
-
const historyKey = options.key ?? "form:default";
|
|
694
|
-
const historyData = shouldRemember ? router$1.history.get(historyKey) : void 0;
|
|
695
|
-
const timeoutIds = {
|
|
696
|
-
recentlyFailed: void 0,
|
|
697
|
-
recentlySuccessful: void 0
|
|
698
|
-
};
|
|
699
|
-
/** Fields that were initially set up. */
|
|
700
|
-
const initial = safeClone(options.fields);
|
|
701
|
-
/** Fields as they were when loaded. */
|
|
702
|
-
const loaded = safeClone(historyData?.fields ?? options.fields);
|
|
703
|
-
/** Current fields. */
|
|
704
|
-
const fields = reactive(safeClone(loaded));
|
|
705
|
-
/** Validation errors for each field. */
|
|
706
|
-
const errors = ref(historyData?.errors ?? {});
|
|
707
|
-
/** Whether the form is dirty. */
|
|
708
|
-
const isDirty = ref(false);
|
|
709
|
-
/** Whether the submission was recently successful. */
|
|
710
|
-
const recentlySuccessful = ref(false);
|
|
711
|
-
/** Whether the submission is successful. */
|
|
712
|
-
const successful = ref(false);
|
|
713
|
-
/** Whether the submission was recently failed. */
|
|
714
|
-
const recentlyFailed = ref(false);
|
|
715
|
-
/** Whether the submission is failed. */
|
|
716
|
-
const failed = ref(false);
|
|
717
|
-
/** Whether the submission is being processed. */
|
|
718
|
-
const processing = ref(false);
|
|
719
|
-
/** The current request's progress. */
|
|
720
|
-
const progress = ref();
|
|
721
|
-
/**
|
|
722
|
-
* Sets new initial values for the form, so subsequent resets will use thse values.
|
|
723
|
-
*/
|
|
724
|
-
function setInitial(newInitial) {
|
|
725
|
-
Object.entries(newInitial).forEach(([key, value]) => {
|
|
726
|
-
Reflect.set(initial, key, safeClone(value));
|
|
727
|
-
});
|
|
728
|
-
}
|
|
729
|
-
/**
|
|
730
|
-
* Resets the form's failed and successful flags.
|
|
731
|
-
*/
|
|
732
|
-
function resetSubmissionState() {
|
|
733
|
-
successful.value = false;
|
|
734
|
-
failed.value = false;
|
|
735
|
-
recentlyFailed.value = false;
|
|
736
|
-
recentlySuccessful.value = false;
|
|
737
|
-
clearTimeout(timeoutIds.recentlySuccessful);
|
|
738
|
-
clearTimeout(timeoutIds.recentlyFailed);
|
|
739
|
-
progress.value = void 0;
|
|
740
|
-
}
|
|
741
|
-
/**
|
|
742
|
-
* Resets the fields, errors and submission state.
|
|
743
|
-
*/
|
|
744
|
-
function reset() {
|
|
745
|
-
resetSubmissionState();
|
|
746
|
-
clearErrors();
|
|
747
|
-
resetFields();
|
|
748
|
-
}
|
|
749
|
-
/**
|
|
750
|
-
* Resets the fields to their initial values.
|
|
751
|
-
*/
|
|
752
|
-
function resetFields(...keys) {
|
|
753
|
-
if (keys.length === 0) keys = Object.keys(fields);
|
|
754
|
-
keys.forEach((key) => {
|
|
755
|
-
Reflect.set(fields, key, safeClone(Reflect.get(initial, key)));
|
|
756
|
-
});
|
|
757
|
-
}
|
|
758
|
-
/**
|
|
759
|
-
* Clear the form fields.
|
|
760
|
-
*/
|
|
761
|
-
function clear(...keys) {
|
|
762
|
-
if (keys.length === 0) keys = Object.keys(fields);
|
|
763
|
-
keys.forEach((key) => {
|
|
764
|
-
delete fields[key];
|
|
765
|
-
});
|
|
766
|
-
}
|
|
767
|
-
/**
|
|
768
|
-
* Submits the form.
|
|
769
|
-
*/
|
|
770
|
-
function submit(optionsOverrides) {
|
|
771
|
-
const { fields: _f, key: _k, ...optionsWithoutFields } = options;
|
|
772
|
-
const resolvedOptions = optionsOverrides ? merge(optionsWithoutFields, optionsOverrides, { mergePlainObjects: true }) : optionsWithoutFields;
|
|
773
|
-
const optionsWithOverrides = merge(formStore.getDefaultConfig(), resolvedOptions, { mergePlainObjects: true });
|
|
774
|
-
const url = typeof optionsWithOverrides.url === "function" ? optionsWithOverrides.url() : optionsWithOverrides.url;
|
|
775
|
-
const data = typeof optionsWithOverrides.transform === "function" ? optionsWithOverrides.transform(fields) : fields;
|
|
776
|
-
const preserveState = optionsWithOverrides.preserveState ?? optionsWithOverrides.method !== "GET";
|
|
777
|
-
const hooks = optionsWithOverrides.hooks ?? {};
|
|
778
|
-
return router$1.navigate({
|
|
779
|
-
...optionsWithOverrides,
|
|
780
|
-
url: url ?? state.context.value?.url,
|
|
781
|
-
method: optionsWithOverrides.method ?? "POST",
|
|
782
|
-
data: safeClone(data),
|
|
783
|
-
preserveState,
|
|
784
|
-
hooks: {
|
|
785
|
-
before: (navigation, context) => {
|
|
786
|
-
resetSubmissionState();
|
|
787
|
-
return hooks.before?.(navigation, context);
|
|
788
|
-
},
|
|
789
|
-
start: (context) => {
|
|
790
|
-
processing.value = true;
|
|
791
|
-
return hooks.start?.(context);
|
|
792
|
-
},
|
|
793
|
-
progress: (incoming, context) => {
|
|
794
|
-
progress.value = incoming;
|
|
795
|
-
return hooks.progress?.(incoming, context);
|
|
796
|
-
},
|
|
797
|
-
error: (incoming, context) => {
|
|
798
|
-
setErrors(incoming);
|
|
799
|
-
failed.value = true;
|
|
800
|
-
recentlyFailed.value = true;
|
|
801
|
-
timeoutIds.recentlyFailed = setTimeout(() => recentlyFailed.value = false, optionsWithOverrides.timeout ?? 5e3);
|
|
802
|
-
return hooks.error?.(incoming, context);
|
|
803
|
-
},
|
|
804
|
-
success: (payload, context) => {
|
|
805
|
-
clearErrors();
|
|
806
|
-
if (optionsWithOverrides.updateInitials) setInitial(fields);
|
|
807
|
-
if (optionsWithOverrides.reset !== false) resetFields();
|
|
808
|
-
successful.value = true;
|
|
809
|
-
recentlySuccessful.value = true;
|
|
810
|
-
timeoutIds.recentlySuccessful = setTimeout(() => recentlySuccessful.value = false, optionsWithOverrides.timeout ?? 5e3);
|
|
811
|
-
return hooks.success?.(payload, context);
|
|
812
|
-
},
|
|
813
|
-
after: (context) => {
|
|
814
|
-
progress.value = void 0;
|
|
815
|
-
processing.value = false;
|
|
816
|
-
return hooks.after?.(context);
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
});
|
|
820
|
-
}
|
|
821
|
-
/**
|
|
822
|
-
* Clears all errors.
|
|
823
|
-
*/
|
|
824
|
-
function clearErrors(...keys) {
|
|
825
|
-
if (keys.length === 0) keys = Object.keys(fields);
|
|
826
|
-
keys.forEach((key) => {
|
|
827
|
-
clearError(key);
|
|
828
|
-
});
|
|
829
|
-
}
|
|
830
|
-
/**
|
|
831
|
-
* Checks if the given keys are dirty in the form.
|
|
832
|
-
*/
|
|
833
|
-
function hasDirty(...keys) {
|
|
834
|
-
if (keys.length === 0) return isDirty.value;
|
|
835
|
-
return keys.some((key) => !isEqual(toRaw(getByPath(fields, key)), toRaw(getByPath(initial, key))));
|
|
836
|
-
}
|
|
837
|
-
/**
|
|
838
|
-
* Clears the given field's error.
|
|
839
|
-
*/
|
|
840
|
-
function clearError(key) {
|
|
841
|
-
unsetPropertyAtPath(errors.value, key);
|
|
842
|
-
}
|
|
843
|
-
/**
|
|
844
|
-
* Sets current errors.
|
|
845
|
-
*/
|
|
846
|
-
function setErrors(incoming) {
|
|
847
|
-
clearErrors();
|
|
848
|
-
Object.entries(incoming).forEach(([path, value]) => {
|
|
849
|
-
setValueAtPath(errors.value, path, value);
|
|
850
|
-
});
|
|
851
|
-
}
|
|
852
|
-
/**
|
|
853
|
-
* Aborts the submission.
|
|
854
|
-
*/
|
|
855
|
-
function abort() {
|
|
856
|
-
router$1.abort();
|
|
857
|
-
}
|
|
858
|
-
watch([
|
|
859
|
-
fields,
|
|
860
|
-
processing,
|
|
861
|
-
errors
|
|
862
|
-
], () => {
|
|
863
|
-
isDirty.value = !isEqual(toRaw(initial), toRaw(fields));
|
|
864
|
-
if (shouldRemember) router$1.history.remember(historyKey, {
|
|
865
|
-
fields: toRaw(fields),
|
|
866
|
-
errors: toRaw(errors.value)
|
|
867
|
-
});
|
|
868
|
-
}, {
|
|
869
|
-
deep: true,
|
|
870
|
-
immediate: true
|
|
871
|
-
});
|
|
872
|
-
return reactive({
|
|
873
|
-
resetFields,
|
|
874
|
-
reset,
|
|
875
|
-
resetSubmissionState,
|
|
876
|
-
clear,
|
|
877
|
-
fields,
|
|
878
|
-
abort,
|
|
879
|
-
setErrors,
|
|
880
|
-
clearErrors,
|
|
881
|
-
clearError,
|
|
882
|
-
setInitial,
|
|
883
|
-
hasDirty,
|
|
884
|
-
submitWith: submit,
|
|
885
|
-
submitWithOptions: submit,
|
|
886
|
-
submit: () => submit(),
|
|
887
|
-
hasErrors: computed(() => Object.values(errors.value ?? {}).length > 0),
|
|
888
|
-
initial,
|
|
889
|
-
loaded,
|
|
890
|
-
progress,
|
|
891
|
-
isDirty,
|
|
892
|
-
errors,
|
|
893
|
-
processing,
|
|
894
|
-
successful,
|
|
895
|
-
failed,
|
|
896
|
-
recentlySuccessful,
|
|
897
|
-
recentlyFailed
|
|
898
|
-
});
|
|
1647
|
+
set(state.properties.value, path, toValue(value));
|
|
1648
|
+
if (state.context.value?.view.properties) set(state.context.value.view.properties, path, toValue(value));
|
|
899
1649
|
}
|
|
900
|
-
|
|
901
1650
|
//#endregion
|
|
902
|
-
//#region src/composables/
|
|
1651
|
+
//#region src/composables/query-parameters.ts
|
|
903
1652
|
/**
|
|
904
|
-
*
|
|
905
|
-
*
|
|
1653
|
+
* Access reactive query parameters.
|
|
1654
|
+
*
|
|
1655
|
+
* @see https://hybridly.dev/api/utils/use-query-parameters.html
|
|
906
1656
|
*/
|
|
907
|
-
function
|
|
908
|
-
const
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
return value;
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
//#endregion
|
|
919
|
-
//#region src/composables/back-forward.ts
|
|
920
|
-
function useBackForward(options) {
|
|
921
|
-
const callbacks = [];
|
|
922
|
-
registerHook$1("navigated", (options) => {
|
|
923
|
-
if (options.type === "back-forward") {
|
|
924
|
-
callbacks.forEach((fn) => fn(state.context.value));
|
|
925
|
-
callbacks.splice(0, callbacks.length);
|
|
1657
|
+
function useQueryParameters() {
|
|
1658
|
+
const state = reactive({});
|
|
1659
|
+
function updateState() {
|
|
1660
|
+
const params = parseQueryString(window.location.search);
|
|
1661
|
+
const unusedKeys = new Set(Object.keys(state));
|
|
1662
|
+
for (const [key, value] of Object.entries(params)) {
|
|
1663
|
+
state[key] = value;
|
|
1664
|
+
unusedKeys.delete(key);
|
|
926
1665
|
}
|
|
927
|
-
|
|
928
|
-
/**
|
|
929
|
-
* Applies the given callback on back-forward navigations.
|
|
930
|
-
*/
|
|
931
|
-
function onBackForward(fn) {
|
|
932
|
-
callbacks.push(fn);
|
|
933
|
-
}
|
|
934
|
-
/**
|
|
935
|
-
* Reloads the page on back-forward navigations.
|
|
936
|
-
*/
|
|
937
|
-
function reloadOnBackForward(options) {
|
|
938
|
-
onBackForward(() => router$1.reload(options));
|
|
1666
|
+
Array.from(unusedKeys).forEach((key) => delete state[key]);
|
|
939
1667
|
}
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
reloadOnBackForward
|
|
944
|
-
};
|
|
1668
|
+
updateState();
|
|
1669
|
+
registerHook$1("navigated", updateState);
|
|
1670
|
+
return state;
|
|
945
1671
|
}
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
...paginator,
|
|
961
|
-
to: (page) => {
|
|
962
|
-
const link = paginator.links.find((l) => l.page === page);
|
|
963
|
-
if (link?.url) return src_exports.router.get(link.url, options);
|
|
964
|
-
}
|
|
1672
|
+
/**
|
|
1673
|
+
* Makes the specified query parameter reactive.
|
|
1674
|
+
*
|
|
1675
|
+
* @see https://hybridly.dev/api/utils/use-query-parameter.html
|
|
1676
|
+
*/
|
|
1677
|
+
function useQueryParameter(name, options = {}) {
|
|
1678
|
+
const query = useQueryParameters();
|
|
1679
|
+
const transform = (value) => {
|
|
1680
|
+
if (options.transform === "bool") return value === true || value === "true" || value === "1" || value === "yes";
|
|
1681
|
+
else if (options.transform === "number") return Number(value);
|
|
1682
|
+
else if (options.transform === "string") return String(value);
|
|
1683
|
+
else if (options.transform === "date") return new Date(value);
|
|
1684
|
+
else if (typeof options.transform === "function") return options.transform(value);
|
|
1685
|
+
return value;
|
|
965
1686
|
};
|
|
966
|
-
|
|
1687
|
+
const value = ref();
|
|
1688
|
+
watch(query, () => {
|
|
1689
|
+
value.value = transform(query[name] ?? toValue(options.defaultValue));
|
|
1690
|
+
}, {
|
|
1691
|
+
deep: true,
|
|
1692
|
+
immediate: true
|
|
1693
|
+
});
|
|
1694
|
+
return value;
|
|
967
1695
|
}
|
|
968
|
-
|
|
969
1696
|
//#endregion
|
|
970
|
-
//#region src/composables/
|
|
1697
|
+
//#region src/composables/refinements.ts
|
|
971
1698
|
/**
|
|
972
|
-
*
|
|
973
|
-
* If called inside a component, unregisters after the component is unmounted.
|
|
1699
|
+
* Checks whether the given filter is a text filter, and narrows its type.
|
|
974
1700
|
*/
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
return unregister;
|
|
979
|
-
};
|
|
980
|
-
|
|
981
|
-
//#endregion
|
|
982
|
-
//#region src/composables/dialog.ts
|
|
1701
|
+
function isTextFilter(filter) {
|
|
1702
|
+
return filter.type === "text";
|
|
1703
|
+
}
|
|
983
1704
|
/**
|
|
984
|
-
*
|
|
1705
|
+
* Checks whether the given filter is a date filter, and narrows its type.
|
|
985
1706
|
*/
|
|
986
|
-
function
|
|
987
|
-
return
|
|
988
|
-
close: () => router$1.dialog.close(),
|
|
989
|
-
closeLocally: () => router$1.dialog.close({ local: true }),
|
|
990
|
-
unmount: () => dialogStore.removeComponent(),
|
|
991
|
-
show: computed({
|
|
992
|
-
get: () => dialogStore.state.show.value,
|
|
993
|
-
set: (v) => !v ? router$1.dialog.close({ local: true }) : null
|
|
994
|
-
}),
|
|
995
|
-
properties: computed(() => state.context.value?.dialog?.properties)
|
|
996
|
-
};
|
|
1707
|
+
function isDateFilter(filter) {
|
|
1708
|
+
return filter.type === "date";
|
|
997
1709
|
}
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
function
|
|
1002
|
-
|
|
1710
|
+
/**
|
|
1711
|
+
* Checks whether the given filter is a select filter, and narrows its type.
|
|
1712
|
+
*/
|
|
1713
|
+
function isSelectFilter(filter) {
|
|
1714
|
+
return filter.type === "select";
|
|
1715
|
+
}
|
|
1716
|
+
/**
|
|
1717
|
+
* Checks whether the given filter is a ternary filter, and narrows its type.
|
|
1718
|
+
*/
|
|
1719
|
+
function isTernaryFilter(filter) {
|
|
1720
|
+
return filter.type === "ternary";
|
|
1721
|
+
}
|
|
1722
|
+
/**
|
|
1723
|
+
* Checks whether the given filter is a boolean filter, and narrows its type.
|
|
1724
|
+
*/
|
|
1725
|
+
function isBooleanFilter(filter) {
|
|
1726
|
+
return filter.type === "boolean";
|
|
1727
|
+
}
|
|
1728
|
+
/**
|
|
1729
|
+
* Checks whether the given filter is a trashed filter, and narrows its type.
|
|
1730
|
+
*/
|
|
1731
|
+
function isTrashedFilter(filter) {
|
|
1732
|
+
return filter.type === "trashed";
|
|
1733
|
+
}
|
|
1734
|
+
/**
|
|
1735
|
+
* Checks whether the given filter is a callback filter, and narrows its type.
|
|
1736
|
+
*/
|
|
1737
|
+
function isCallbackFilter(filter) {
|
|
1738
|
+
return filter.type === "callback" || ![
|
|
1739
|
+
"text",
|
|
1740
|
+
"select",
|
|
1741
|
+
"ternary",
|
|
1742
|
+
"boolean",
|
|
1743
|
+
"trashed"
|
|
1744
|
+
].includes(filter.type);
|
|
1745
|
+
}
|
|
1746
|
+
function useRefinements(input, defaultOptions = {}) {
|
|
1747
|
+
const refinements = computed(() => toValue(input));
|
|
1003
1748
|
const sortsKey = computed(() => refinements.value.keys.sorts);
|
|
1004
1749
|
const filtersKey = computed(() => refinements.value.keys.filters);
|
|
1005
1750
|
defaultOptions = {
|
|
1006
1751
|
replace: false,
|
|
1752
|
+
group: "refining",
|
|
1753
|
+
interruptAsyncOnStart: "same-group",
|
|
1754
|
+
reset: "*",
|
|
1007
1755
|
...defaultOptions
|
|
1008
1756
|
};
|
|
1757
|
+
const sorts = computed(() => {
|
|
1758
|
+
return refinements.value.sorts.map((sort) => ({
|
|
1759
|
+
...sort,
|
|
1760
|
+
toggle: (options) => toggleSort(sort.name, options),
|
|
1761
|
+
isSorting: (direction) => isSorting(sort.name, direction),
|
|
1762
|
+
clear: (options) => clearSorts(options)
|
|
1763
|
+
}));
|
|
1764
|
+
});
|
|
1765
|
+
const filters = computed(() => {
|
|
1766
|
+
return refinements.value.filters.map((filter) => ({
|
|
1767
|
+
...filter,
|
|
1768
|
+
apply: (value, options) => applyFilter(filter.name, value, options),
|
|
1769
|
+
clear: (options) => clearFilter(filter.name, options),
|
|
1770
|
+
search: (query, options) => searchFilter(filter.name, query, options),
|
|
1771
|
+
isSelectFilter: () => isSelectFilter(filter),
|
|
1772
|
+
isDateFilter: () => isDateFilter(filter),
|
|
1773
|
+
isSearchFilter: () => isTextFilter(filter),
|
|
1774
|
+
isTernaryFilter: () => isTernaryFilter(filter),
|
|
1775
|
+
isBooleanFilter: () => isBooleanFilter(filter),
|
|
1776
|
+
isTrashedFilter: () => isTrashedFilter(filter),
|
|
1777
|
+
isCallbackFilter: () => isCallbackFilter(filter)
|
|
1778
|
+
}));
|
|
1779
|
+
});
|
|
1009
1780
|
function getSort(name) {
|
|
1010
|
-
return
|
|
1781
|
+
return sorts.value.find((sort) => sort.name === name);
|
|
1011
1782
|
}
|
|
1012
1783
|
function getFilter(name) {
|
|
1013
|
-
return
|
|
1784
|
+
return filters.value.find((sort) => sort.name === name);
|
|
1014
1785
|
}
|
|
1015
1786
|
async function reset(options = {}) {
|
|
1016
1787
|
return await router$1.reload({
|
|
@@ -1036,17 +1807,40 @@ function useRefinements(properties, refinementsKeys, defaultOptions = {}) {
|
|
|
1036
1807
|
data: { [filtersKey.value]: { [filter]: void 0 } }
|
|
1037
1808
|
});
|
|
1038
1809
|
}
|
|
1810
|
+
async function searchFilter(name, query, options = {}) {
|
|
1811
|
+
if (!getFilter(name)) {
|
|
1812
|
+
console.warn(`[Refinement] Filter "${name}" does not exist.`);
|
|
1813
|
+
return;
|
|
1814
|
+
}
|
|
1815
|
+
if (["", null].includes(query?.toString() || null)) query = void 0;
|
|
1816
|
+
return await router$1.reload({
|
|
1817
|
+
...defaultOptions,
|
|
1818
|
+
...options,
|
|
1819
|
+
data: { [filtersKey.value]: { [name]: { search: query } } }
|
|
1820
|
+
});
|
|
1821
|
+
}
|
|
1039
1822
|
async function applyFilter(name, value, options = {}) {
|
|
1040
1823
|
const filter = getFilter(name);
|
|
1041
1824
|
if (!filter) {
|
|
1042
1825
|
console.warn(`[Refinement] Filter "${name}" does not exist.`);
|
|
1043
1826
|
return;
|
|
1044
1827
|
}
|
|
1045
|
-
if (["", null].includes(value) || value
|
|
1828
|
+
if (["", null].includes(value) || filter.has_default && isEqual(value, filter.default)) value = void 0;
|
|
1829
|
+
const operator = (() => {
|
|
1830
|
+
const operator = options.operator ?? filter.operator;
|
|
1831
|
+
if (operator === filter.default_operator) return;
|
|
1832
|
+
return operator;
|
|
1833
|
+
})();
|
|
1046
1834
|
return await router$1.reload({
|
|
1835
|
+
progress: true,
|
|
1047
1836
|
...defaultOptions,
|
|
1048
1837
|
...options,
|
|
1049
|
-
data: { [filtersKey.value]: { [name]:
|
|
1838
|
+
data: { [filtersKey.value]: { [name]: {
|
|
1839
|
+
value,
|
|
1840
|
+
search: void 0,
|
|
1841
|
+
operator,
|
|
1842
|
+
options: options.options ?? void 0
|
|
1843
|
+
} } }
|
|
1050
1844
|
});
|
|
1051
1845
|
}
|
|
1052
1846
|
async function clearSorts(options = {}) {
|
|
@@ -1094,15 +1888,15 @@ function useRefinements(properties, refinementsKeys, defaultOptions = {}) {
|
|
|
1094
1888
|
const _proxy = ref(getFilterValue());
|
|
1095
1889
|
let filterIsBeingApplied = false;
|
|
1096
1890
|
let proxyIsBeingUpdated = false;
|
|
1097
|
-
const debouncedApplyFilter = debounce(
|
|
1891
|
+
const debouncedApplyFilter = debounce(async (value) => {
|
|
1098
1892
|
await applyFilter(name, transform(value), options);
|
|
1099
1893
|
nextTick(() => filterIsBeingApplied = false);
|
|
1100
|
-
});
|
|
1101
|
-
const debounceUpdateProxyValue = debounce(
|
|
1894
|
+
}, options.debounce ?? 250);
|
|
1895
|
+
const debounceUpdateProxyValue = debounce(() => {
|
|
1102
1896
|
const filter = refinements.value.filters.find((f) => f.name === name);
|
|
1103
1897
|
if (filter) _proxy.value = transform(filter?.value);
|
|
1104
1898
|
nextTick(() => proxyIsBeingUpdated = false);
|
|
1105
|
-
}, {
|
|
1899
|
+
}, options.syncDebounce ?? 250, { edges: ["leading"] });
|
|
1106
1900
|
watch(() => refinements.value.filters.find((f) => f.name === name)?.value, () => {
|
|
1107
1901
|
if (filterIsBeingApplied === true) return;
|
|
1108
1902
|
proxyIsBeingUpdated = true;
|
|
@@ -1117,18 +1911,10 @@ function useRefinements(properties, refinementsKeys, defaultOptions = {}) {
|
|
|
1117
1911
|
}
|
|
1118
1912
|
return {
|
|
1119
1913
|
bindFilter,
|
|
1120
|
-
filters
|
|
1121
|
-
|
|
1122
|
-
apply: (value, options) => applyFilter(filter.name, value, options),
|
|
1123
|
-
clear: (options) => clearFilter(filter.name, options)
|
|
1124
|
-
}))),
|
|
1125
|
-
sorts: toReactive(refinements.value.sorts.map((sort) => ({
|
|
1126
|
-
...sort,
|
|
1127
|
-
toggle: (options) => toggleSort(sort.name, options),
|
|
1128
|
-
isSorting: (direction) => isSorting(sort.name, direction),
|
|
1129
|
-
clear: (options) => clearSorts(options)
|
|
1130
|
-
}))),
|
|
1914
|
+
filters,
|
|
1915
|
+
sorts,
|
|
1131
1916
|
filtersKey,
|
|
1917
|
+
sortsKey,
|
|
1132
1918
|
getFilter,
|
|
1133
1919
|
getSort,
|
|
1134
1920
|
reset,
|
|
@@ -1143,176 +1929,26 @@ function useRefinements(properties, refinementsKeys, defaultOptions = {}) {
|
|
|
1143
1929
|
applyFilter
|
|
1144
1930
|
};
|
|
1145
1931
|
}
|
|
1146
|
-
|
|
1147
|
-
//#endregion
|
|
1148
|
-
//#region src/composables/current-route.ts
|
|
1149
|
-
const isNavigating = ref(false);
|
|
1150
|
-
function useRoute() {
|
|
1151
|
-
const current = ref(router$1.current());
|
|
1152
|
-
function matches(name, parameters) {
|
|
1153
|
-
return router$1.matches(toValue(name), parameters);
|
|
1154
|
-
}
|
|
1155
|
-
registerHook("before", () => isNavigating.value = true);
|
|
1156
|
-
registerHook("after", () => isNavigating.value = false);
|
|
1157
|
-
registerHook("navigated", () => {
|
|
1158
|
-
current.value = router$1.current();
|
|
1159
|
-
});
|
|
1160
|
-
return {
|
|
1161
|
-
isNavigating: readonly(isNavigating),
|
|
1162
|
-
current: readonly(current),
|
|
1163
|
-
matches
|
|
1164
|
-
};
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
|
-
//#endregion
|
|
1168
|
-
//#region src/composables/bulk-select.ts
|
|
1169
|
-
function useBulkSelect() {
|
|
1170
|
-
const selection = ref({
|
|
1171
|
-
all: false,
|
|
1172
|
-
only: /* @__PURE__ */ new Set(),
|
|
1173
|
-
except: /* @__PURE__ */ new Set()
|
|
1174
|
-
});
|
|
1175
|
-
/**
|
|
1176
|
-
* Selects all records.
|
|
1177
|
-
*/
|
|
1178
|
-
function selectAll() {
|
|
1179
|
-
selection.value.all = true;
|
|
1180
|
-
selection.value.only.clear();
|
|
1181
|
-
selection.value.except.clear();
|
|
1182
|
-
}
|
|
1183
|
-
/**
|
|
1184
|
-
* Deselects all records.
|
|
1185
|
-
*/
|
|
1186
|
-
function deselectAll() {
|
|
1187
|
-
selection.value.all = false;
|
|
1188
|
-
selection.value.only.clear();
|
|
1189
|
-
selection.value.except.clear();
|
|
1190
|
-
}
|
|
1191
|
-
/**
|
|
1192
|
-
* Selects the given records.
|
|
1193
|
-
*/
|
|
1194
|
-
function select(...records) {
|
|
1195
|
-
records.forEach((record) => selection.value.except.delete(record));
|
|
1196
|
-
records.forEach((record) => selection.value.only.add(record));
|
|
1197
|
-
}
|
|
1198
|
-
/**
|
|
1199
|
-
* Deselects the given records.
|
|
1200
|
-
*/
|
|
1201
|
-
function deselect(...records) {
|
|
1202
|
-
records.forEach((record) => selection.value.except.add(record));
|
|
1203
|
-
records.forEach((record) => selection.value.only.delete(record));
|
|
1204
|
-
}
|
|
1205
|
-
/**
|
|
1206
|
-
* Toggles selection for the given records.
|
|
1207
|
-
*/
|
|
1208
|
-
function toggle(record, force) {
|
|
1209
|
-
if (selected(record) || force === false) return deselect(record);
|
|
1210
|
-
if (!selected(record) || force === true) return select(record);
|
|
1211
|
-
}
|
|
1212
|
-
/**
|
|
1213
|
-
* Checks whether the given record is selected.
|
|
1214
|
-
*/
|
|
1215
|
-
function selected(record) {
|
|
1216
|
-
if (selection.value.all) return !selection.value.except.has(record);
|
|
1217
|
-
return selection.value.only.has(record);
|
|
1218
|
-
}
|
|
1219
|
-
/**
|
|
1220
|
-
* Checks whether all records are selected.
|
|
1221
|
-
*/
|
|
1222
|
-
const allSelected = computed(() => {
|
|
1223
|
-
return selection.value.all && selection.value.except.size === 0;
|
|
1224
|
-
});
|
|
1225
|
-
/**
|
|
1226
|
-
* Binds a checkbox's properties.
|
|
1227
|
-
*/
|
|
1228
|
-
function bindCheckbox(key) {
|
|
1229
|
-
return {
|
|
1230
|
-
onChange: (event) => {
|
|
1231
|
-
const target = event.target;
|
|
1232
|
-
if (target.checked) select(target.value);
|
|
1233
|
-
else deselect(target.value);
|
|
1234
|
-
},
|
|
1235
|
-
checked: selected(key),
|
|
1236
|
-
value: key
|
|
1237
|
-
};
|
|
1238
|
-
}
|
|
1239
|
-
return {
|
|
1240
|
-
allSelected,
|
|
1241
|
-
selectAll,
|
|
1242
|
-
deselectAll,
|
|
1243
|
-
select,
|
|
1244
|
-
deselect,
|
|
1245
|
-
toggle,
|
|
1246
|
-
selected,
|
|
1247
|
-
selection,
|
|
1248
|
-
bindCheckbox
|
|
1249
|
-
};
|
|
1250
|
-
}
|
|
1251
|
-
|
|
1252
|
-
//#endregion
|
|
1253
|
-
//#region src/composables/query-parameters.ts
|
|
1254
|
-
/**
|
|
1255
|
-
* Access reactive query parameters.
|
|
1256
|
-
*
|
|
1257
|
-
* @see https://hybridly.dev/api/utils/use-query-parameters.html
|
|
1258
|
-
*/
|
|
1259
|
-
function useQueryParameters() {
|
|
1260
|
-
const state = reactive({});
|
|
1261
|
-
function updateState() {
|
|
1262
|
-
const params = new URLSearchParams(window.location.search);
|
|
1263
|
-
const unusedKeys = new Set(Object.keys(state));
|
|
1264
|
-
for (const key of params.keys()) {
|
|
1265
|
-
const paramsForKey = params.getAll(key);
|
|
1266
|
-
state[key] = paramsForKey.length > 1 ? paramsForKey : params.get(key) || "";
|
|
1267
|
-
unusedKeys.delete(key);
|
|
1268
|
-
}
|
|
1269
|
-
Array.from(unusedKeys).forEach((key) => delete state[key]);
|
|
1270
|
-
}
|
|
1271
|
-
updateState();
|
|
1272
|
-
registerHook$1("navigated", updateState);
|
|
1273
|
-
return state;
|
|
1274
|
-
}
|
|
1275
|
-
/**
|
|
1276
|
-
* Makes the specified query parameter reactive.
|
|
1277
|
-
*
|
|
1278
|
-
* @see https://hybridly.dev/api/utils/use-query-parameter.html
|
|
1279
|
-
*/
|
|
1280
|
-
function useQueryParameter(name, options = {}) {
|
|
1281
|
-
const query = useQueryParameters();
|
|
1282
|
-
const transform = (value) => {
|
|
1283
|
-
if (options.transform === "bool") return value === true || value === "true" || value === "1" || value === "yes";
|
|
1284
|
-
else if (options.transform === "number") return Number(value);
|
|
1285
|
-
else if (options.transform === "string") return String(value);
|
|
1286
|
-
else if (options.transform === "date") return new Date(value);
|
|
1287
|
-
else if (typeof options.transform === "function") return options.transform(value);
|
|
1288
|
-
return value;
|
|
1289
|
-
};
|
|
1290
|
-
const value = ref();
|
|
1291
|
-
watch(query, () => {
|
|
1292
|
-
value.value = transform(query[name] ?? toValue(options.defaultValue));
|
|
1293
|
-
}, {
|
|
1294
|
-
deep: true,
|
|
1295
|
-
immediate: true
|
|
1296
|
-
});
|
|
1297
|
-
return value;
|
|
1298
|
-
}
|
|
1299
|
-
|
|
1300
1932
|
//#endregion
|
|
1301
1933
|
//#region src/composables/table.ts
|
|
1302
1934
|
/**
|
|
1303
1935
|
* Provides utilities for working with tables.
|
|
1304
1936
|
*/
|
|
1305
|
-
function useTable(
|
|
1306
|
-
const table = computed(() =>
|
|
1937
|
+
function useTable(input, defaultOptions = {}) {
|
|
1938
|
+
const table = computed(() => toValue(input));
|
|
1307
1939
|
const bulk = useBulkSelect();
|
|
1308
|
-
const refinements = useRefinements(
|
|
1940
|
+
const refinements = useRefinements(() => toValue(input).refinements, defaultOptions);
|
|
1309
1941
|
/**
|
|
1310
1942
|
* Gets additionnal data to send with the request.
|
|
1311
1943
|
*/
|
|
1312
|
-
function getAdditionnalData() {
|
|
1944
|
+
function getAdditionnalData(options) {
|
|
1313
1945
|
const data = {};
|
|
1946
|
+
options = {
|
|
1947
|
+
...defaultOptions,
|
|
1948
|
+
...options
|
|
1949
|
+
};
|
|
1314
1950
|
if (defaultOptions?.includeQueryParameters !== false) Object.assign(data, structuredClone(toRaw(useQueryParameters())));
|
|
1315
|
-
if (
|
|
1951
|
+
if (options?.data) Object.assign(data, options.data);
|
|
1316
1952
|
return data;
|
|
1317
1953
|
}
|
|
1318
1954
|
/**
|
|
@@ -1320,34 +1956,73 @@ function useTable(props, key, defaultOptions = {}) {
|
|
|
1320
1956
|
*/
|
|
1321
1957
|
function getRecordKey(record) {
|
|
1322
1958
|
if (typeof record !== "object") return record;
|
|
1323
|
-
if (
|
|
1324
|
-
|
|
1959
|
+
if (!table.value.keyName) return;
|
|
1960
|
+
const value = Reflect.get(record, table.value.keyName);
|
|
1961
|
+
if (typeof value === "string" || typeof value === "number") return value;
|
|
1325
1962
|
}
|
|
1326
|
-
function
|
|
1327
|
-
return
|
|
1963
|
+
function getPageRecordKeys() {
|
|
1964
|
+
return table.value.records.map((record) => getRecordKey(record)).filter((key) => key !== void 0);
|
|
1965
|
+
}
|
|
1966
|
+
function warnMissingRecordKey(action) {
|
|
1967
|
+
console.warn(`Cannot ${action} because this table record has no key.`);
|
|
1968
|
+
}
|
|
1969
|
+
function getColumnName(column) {
|
|
1970
|
+
return typeof column === "string" ? column : column.name;
|
|
1971
|
+
}
|
|
1972
|
+
function getCell(index, column) {
|
|
1973
|
+
return table.value.cells[index]?.columns[getColumnName(column)] ?? {
|
|
1974
|
+
value: void 0,
|
|
1975
|
+
extra: {}
|
|
1976
|
+
};
|
|
1977
|
+
}
|
|
1978
|
+
function resolveInlineAction(action) {
|
|
1979
|
+
if (typeof action !== "string") return action;
|
|
1980
|
+
return table.value.inlineActions.find(({ name }) => name === action);
|
|
1981
|
+
}
|
|
1982
|
+
function resolveBulkAction(action) {
|
|
1983
|
+
if (typeof action !== "string") return action;
|
|
1984
|
+
return table.value.bulkActions.find(({ name }) => name === action);
|
|
1985
|
+
}
|
|
1986
|
+
function getActionUrl(action, table) {
|
|
1987
|
+
if (action.url) return action.url;
|
|
1988
|
+
return route$1(table.endpoint);
|
|
1328
1989
|
}
|
|
1329
1990
|
/**
|
|
1330
1991
|
* Executes the given inline action by name.
|
|
1331
1992
|
*/
|
|
1332
|
-
async function executeInlineAction(action,
|
|
1993
|
+
async function executeInlineAction(action, options) {
|
|
1994
|
+
const resolvedAction = resolveInlineAction(action);
|
|
1995
|
+
const recordKey = getRecordKey(options.record);
|
|
1996
|
+
if (!resolvedAction) {
|
|
1997
|
+
console.warn(`Action [${action}] is not defined`);
|
|
1998
|
+
return;
|
|
1999
|
+
}
|
|
2000
|
+
if (recordKey === void 0) {
|
|
2001
|
+
warnMissingRecordKey("execute an inline action");
|
|
2002
|
+
return;
|
|
2003
|
+
}
|
|
1333
2004
|
return await router$1.navigate({
|
|
1334
2005
|
method: "post",
|
|
1335
|
-
url:
|
|
2006
|
+
url: getActionUrl(resolvedAction, table.value),
|
|
1336
2007
|
preserveState: true,
|
|
1337
2008
|
data: {
|
|
1338
|
-
...getAdditionnalData(),
|
|
2009
|
+
...getAdditionnalData(options),
|
|
1339
2010
|
type: "action:inline",
|
|
1340
|
-
action:
|
|
2011
|
+
action: resolvedAction.name,
|
|
1341
2012
|
tableId: table.value.id,
|
|
1342
|
-
recordId:
|
|
2013
|
+
recordId: recordKey
|
|
1343
2014
|
}
|
|
1344
2015
|
});
|
|
1345
2016
|
}
|
|
1346
2017
|
/**
|
|
1347
2018
|
* Executes the given bulk action for the given records.
|
|
1348
2019
|
*/
|
|
1349
|
-
async function executeBulkAction(action, options) {
|
|
1350
|
-
const
|
|
2020
|
+
async function executeBulkAction(action, options = {}) {
|
|
2021
|
+
const resolvedAction = resolveBulkAction(action);
|
|
2022
|
+
if (!resolvedAction) {
|
|
2023
|
+
console.warn(`Action [${action}] is not defined`);
|
|
2024
|
+
return;
|
|
2025
|
+
}
|
|
1351
2026
|
const filterParameters = refinements.currentFilters().reduce((carry, filter) => {
|
|
1352
2027
|
return {
|
|
1353
2028
|
...carry,
|
|
@@ -1356,12 +2031,12 @@ function useTable(props, key, defaultOptions = {}) {
|
|
|
1356
2031
|
}, {});
|
|
1357
2032
|
return await router$1.navigate({
|
|
1358
2033
|
method: "post",
|
|
1359
|
-
url:
|
|
2034
|
+
url: getActionUrl(resolvedAction, table.value),
|
|
1360
2035
|
preserveState: true,
|
|
1361
2036
|
data: {
|
|
1362
|
-
...getAdditionnalData(),
|
|
2037
|
+
...getAdditionnalData(options),
|
|
1363
2038
|
type: "action:bulk",
|
|
1364
|
-
action:
|
|
2039
|
+
action: resolvedAction.name,
|
|
1365
2040
|
tableId: table.value.id,
|
|
1366
2041
|
all: bulk.selection.value.all,
|
|
1367
2042
|
only: [...bulk.selection.value.only],
|
|
@@ -1369,29 +2044,46 @@ function useTable(props, key, defaultOptions = {}) {
|
|
|
1369
2044
|
[refinements.filtersKey.value]: filterParameters
|
|
1370
2045
|
},
|
|
1371
2046
|
hooks: { after: () => {
|
|
1372
|
-
if (options?.deselect === true ||
|
|
2047
|
+
if (options?.deselect === true || resolvedAction.deselect !== false) bulk.deselectAll();
|
|
1373
2048
|
} }
|
|
1374
2049
|
});
|
|
1375
2050
|
}
|
|
1376
2051
|
return reactive({
|
|
1377
|
-
selectAll: bulk.selectAll,
|
|
2052
|
+
selectAll: () => table.value.keyName ? bulk.selectAll() : warnMissingRecordKey("select all records"),
|
|
1378
2053
|
deselectAll: bulk.deselectAll,
|
|
1379
|
-
selectPage: () => bulk.select(...
|
|
1380
|
-
deselectPage: () => bulk.deselect(...
|
|
1381
|
-
isPageSelected: computed(() =>
|
|
1382
|
-
|
|
2054
|
+
selectPage: () => bulk.select(...getPageRecordKeys()),
|
|
2055
|
+
deselectPage: () => bulk.deselect(...getPageRecordKeys()),
|
|
2056
|
+
isPageSelected: computed(() => {
|
|
2057
|
+
const keys = getPageRecordKeys();
|
|
2058
|
+
return keys.length > 0 && keys.every((key) => bulk.selected(key));
|
|
2059
|
+
}),
|
|
2060
|
+
isSelected: (record) => {
|
|
2061
|
+
const key = getRecordKey(record);
|
|
2062
|
+
return key === void 0 ? false : bulk.selected(key);
|
|
2063
|
+
},
|
|
1383
2064
|
allSelected: bulk.allSelected,
|
|
2065
|
+
anySelected: bulk.anySelected,
|
|
1384
2066
|
selection: bulk.selection,
|
|
1385
2067
|
bindCheckbox: (key) => bulk.bindCheckbox(key),
|
|
1386
|
-
toggle: (record) =>
|
|
1387
|
-
|
|
1388
|
-
|
|
2068
|
+
toggle: (record, force) => {
|
|
2069
|
+
const key = getRecordKey(record);
|
|
2070
|
+
return key === void 0 ? warnMissingRecordKey("toggle record selection") : bulk.toggle(key, force);
|
|
2071
|
+
},
|
|
2072
|
+
toggleAll: (force) => table.value.keyName ? bulk.toggleAll(force) : warnMissingRecordKey("toggle all records"),
|
|
2073
|
+
select: (record) => {
|
|
2074
|
+
const key = getRecordKey(record);
|
|
2075
|
+
return key === void 0 ? warnMissingRecordKey("select this record") : bulk.select(key);
|
|
2076
|
+
},
|
|
2077
|
+
deselect: (record) => {
|
|
2078
|
+
const key = getRecordKey(record);
|
|
2079
|
+
return key === void 0 ? warnMissingRecordKey("deselect this record") : bulk.deselect(key);
|
|
2080
|
+
},
|
|
1389
2081
|
inlineActions: computed(() => table.value.inlineActions.map((action) => ({
|
|
1390
|
-
execute: (record) => executeInlineAction(action
|
|
2082
|
+
execute: (record) => executeInlineAction(action, { record }),
|
|
1391
2083
|
...action
|
|
1392
2084
|
}))),
|
|
1393
2085
|
bulkActions: computed(() => table.value.bulkActions.map((action) => ({
|
|
1394
|
-
execute: (options) => executeBulkAction(action
|
|
2086
|
+
execute: (options) => executeBulkAction(action, options),
|
|
1395
2087
|
...action
|
|
1396
2088
|
}))),
|
|
1397
2089
|
executeInlineAction,
|
|
@@ -1402,31 +2094,45 @@ function useTable(props, key, defaultOptions = {}) {
|
|
|
1402
2094
|
isSorting: (direction) => refinements.isSorting(column.name, direction),
|
|
1403
2095
|
applyFilter: (value, options) => refinements.applyFilter(column.name, value, options),
|
|
1404
2096
|
clearFilter: (options) => refinements.clearFilter(column.name, options),
|
|
1405
|
-
isSortable: !!refinements.sorts.find((sort) => sort.name === column.name),
|
|
1406
|
-
isFilterable: !!refinements.filters.find((filters) => filters.name === column.name)
|
|
2097
|
+
isSortable: !!refinements.sorts.value.find((sort) => sort.name === column.name),
|
|
2098
|
+
isFilterable: !!refinements.filters.value.find((filters) => filters.name === column.name)
|
|
1407
2099
|
}))),
|
|
1408
|
-
data: computed(() => table.value.records
|
|
1409
|
-
|
|
2100
|
+
data: computed(() => table.value.records),
|
|
2101
|
+
records: computed(() => table.value.records.map((record, index) => {
|
|
2102
|
+
const recordKey = getRecordKey(record);
|
|
2103
|
+
const localKey = recordKey ?? `row-${index}`;
|
|
2104
|
+
const selected = recordKey === void 0 ? false : bulk.selected(recordKey);
|
|
2105
|
+
return {
|
|
2106
|
+
record,
|
|
2107
|
+
key: localKey,
|
|
2108
|
+
recordKey,
|
|
2109
|
+
hasKey: recordKey !== void 0,
|
|
2110
|
+
execute: (action) => executeInlineAction(action, { record }),
|
|
2111
|
+
actions: table.value.inlineActions.map((action) => ({
|
|
2112
|
+
...action,
|
|
2113
|
+
execute: () => executeInlineAction(action.name, { record })
|
|
2114
|
+
})),
|
|
2115
|
+
select: () => recordKey === void 0 ? warnMissingRecordKey("select this record") : bulk.select(recordKey),
|
|
2116
|
+
deselect: () => recordKey === void 0 ? warnMissingRecordKey("deselect this record") : bulk.deselect(recordKey),
|
|
2117
|
+
toggle: (force) => recordKey === void 0 ? warnMissingRecordKey("toggle record selection") : bulk.toggle(recordKey, force),
|
|
2118
|
+
selected,
|
|
2119
|
+
value: (column) => getCell(index, column).value,
|
|
2120
|
+
extra: (column, path) => getByPath(getCell(index, column).extra, path)
|
|
2121
|
+
};
|
|
1410
2122
|
})),
|
|
1411
|
-
|
|
1412
|
-
record: Object.values(record).map((record) => record.value),
|
|
1413
|
-
key: getRecordKey(record),
|
|
1414
|
-
execute: (action) => executeInlineAction(getActionName(action), getRecordKey(record)),
|
|
1415
|
-
actions: table.value.inlineActions.map((action) => ({
|
|
1416
|
-
...action,
|
|
1417
|
-
execute: () => executeInlineAction(action.name, getRecordKey(record))
|
|
1418
|
-
})),
|
|
1419
|
-
select: () => bulk.select(getRecordKey(record)),
|
|
1420
|
-
deselect: () => bulk.deselect(getRecordKey(record)),
|
|
1421
|
-
toggle: (force) => bulk.toggle(getRecordKey(record), force),
|
|
1422
|
-
selected: bulk.selected(getRecordKey(record)),
|
|
1423
|
-
value: (column) => record[typeof column === "string" ? column : column.name].value,
|
|
1424
|
-
extra: (column, path) => getByPath(record[typeof column === "string" ? column : column.name].extra, path)
|
|
1425
|
-
}))),
|
|
1426
|
-
paginator: computed(() => createPaginator(table.value.paginator)),
|
|
2123
|
+
paginator: computed(() => createPaginator(table.value.paginator, defaultOptions)),
|
|
1427
2124
|
...refinements
|
|
1428
2125
|
});
|
|
1429
2126
|
}
|
|
1430
|
-
|
|
1431
2127
|
//#endregion
|
|
1432
|
-
|
|
2128
|
+
//#region src/composables/validation.ts
|
|
2129
|
+
/** Accesses all validation errors grouped by bag name. */
|
|
2130
|
+
function useValidation() {
|
|
2131
|
+
return readonly(toReactive(computed(() => state.context.value?.validation ?? {})));
|
|
2132
|
+
}
|
|
2133
|
+
/** Accesses validation errors for a single bag. */
|
|
2134
|
+
function useValidationBag(bag = "default") {
|
|
2135
|
+
return readonly(toReactive(computed(() => state.context.value?.validation?.[bag] ?? {})));
|
|
2136
|
+
}
|
|
2137
|
+
//#endregion
|
|
2138
|
+
export { Deferred, Form, RouterLink, WhenVisible, createPaginator, getBulkSelectionRange, initializeHybridly, isBooleanFilter, isCallbackFilter, isDateFilter, isSelectFilter, isTernaryFilter, isTextFilter, isTrashedFilter, registerHook, route, router, setProperty, useBackForward, useBulkSelect, useDialog, useForm, useHistoryState, useProperties, useProperty, useQueryParameter, useQueryParameters, useRefinements, useRoute, useTable, useValidation, useValidationBag };
|