@hybridly/vue 0.10.0-beta.1 → 0.10.0-beta.10

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/index.mjs CHANGED
@@ -1,16 +1,127 @@
1
- import { computed, createApp, defineComponent, getCurrentInstance, h, isRef, nextTick, onUnmounted, reactive, readonly, ref, shallowRef, toRaw, toValue, triggerRef, unref, watch } from "vue";
1
+ import { n as __reExport, t as __exportAll } from "./_chunks/chunk.mjs";
2
2
  import { can, createRouter, definePlugin, makeUrl, registerHook as registerHook$1, route, route as route$1, router, router as router$1 } from "@hybridly/core";
3
3
  import { clone, debounce, debug, merge, random, setValueAtPath, showViewComponentErrorModal, unsetPropertyAtPath } from "@hybridly/utils";
4
+ import qs from "qs";
5
+ import { computed, createApp, defineComponent, getCurrentInstance, h, isRef, nextTick, onUnmounted, reactive, readonly, ref, shallowRef, toRaw, toValue, triggerRef, unref, watch } from "vue";
4
6
  import nprogress from "nprogress";
5
7
  import { setupDevtoolsPlugin } from "@vue/devtools-api";
6
- import qs from "qs";
7
8
  import { getByPath, setByPath } from "@clickbar/dot-diver";
8
9
  import isEqual from "lodash.isequal";
9
-
10
- //#region src/plugins/progress.ts
11
- /**
12
- * A plugin to display a progress bar.
13
- */
10
+ const RouterLink = defineComponent({
11
+ name: "RouterLink",
12
+ setup(_, { slots, attrs }) {
13
+ return (props) => {
14
+ let data = props.data ?? {};
15
+ const preloads = props.preload ?? false;
16
+ const preserveScroll = props.preserveScroll;
17
+ const preserveState = props.preserveState;
18
+ const url = makeUrl(props.href ?? "");
19
+ const method = props.method?.toUpperCase() ?? "GET";
20
+ const as = typeof props.as === "object" ? props.as : props.as?.toLowerCase() ?? "a";
21
+ if (method === "GET") {
22
+ debug.adapter("vue", "Moving data object to URL parameters.");
23
+ url.search = qs.stringify(merge(data, qs.parse(url.search, { ignoreQueryPrefix: true })), {
24
+ encodeValuesOnly: true,
25
+ arrayFormat: "indices"
26
+ });
27
+ data = {};
28
+ }
29
+ 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>`);
30
+ function performPreload(type) {
31
+ if (!preloads) return;
32
+ if (props.external) return;
33
+ if (method !== "GET") return;
34
+ if (type !== "mount" && props.disabled) return;
35
+ if (type === "hover" && preloads === "mount") return;
36
+ if (type === "mount" && preloads !== "mount") return;
37
+ router$1.preload(url, {
38
+ data,
39
+ preserveScroll,
40
+ preserveState,
41
+ ...props.options
42
+ });
43
+ }
44
+ performPreload("mount");
45
+ return h(props.as, {
46
+ ...attrs,
47
+ ...as === "a" ? { href: url } : {},
48
+ ...props.disabled ? { disabled: props.disabled } : {},
49
+ onMouseenter: () => performPreload("hover"),
50
+ onAuxclick: (event) => {
51
+ if (props.disabled) event.preventDefault();
52
+ },
53
+ onClick: (event) => {
54
+ if (props.disabled) {
55
+ event.preventDefault();
56
+ return;
57
+ }
58
+ if (props.external) return;
59
+ if (!shouldIntercept(event)) return;
60
+ event.preventDefault();
61
+ router$1.navigate({
62
+ url,
63
+ data,
64
+ method,
65
+ preserveState: method !== "GET",
66
+ ...props.options
67
+ });
68
+ }
69
+ }, slots.default ? slots : props.text);
70
+ };
71
+ },
72
+ props: {
73
+ href: {
74
+ type: String,
75
+ required: false,
76
+ default: void 0
77
+ },
78
+ as: {
79
+ type: [String, Object],
80
+ default: "a"
81
+ },
82
+ method: {
83
+ type: String,
84
+ default: "GET"
85
+ },
86
+ data: {
87
+ type: Object,
88
+ default: () => ({})
89
+ },
90
+ external: {
91
+ type: Boolean,
92
+ default: false
93
+ },
94
+ disabled: {
95
+ type: Boolean,
96
+ default: false
97
+ },
98
+ options: {
99
+ type: Object,
100
+ default: () => ({})
101
+ },
102
+ text: {
103
+ type: String,
104
+ required: false,
105
+ default: void 0
106
+ },
107
+ preload: {
108
+ type: [Boolean, String],
109
+ default: false
110
+ },
111
+ preserveScroll: {
112
+ type: Boolean,
113
+ default: void 0
114
+ },
115
+ preserveState: {
116
+ type: Boolean,
117
+ default: void 0
118
+ }
119
+ }
120
+ });
121
+ function shouldIntercept(event) {
122
+ const isLink = event.currentTarget.tagName.toLowerCase() === "a";
123
+ return !(event.target && (event?.target).isContentEditable || event.defaultPrevented || isLink && event.which > 1 || isLink && event.altKey || isLink && event.ctrlKey || isLink && event.metaKey || isLink && event.shiftKey);
124
+ }
14
125
  function progress(options) {
15
126
  const resolved = {
16
127
  delay: 250,
@@ -43,8 +154,8 @@ function progress(options) {
43
154
  clearTimeout(timeout);
44
155
  timeout = setTimeout(() => startProgress(), resolved.delay);
45
156
  },
46
- progress: (progress$1) => {
47
- if (nprogress.isStarted() && progress$1.percentage) nprogress.set(Math.max(nprogress.status, progress$1.percentage / 100 * .9));
157
+ progress: (progress) => {
158
+ if (nprogress.isStarted() && progress.percentage) nprogress.set(Math.max(nprogress.status, progress.percentage / 100 * .9));
48
159
  },
49
160
  after: () => finishProgress()
50
161
  });
@@ -114,9 +225,6 @@ function injectCSS(color) {
114
225
  `;
115
226
  document.head.appendChild(element);
116
227
  }
117
-
118
- //#endregion
119
- //#region src/stores/dialog.ts
120
228
  const DEBUG_KEY = "vue:state:dialog";
121
229
  const dialogStore = {
122
230
  state: {
@@ -159,16 +267,7 @@ const dialogStore = {
159
267
  }
160
268
  }
161
269
  };
162
-
163
- //#endregion
164
- //#region src/stores/mount.ts
165
- /**
166
- * These callbacks are run on the first stick after the view component is mounted.
167
- */
168
270
  const onMountedCallbacks = [];
169
-
170
- //#endregion
171
- //#region src/stores/state.ts
172
271
  const state = {
173
272
  context: shallowRef(),
174
273
  view: shallowRef(),
@@ -192,9 +291,6 @@ const state = {
192
291
  state.viewKey.value = unref(key);
193
292
  }
194
293
  };
195
-
196
- //#endregion
197
- //#region src/components/wrapper.ts
198
294
  const wrapper = defineComponent({
199
295
  name: "Hybridly",
200
296
  setup() {
@@ -255,9 +351,6 @@ const wrapper = defineComponent({
255
351
  };
256
352
  }
257
353
  });
258
-
259
- //#endregion
260
- //#region src/devtools.ts
261
354
  const hybridlyStateType = "hybridly";
262
355
  const hybridlyEventsTimelineLayerId = "Hybridly";
263
356
  function setupDevtools(app) {
@@ -364,9 +457,6 @@ function setupDevtools(app) {
364
457
  const devtools = { install(app) {
365
458
  if (process.env.NODE_ENV === "development" || __VUE_PROD_DEVTOOLS__) setupDevtools(app);
366
459
  } };
367
-
368
- //#endregion
369
- //#region src/plugins/view-transition.ts
370
460
  function viewTransition() {
371
461
  if (!document.startViewTransition) return { name: "view-transition" };
372
462
  let domUpdated;
@@ -389,9 +479,6 @@ function viewTransition() {
389
479
  }
390
480
  };
391
481
  }
392
-
393
- //#endregion
394
- //#region src/stores/form.ts
395
482
  const formStore = {
396
483
  defaultConfig: {},
397
484
  setDefaultConfig: (config) => {
@@ -401,12 +488,6 @@ const formStore = {
401
488
  return clone(formStore.defaultConfig);
402
489
  }
403
490
  };
404
-
405
- //#endregion
406
- //#region src/initialize.ts
407
- /**
408
- * Initializes Hybridly's router and context.
409
- */
410
491
  async function initializeHybridly(options = {}) {
411
492
  const resolved = options;
412
493
  const { element, payload, resolve } = prepare(resolved);
@@ -428,18 +509,18 @@ async function initializeHybridly(options = {}) {
428
509
  onContextUpdate: (context) => {
429
510
  state.setContext(context);
430
511
  },
431
- onViewSwap: async (options$1) => {
432
- if (options$1.component) {
433
- onMountedCallbacks.push(() => options$1.onMounted?.({ isDialog: false }));
434
- state.setView(options$1.component);
512
+ onViewSwap: async (options) => {
513
+ if (options.component) {
514
+ onMountedCallbacks.push(() => options.onMounted?.({ isDialog: false }));
515
+ state.setView(options.component);
435
516
  }
436
- state.setProperties(options$1.properties);
437
- if (!options$1.preserveState && !options$1.dialog) state.setViewKey(random());
438
- if (options$1.dialog) {
439
- onMountedCallbacks.push(() => options$1.onMounted?.({ isDialog: true }));
440
- dialogStore.setComponent(await resolve(options$1.dialog.component));
441
- dialogStore.setProperties(options$1.dialog.properties);
442
- dialogStore.setKey(options$1.dialog.key);
517
+ state.setProperties(options.properties);
518
+ if (!options.preserveState && !options.dialog) state.setViewKey(random());
519
+ if (options.dialog) {
520
+ onMountedCallbacks.push(() => options.onMounted?.({ isDialog: true }));
521
+ dialogStore.setComponent(await resolve(options.dialog.component));
522
+ dialogStore.setProperties(options.dialog.properties);
523
+ dialogStore.setKey(options.dialog.key);
443
524
  dialogStore.show();
444
525
  } else dialogStore.hide();
445
526
  }
@@ -490,13 +571,10 @@ function prepare(options) {
490
571
  resolve
491
572
  };
492
573
  }
493
- /**
494
- * Resolves a view by its name.
495
- */
496
574
  async function resolveViewComponent(name, options) {
497
575
  const components = options.imported;
498
576
  const result = options.components.views.find((view) => name === view.identifier);
499
- const path = Object.keys(components).sort((a, b) => a.length - b.length).find((path$1) => result ? path$1.endsWith(result?.path) : false);
577
+ const path = Object.keys(components).sort((a, b) => a.length - b.length).find((path) => result ? path.endsWith(result?.path) : false);
500
578
  if (!result || !path) {
501
579
  console.warn(`View component [${name}] not found. Available components: `, options.components.views.map(({ identifier }) => identifier));
502
580
  showViewComponentErrorModal(name);
@@ -506,133 +584,6 @@ async function resolveViewComponent(name, options) {
506
584
  component = component.default ?? component;
507
585
  return component;
508
586
  }
509
-
510
- //#endregion
511
- //#region src/components/link.ts
512
- const RouterLink = defineComponent({
513
- name: "RouterLink",
514
- setup(_, { slots, attrs }) {
515
- return (props) => {
516
- let data = props.data ?? {};
517
- const preloads = props.preload ?? false;
518
- const preserveScroll = props.preserveScroll;
519
- const preserveState = props.preserveState;
520
- const url = makeUrl(props.href ?? "");
521
- const method = props.method?.toUpperCase() ?? "GET";
522
- const as = typeof props.as === "object" ? props.as : props.as?.toLowerCase() ?? "a";
523
- if (method === "GET") {
524
- debug.adapter("vue", "Moving data object to URL parameters.");
525
- url.search = qs.stringify(merge(data, qs.parse(url.search, { ignoreQueryPrefix: true })), {
526
- encodeValuesOnly: true,
527
- arrayFormat: "indices"
528
- });
529
- data = {};
530
- }
531
- 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>`);
532
- function performPreload(type) {
533
- if (!preloads) return;
534
- if (props.external) return;
535
- if (method !== "GET") return;
536
- if (type !== "mount" && props.disabled) return;
537
- if (type === "hover" && preloads === "mount") return;
538
- if (type === "mount" && preloads !== "mount") return;
539
- router$1.preload(url, {
540
- data,
541
- preserveScroll,
542
- preserveState,
543
- ...props.options
544
- });
545
- }
546
- performPreload("mount");
547
- return h(props.as, {
548
- ...attrs,
549
- ...as === "a" ? { href: url } : {},
550
- ...props.disabled ? { disabled: props.disabled } : {},
551
- onMouseenter: () => performPreload("hover"),
552
- onAuxclick: (event) => {
553
- if (props.disabled) event.preventDefault();
554
- },
555
- onClick: (event) => {
556
- if (props.disabled) {
557
- event.preventDefault();
558
- return;
559
- }
560
- if (props.external) return;
561
- if (!shouldIntercept(event)) return;
562
- event.preventDefault();
563
- router$1.navigate({
564
- url,
565
- data,
566
- method,
567
- preserveState: method !== "GET",
568
- ...props.options
569
- });
570
- }
571
- }, slots.default ? slots : props.text);
572
- };
573
- },
574
- props: {
575
- href: {
576
- type: String,
577
- required: false,
578
- default: void 0
579
- },
580
- as: {
581
- type: [String, Object],
582
- default: "a"
583
- },
584
- method: {
585
- type: String,
586
- default: "GET"
587
- },
588
- data: {
589
- type: Object,
590
- default: () => ({})
591
- },
592
- external: {
593
- type: Boolean,
594
- default: false
595
- },
596
- disabled: {
597
- type: Boolean,
598
- default: false
599
- },
600
- options: {
601
- type: Object,
602
- default: () => ({})
603
- },
604
- text: {
605
- type: String,
606
- required: false,
607
- default: void 0
608
- },
609
- preload: {
610
- type: [Boolean, String],
611
- default: false
612
- },
613
- preserveScroll: {
614
- type: Boolean,
615
- default: void 0
616
- },
617
- preserveState: {
618
- type: Boolean,
619
- default: void 0
620
- }
621
- }
622
- });
623
- function shouldIntercept(event) {
624
- const isLink = event.currentTarget.tagName.toLowerCase() === "a";
625
- return !(event.target && (event?.target).isContentEditable || event.defaultPrevented || isLink && event.which > 1 || isLink && event.altKey || isLink && event.ctrlKey || isLink && event.metaKey || isLink && event.shiftKey);
626
- }
627
-
628
- //#endregion
629
- //#region src/utils.ts
630
- /**
631
- * Converts ref to reactive.
632
- *
633
- * @see https://vueuse.org/toReactive
634
- * @param objectRef A ref of object
635
- */
636
587
  function toReactive(objectRef) {
637
588
  if (!isRef(objectRef)) return reactive(objectRef);
638
589
  return reactive(new Proxy({}, {
@@ -661,29 +612,17 @@ function toReactive(objectRef) {
661
612
  }
662
613
  }));
663
614
  }
664
-
665
- //#endregion
666
- //#region src/composables/property.ts
667
- /** Accesses all current properties. */
668
615
  function useProperties() {
669
616
  return readonly(toReactive(computed(() => state.properties.value)));
670
617
  }
671
- /** Accesses a property with a dot notation. */
672
618
  function useProperty(path) {
673
619
  return computed(() => getByPath(state.properties.value, path));
674
620
  }
675
- /**
676
- * Sets the property at the given path to the given value.
677
- * Note: this helper is experimental and may change in the future.
678
- */
679
621
  function setProperty(path, value) {
680
622
  if (!state.properties.value) return;
681
623
  setByPath(state.properties.value, path, toValue(value));
682
624
  if (state.context.value?.view.properties) setByPath(state.context.value.view.properties, path, toValue(value));
683
625
  }
684
-
685
- //#endregion
686
- //#region src/composables/form.ts
687
626
  function safeClone(obj) {
688
627
  return clone(toRaw(obj));
689
628
  }
@@ -695,39 +634,22 @@ function useForm(options) {
695
634
  recentlyFailed: void 0,
696
635
  recentlySuccessful: void 0
697
636
  };
698
- /** Fields that were initially set up. */
699
637
  const initial = safeClone(options.fields);
700
- /** Fields as they were when loaded. */
701
638
  const loaded = safeClone(historyData?.fields ?? options.fields);
702
- /** Current fields. */
703
639
  const fields = reactive(safeClone(loaded));
704
- /** Validation errors for each field. */
705
640
  const errors = ref(historyData?.errors ?? {});
706
- /** Whether the form is dirty. */
707
641
  const isDirty = ref(false);
708
- /** Whether the submission was recently successful. */
709
642
  const recentlySuccessful = ref(false);
710
- /** Whether the submission is successful. */
711
643
  const successful = ref(false);
712
- /** Whether the submission was recently failed. */
713
644
  const recentlyFailed = ref(false);
714
- /** Whether the submission is failed. */
715
645
  const failed = ref(false);
716
- /** Whether the submission is being processed. */
717
646
  const processing = ref(false);
718
- /** The current request's progress. */
719
- const progress$1 = ref();
720
- /**
721
- * Sets new initial values for the form, so subsequent resets will use thse values.
722
- */
647
+ const progress = ref();
723
648
  function setInitial(newInitial) {
724
649
  Object.entries(newInitial).forEach(([key, value]) => {
725
650
  Reflect.set(initial, key, safeClone(value));
726
651
  });
727
652
  }
728
- /**
729
- * Resets the form's failed and successful flags.
730
- */
731
653
  function resetSubmissionState() {
732
654
  successful.value = false;
733
655
  failed.value = false;
@@ -735,37 +657,25 @@ function useForm(options) {
735
657
  recentlySuccessful.value = false;
736
658
  clearTimeout(timeoutIds.recentlySuccessful);
737
659
  clearTimeout(timeoutIds.recentlyFailed);
738
- progress$1.value = void 0;
660
+ progress.value = void 0;
739
661
  }
740
- /**
741
- * Resets the fields, errors and submission state.
742
- */
743
662
  function reset() {
744
663
  resetSubmissionState();
745
664
  clearErrors();
746
665
  resetFields();
747
666
  }
748
- /**
749
- * Resets the fields to their initial values.
750
- */
751
667
  function resetFields(...keys) {
752
668
  if (keys.length === 0) keys = Object.keys(fields);
753
669
  keys.forEach((key) => {
754
670
  Reflect.set(fields, key, safeClone(Reflect.get(initial, key)));
755
671
  });
756
672
  }
757
- /**
758
- * Clear the form fields.
759
- */
760
673
  function clear(...keys) {
761
674
  if (keys.length === 0) keys = Object.keys(fields);
762
675
  keys.forEach((key) => {
763
676
  delete fields[key];
764
677
  });
765
678
  }
766
- /**
767
- * Submits the form.
768
- */
769
679
  function submit(optionsOverrides) {
770
680
  const { fields: _f, key: _k, ...optionsWithoutFields } = options;
771
681
  const resolvedOptions = optionsOverrides ? merge(optionsWithoutFields, optionsOverrides, { mergePlainObjects: true }) : optionsWithoutFields;
@@ -790,7 +700,7 @@ function useForm(options) {
790
700
  return hooks.start?.(context);
791
701
  },
792
702
  progress: (incoming, context) => {
793
- progress$1.value = incoming;
703
+ progress.value = incoming;
794
704
  return hooks.progress?.(incoming, context);
795
705
  },
796
706
  error: (incoming, context) => {
@@ -810,47 +720,32 @@ function useForm(options) {
810
720
  return hooks.success?.(payload, context);
811
721
  },
812
722
  after: (context) => {
813
- progress$1.value = void 0;
723
+ progress.value = void 0;
814
724
  processing.value = false;
815
725
  return hooks.after?.(context);
816
726
  }
817
727
  }
818
728
  });
819
729
  }
820
- /**
821
- * Clears all errors.
822
- */
823
730
  function clearErrors(...keys) {
824
731
  if (keys.length === 0) keys = Object.keys(fields);
825
732
  keys.forEach((key) => {
826
733
  clearError(key);
827
734
  });
828
735
  }
829
- /**
830
- * Checks if the given keys are dirty in the form.
831
- */
832
736
  function hasDirty(...keys) {
833
737
  if (keys.length === 0) return isDirty.value;
834
738
  return keys.some((key) => !isEqual(toRaw(getByPath(fields, key)), toRaw(getByPath(initial, key))));
835
739
  }
836
- /**
837
- * Clears the given field's error.
838
- */
839
740
  function clearError(key) {
840
741
  unsetPropertyAtPath(errors.value, key);
841
742
  }
842
- /**
843
- * Sets current errors.
844
- */
845
743
  function setErrors(incoming) {
846
744
  clearErrors();
847
745
  Object.entries(incoming).forEach(([path, value]) => {
848
746
  setValueAtPath(errors.value, path, value);
849
747
  });
850
748
  }
851
- /**
852
- * Aborts the submission.
853
- */
854
749
  function abort() {
855
750
  router$1.abort();
856
751
  }
@@ -886,7 +781,7 @@ function useForm(options) {
886
781
  hasErrors: computed(() => Object.values(errors.value ?? {}).length > 0),
887
782
  initial,
888
783
  loaded,
889
- progress: progress$1,
784
+ progress,
890
785
  isDirty,
891
786
  errors,
892
787
  processing,
@@ -896,45 +791,29 @@ function useForm(options) {
896
791
  recentlyFailed
897
792
  });
898
793
  }
899
-
900
- //#endregion
901
- //#region src/composables/history.ts
902
- /**
903
- * Returns a ref with a value saved in the history state through Hybridly.
904
- * The state is linked to a specific browser history entry.
905
- */
906
794
  function useHistoryState(key, initial) {
907
795
  const value = ref(router$1.history.get(key) ?? initial);
908
- watch(value, (value$1) => {
909
- router$1.history.remember(key, toRaw(value$1));
796
+ watch(value, (value) => {
797
+ router$1.history.remember(key, toRaw(value));
910
798
  }, {
911
799
  immediate: true,
912
800
  deep: true
913
801
  });
914
802
  return value;
915
803
  }
916
-
917
- //#endregion
918
- //#region src/composables/back-forward.ts
919
804
  function useBackForward(options) {
920
805
  const callbacks = [];
921
- registerHook$1("navigated", (options$1) => {
922
- if (options$1.type === "back-forward") {
806
+ registerHook$1("navigated", (options) => {
807
+ if (options.type === "back-forward") {
923
808
  callbacks.forEach((fn) => fn(state.context.value));
924
809
  callbacks.splice(0, callbacks.length);
925
810
  }
926
811
  });
927
- /**
928
- * Applies the given callback on back-forward navigations.
929
- */
930
812
  function onBackForward(fn) {
931
813
  callbacks.push(fn);
932
814
  }
933
- /**
934
- * Reloads the page on back-forward navigations.
935
- */
936
- function reloadOnBackForward(options$1) {
937
- onBackForward(() => router$1.reload(options$1));
815
+ function reloadOnBackForward(options) {
816
+ onBackForward(() => router$1.reload(options));
938
817
  }
939
818
  if (options?.reload) reloadOnBackForward(options.reload === true ? void 0 : options.reload);
940
819
  return {
@@ -942,24 +821,31 @@ function useBackForward(options) {
942
821
  reloadOnBackForward
943
822
  };
944
823
  }
945
-
946
- //#endregion
947
- //#region src/composables/register-hook.ts
948
- /**
949
- * Registers a global hook.
950
- * If called inside a component, unregisters after the component is unmounted.
951
- */
824
+ var src_exports = /* @__PURE__ */ __exportAll({});
825
+ import * as import__hybridly_core from "@hybridly/core";
826
+ __reExport(src_exports, import__hybridly_core);
827
+ function createPaginator(paginator, defaultOptions) {
828
+ const isPaginator = (p) => {
829
+ return "links" in p && "meta" in p && "last_page" in p.meta;
830
+ };
831
+ if (isPaginator(paginator)) return {
832
+ ...paginator,
833
+ to: (page, options) => {
834
+ const link = paginator.links.find((l) => l.page === page);
835
+ if (link?.url) return src_exports.router.get(link.url, {
836
+ preserveState: true,
837
+ ...defaultOptions,
838
+ ...options
839
+ });
840
+ }
841
+ };
842
+ return paginator;
843
+ }
952
844
  const registerHook = (hook, fn, options) => {
953
845
  const unregister = registerHook$1(hook, fn, options);
954
846
  if (getCurrentInstance()) onUnmounted(() => unregister());
955
847
  return unregister;
956
848
  };
957
-
958
- //#endregion
959
- //#region src/composables/dialog.ts
960
- /**
961
- * Exposes utilities related to the dialogs.
962
- */
963
849
  function useDialog() {
964
850
  return {
965
851
  close: () => router$1.dialog.close(),
@@ -972,22 +858,69 @@ function useDialog() {
972
858
  properties: computed(() => state.context.value?.dialog?.properties)
973
859
  };
974
860
  }
975
-
976
- //#endregion
977
- //#region src/composables/refinements.ts
978
- function useRefinements(properties, refinementsKeys, defaultOptions = {}) {
979
- const refinements = computed(() => properties[refinementsKeys]);
861
+ function isTextFilter(filter) {
862
+ return filter.type === "text";
863
+ }
864
+ function isDateFilter(filter) {
865
+ return filter.type === "date";
866
+ }
867
+ function isSelectFilter(filter) {
868
+ return filter.type === "select";
869
+ }
870
+ function isTernaryFilter(filter) {
871
+ return filter.type === "ternary";
872
+ }
873
+ function isBooleanFilter(filter) {
874
+ return filter.type === "boolean";
875
+ }
876
+ function isTrashedFilter(filter) {
877
+ return filter.type === "trashed";
878
+ }
879
+ function isCallbackFilter(filter) {
880
+ return filter.type === "callback" || ![
881
+ "text",
882
+ "select",
883
+ "ternary",
884
+ "boolean",
885
+ "trashed"
886
+ ].includes(filter.type);
887
+ }
888
+ function useRefinements(input, defaultOptions = {}) {
889
+ const refinements = computed(() => toValue(input));
980
890
  const sortsKey = computed(() => refinements.value.keys.sorts);
981
891
  const filtersKey = computed(() => refinements.value.keys.filters);
982
892
  defaultOptions = {
983
893
  replace: false,
984
894
  ...defaultOptions
985
895
  };
896
+ const sorts = computed(() => {
897
+ return refinements.value.sorts.map((sort) => ({
898
+ ...sort,
899
+ toggle: (options) => toggleSort(sort.name, options),
900
+ isSorting: (direction) => isSorting(sort.name, direction),
901
+ clear: (options) => clearSorts(options)
902
+ }));
903
+ });
904
+ const filters = computed(() => {
905
+ return refinements.value.filters.map((filter) => ({
906
+ ...filter,
907
+ apply: (value, options) => applyFilter(filter.name, value, options),
908
+ clear: (options) => clearFilter(filter.name, options),
909
+ search: (query, options) => searchFilter(filter.name, query, options),
910
+ isSelectFilter: () => isSelectFilter(filter),
911
+ isDateFilter: () => isDateFilter(filter),
912
+ isSearchFilter: () => isTextFilter(filter),
913
+ isTernaryFilter: () => isTernaryFilter(filter),
914
+ isBooleanFilter: () => isBooleanFilter(filter),
915
+ isTrashedFilter: () => isTrashedFilter(filter),
916
+ isCallbackFilter: () => isCallbackFilter(filter)
917
+ }));
918
+ });
986
919
  function getSort(name) {
987
- return refinements.value.sorts.find((sort) => sort.name === name);
920
+ return sorts.value.find((sort) => sort.name === name);
988
921
  }
989
922
  function getFilter(name) {
990
- return refinements.value.filters.find((sort) => sort.name === name);
923
+ return filters.value.find((sort) => sort.name === name);
991
924
  }
992
925
  async function reset(options = {}) {
993
926
  return await router$1.reload({
@@ -1013,6 +946,18 @@ function useRefinements(properties, refinementsKeys, defaultOptions = {}) {
1013
946
  data: { [filtersKey.value]: { [filter]: void 0 } }
1014
947
  });
1015
948
  }
949
+ async function searchFilter(name, query, options = {}) {
950
+ if (!getFilter(name)) {
951
+ console.warn(`[Refinement] Filter "${name}" does not exist.`);
952
+ return;
953
+ }
954
+ if (["", null].includes(query?.toString() || null)) query = void 0;
955
+ return await router$1.reload({
956
+ ...defaultOptions,
957
+ ...options,
958
+ data: { [filtersKey.value]: { [name]: { search: query } } }
959
+ });
960
+ }
1016
961
  async function applyFilter(name, value, options = {}) {
1017
962
  const filter = getFilter(name);
1018
963
  if (!filter) {
@@ -1020,10 +965,20 @@ function useRefinements(properties, refinementsKeys, defaultOptions = {}) {
1020
965
  return;
1021
966
  }
1022
967
  if (["", null].includes(value) || value === filter.default) value = void 0;
968
+ const operator = (() => {
969
+ const operator = options.operator ?? filter.operator;
970
+ if (operator === filter.default_operator) return;
971
+ return operator;
972
+ })();
1023
973
  return await router$1.reload({
1024
974
  ...defaultOptions,
1025
975
  ...options,
1026
- data: { [filtersKey.value]: { [name]: value } }
976
+ data: { [filtersKey.value]: { [name]: {
977
+ value,
978
+ search: void 0,
979
+ operator,
980
+ options: options.options ?? void 0
981
+ } } }
1027
982
  });
1028
983
  }
1029
984
  async function clearSorts(options = {}) {
@@ -1094,18 +1049,10 @@ function useRefinements(properties, refinementsKeys, defaultOptions = {}) {
1094
1049
  }
1095
1050
  return {
1096
1051
  bindFilter,
1097
- filters: toReactive(refinements.value.filters.map((filter) => ({
1098
- ...filter,
1099
- apply: (value, options) => applyFilter(filter.name, value, options),
1100
- clear: (options) => clearFilter(filter.name, options)
1101
- }))),
1102
- sorts: toReactive(refinements.value.sorts.map((sort) => ({
1103
- ...sort,
1104
- toggle: (options) => toggleSort(sort.name, options),
1105
- isSorting: (direction) => isSorting(sort.name, direction),
1106
- clear: (options) => clearSorts(options)
1107
- }))),
1052
+ filters,
1053
+ sorts,
1108
1054
  filtersKey,
1055
+ sortsKey,
1109
1056
  getFilter,
1110
1057
  getSort,
1111
1058
  reset,
@@ -1120,9 +1067,6 @@ function useRefinements(properties, refinementsKeys, defaultOptions = {}) {
1120
1067
  applyFilter
1121
1068
  };
1122
1069
  }
1123
-
1124
- //#endregion
1125
- //#region src/composables/current-route.ts
1126
1070
  const isNavigating = ref(false);
1127
1071
  function useRoute() {
1128
1072
  const current = ref(router$1.current());
@@ -1140,68 +1084,48 @@ function useRoute() {
1140
1084
  matches
1141
1085
  };
1142
1086
  }
1143
-
1144
- //#endregion
1145
- //#region src/composables/bulk-select.ts
1146
1087
  function useBulkSelect() {
1147
1088
  const selection = ref({
1148
1089
  all: false,
1149
1090
  only: /* @__PURE__ */ new Set(),
1150
1091
  except: /* @__PURE__ */ new Set()
1151
1092
  });
1152
- /**
1153
- * Selects all records.
1154
- */
1093
+ function toggleAll(force) {
1094
+ if (!selection.value.all || force === true) selectAll();
1095
+ else deselectAll();
1096
+ }
1155
1097
  function selectAll() {
1156
1098
  selection.value.all = true;
1157
1099
  selection.value.only.clear();
1158
1100
  selection.value.except.clear();
1159
1101
  }
1160
- /**
1161
- * Deselects all records.
1162
- */
1163
1102
  function deselectAll() {
1164
1103
  selection.value.all = false;
1165
1104
  selection.value.only.clear();
1166
1105
  selection.value.except.clear();
1167
1106
  }
1168
- /**
1169
- * Selects the given records.
1170
- */
1171
1107
  function select(...records) {
1172
1108
  records.forEach((record) => selection.value.except.delete(record));
1173
1109
  records.forEach((record) => selection.value.only.add(record));
1174
1110
  }
1175
- /**
1176
- * Deselects the given records.
1177
- */
1178
1111
  function deselect(...records) {
1179
1112
  records.forEach((record) => selection.value.except.add(record));
1180
1113
  records.forEach((record) => selection.value.only.delete(record));
1181
1114
  }
1182
- /**
1183
- * Toggles selection for the given records.
1184
- */
1185
1115
  function toggle(record, force) {
1186
1116
  if (selected(record) || force === false) return deselect(record);
1187
1117
  if (!selected(record) || force === true) return select(record);
1188
1118
  }
1189
- /**
1190
- * Checks whether the given record is selected.
1191
- */
1192
1119
  function selected(record) {
1193
1120
  if (selection.value.all) return !selection.value.except.has(record);
1194
1121
  return selection.value.only.has(record);
1195
1122
  }
1196
- /**
1197
- * Checks whether all records are selected.
1198
- */
1199
1123
  const allSelected = computed(() => {
1200
1124
  return selection.value.all && selection.value.except.size === 0;
1201
1125
  });
1202
- /**
1203
- * Binds a checkbox's properties.
1204
- */
1126
+ const anySelected = computed(() => {
1127
+ return selection.value.all || selection.value.only.size > 0;
1128
+ });
1205
1129
  function bindCheckbox(key) {
1206
1130
  return {
1207
1131
  onChange: (event) => {
@@ -1215,6 +1139,8 @@ function useBulkSelect() {
1215
1139
  }
1216
1140
  return {
1217
1141
  allSelected,
1142
+ anySelected,
1143
+ toggleAll,
1218
1144
  selectAll,
1219
1145
  deselectAll,
1220
1146
  select,
@@ -1225,44 +1151,31 @@ function useBulkSelect() {
1225
1151
  bindCheckbox
1226
1152
  };
1227
1153
  }
1228
-
1229
- //#endregion
1230
- //#region src/composables/query-parameters.ts
1231
- /**
1232
- * Access reactive query parameters.
1233
- *
1234
- * @see https://hybridly.dev/api/utils/use-query-parameters.html
1235
- */
1236
1154
  function useQueryParameters() {
1237
- const state$1 = reactive({});
1155
+ const state = reactive({});
1238
1156
  function updateState() {
1239
1157
  const params = new URLSearchParams(window.location.search);
1240
- const unusedKeys = new Set(Object.keys(state$1));
1158
+ const unusedKeys = new Set(Object.keys(state));
1241
1159
  for (const key of params.keys()) {
1242
1160
  const paramsForKey = params.getAll(key);
1243
- state$1[key] = paramsForKey.length > 1 ? paramsForKey : params.get(key) || "";
1161
+ state[key] = paramsForKey.length > 1 ? paramsForKey : params.get(key) || "";
1244
1162
  unusedKeys.delete(key);
1245
1163
  }
1246
- Array.from(unusedKeys).forEach((key) => delete state$1[key]);
1164
+ Array.from(unusedKeys).forEach((key) => delete state[key]);
1247
1165
  }
1248
1166
  updateState();
1249
1167
  registerHook$1("navigated", updateState);
1250
- return state$1;
1168
+ return state;
1251
1169
  }
1252
- /**
1253
- * Makes the specified query parameter reactive.
1254
- *
1255
- * @see https://hybridly.dev/api/utils/use-query-parameter.html
1256
- */
1257
1170
  function useQueryParameter(name, options = {}) {
1258
1171
  const query = useQueryParameters();
1259
- const transform = (value$1) => {
1260
- if (options.transform === "bool") return value$1 === true || value$1 === "true" || value$1 === "1" || value$1 === "yes";
1261
- else if (options.transform === "number") return Number(value$1);
1262
- else if (options.transform === "string") return String(value$1);
1263
- else if (options.transform === "date") return new Date(value$1);
1264
- else if (typeof options.transform === "function") return options.transform(value$1);
1265
- return value$1;
1172
+ const transform = (value) => {
1173
+ if (options.transform === "bool") return value === true || value === "true" || value === "1" || value === "yes";
1174
+ else if (options.transform === "number") return Number(value);
1175
+ else if (options.transform === "string") return String(value);
1176
+ else if (options.transform === "date") return new Date(value);
1177
+ else if (typeof options.transform === "function") return options.transform(value);
1178
+ return value;
1266
1179
  };
1267
1180
  const value = ref();
1268
1181
  watch(query, () => {
@@ -1273,58 +1186,65 @@ function useQueryParameter(name, options = {}) {
1273
1186
  });
1274
1187
  return value;
1275
1188
  }
1276
-
1277
- //#endregion
1278
- //#region src/composables/table.ts
1279
- /**
1280
- * Provides utilities for working with tables.
1281
- */
1282
- function useTable(props, key, defaultOptions = {}) {
1283
- const table = computed(() => props[key]);
1189
+ function useTable(input, defaultOptions = {}) {
1190
+ const table = computed(() => toValue(input));
1284
1191
  const bulk = useBulkSelect();
1285
- const refinements = useRefinements(toReactive(table), "refinements", defaultOptions);
1286
- /**
1287
- * Gets additionnal data to send with the request.
1288
- */
1289
- function getAdditionnalData() {
1192
+ const refinements = useRefinements(() => toValue(input).refinements, defaultOptions);
1193
+ function getAdditionnalData(options) {
1290
1194
  const data = {};
1195
+ options = {
1196
+ ...defaultOptions,
1197
+ ...options
1198
+ };
1291
1199
  if (defaultOptions?.includeQueryParameters !== false) Object.assign(data, structuredClone(toRaw(useQueryParameters())));
1292
- if (defaultOptions?.data) Object.assign(data, defaultOptions.data);
1200
+ if (options?.data) Object.assign(data, options.data);
1293
1201
  return data;
1294
1202
  }
1295
- /**
1296
- * Gets the actual identifier for a record.
1297
- */
1298
1203
  function getRecordKey(record) {
1299
1204
  if (typeof record !== "object") return record;
1300
1205
  if (Reflect.has(record, "__hybridId")) return Reflect.get(record, "__hybridId");
1301
- return Reflect.get(record, table.value.keyName).value;
1206
+ if (!table.value.keyName) throw new Error("Record key cannot be fetched because the table has no defined key.");
1207
+ const value = Reflect.get(record, table.value.keyName);
1208
+ if (typeof value === "object" && Reflect.has(value, "value")) return value.value;
1209
+ return value;
1210
+ }
1211
+ function resolveInlineAction(action) {
1212
+ if (typeof action !== "string") return action;
1213
+ return table.value.inlineActions.find(({ name }) => name === action);
1214
+ }
1215
+ function resolveBulkAction(action) {
1216
+ if (typeof action !== "string") return action;
1217
+ return table.value.bulkActions.find(({ name }) => name === action);
1302
1218
  }
1303
- function getActionName(action) {
1304
- return typeof action === "string" ? action : action.name;
1219
+ function getActionUrl(action, table) {
1220
+ if (action.url) return action.url;
1221
+ return route$1(table.endpoint);
1305
1222
  }
1306
- /**
1307
- * Executes the given inline action by name.
1308
- */
1309
- async function executeInlineAction(action, record) {
1223
+ async function executeInlineAction(action, options) {
1224
+ const resolvedAction = resolveInlineAction(action);
1225
+ if (!resolvedAction) {
1226
+ console.warn(`Action [${action}] is not defined`);
1227
+ return;
1228
+ }
1310
1229
  return await router$1.navigate({
1311
1230
  method: "post",
1312
- url: route$1(table.value.endpoint),
1231
+ url: getActionUrl(resolvedAction, table.value),
1313
1232
  preserveState: true,
1314
1233
  data: {
1315
- ...getAdditionnalData(),
1234
+ ...getAdditionnalData(options),
1316
1235
  type: "action:inline",
1317
- action: getActionName(action),
1236
+ action: resolvedAction.name,
1318
1237
  tableId: table.value.id,
1319
- recordId: getRecordKey(record)
1238
+ recordId: getRecordKey(options.record)
1320
1239
  }
1321
1240
  });
1322
1241
  }
1323
- /**
1324
- * Executes the given bulk action for the given records.
1325
- */
1326
- async function executeBulkAction(action, options) {
1327
- const actionName = getActionName(action);
1242
+ async function executeBulkAction(action, options = {}) {
1243
+ const resolvedAction = resolveBulkAction(action);
1244
+ if (!resolvedAction) {
1245
+ console.warn(`Action [${action}] is not defined`);
1246
+ return;
1247
+ }
1328
1248
  const filterParameters = refinements.currentFilters().reduce((carry, filter) => {
1329
1249
  return {
1330
1250
  ...carry,
@@ -1333,12 +1253,12 @@ function useTable(props, key, defaultOptions = {}) {
1333
1253
  }, {});
1334
1254
  return await router$1.navigate({
1335
1255
  method: "post",
1336
- url: route$1(table.value.endpoint),
1256
+ url: getActionUrl(resolvedAction, table.value),
1337
1257
  preserveState: true,
1338
1258
  data: {
1339
- ...getAdditionnalData(),
1259
+ ...getAdditionnalData(options),
1340
1260
  type: "action:bulk",
1341
- action: actionName,
1261
+ action: resolvedAction.name,
1342
1262
  tableId: table.value.id,
1343
1263
  all: bulk.selection.value.all,
1344
1264
  only: [...bulk.selection.value.only],
@@ -1346,7 +1266,7 @@ function useTable(props, key, defaultOptions = {}) {
1346
1266
  [refinements.filtersKey.value]: filterParameters
1347
1267
  },
1348
1268
  hooks: { after: () => {
1349
- if (options?.deselect === true || table.value.bulkActions.find(({ name }) => name === actionName)?.deselect !== false) bulk.deselectAll();
1269
+ if (options?.deselect === true || resolvedAction.deselect !== false) bulk.deselectAll();
1350
1270
  } }
1351
1271
  });
1352
1272
  }
@@ -1358,17 +1278,19 @@ function useTable(props, key, defaultOptions = {}) {
1358
1278
  isPageSelected: computed(() => table.value.records.length > 0 && table.value.records.every((record) => bulk.selected(getRecordKey(record)))),
1359
1279
  isSelected: (record) => bulk.selected(getRecordKey(record)),
1360
1280
  allSelected: bulk.allSelected,
1281
+ anySelected: bulk.anySelected,
1361
1282
  selection: bulk.selection,
1362
- bindCheckbox: (key$1) => bulk.bindCheckbox(key$1),
1363
- toggle: (record) => bulk.toggle(getRecordKey(record)),
1283
+ bindCheckbox: (key) => bulk.bindCheckbox(key),
1284
+ toggle: (record, force) => bulk.toggle(getRecordKey(record), force),
1285
+ toggleAll: (force) => bulk.toggleAll(force),
1364
1286
  select: (record) => bulk.select(getRecordKey(record)),
1365
1287
  deselect: (record) => bulk.deselect(getRecordKey(record)),
1366
1288
  inlineActions: computed(() => table.value.inlineActions.map((action) => ({
1367
- execute: (record) => executeInlineAction(action.name, record),
1289
+ execute: (record) => executeInlineAction(action, { record }),
1368
1290
  ...action
1369
1291
  }))),
1370
1292
  bulkActions: computed(() => table.value.bulkActions.map((action) => ({
1371
- execute: (options) => executeBulkAction(action.name, options),
1293
+ execute: (options) => executeBulkAction(action, options),
1372
1294
  ...action
1373
1295
  }))),
1374
1296
  executeInlineAction,
@@ -1379,31 +1301,36 @@ function useTable(props, key, defaultOptions = {}) {
1379
1301
  isSorting: (direction) => refinements.isSorting(column.name, direction),
1380
1302
  applyFilter: (value, options) => refinements.applyFilter(column.name, value, options),
1381
1303
  clearFilter: (options) => refinements.clearFilter(column.name, options),
1382
- isSortable: !!refinements.sorts.find((sort) => sort.name === column.name),
1383
- isFilterable: !!refinements.filters.find((filters) => filters.name === column.name)
1304
+ isSortable: !!refinements.sorts.value.find((sort) => sort.name === column.name),
1305
+ isFilterable: !!refinements.filters.value.find((filters) => filters.name === column.name)
1384
1306
  }))),
1385
- data: computed(() => table.value.records.map((record) => {
1386
- return Object.fromEntries(Object.entries(record).map(([key$1, value]) => [key$1, value.value]));
1307
+ data: computed(() => {
1308
+ return table.value.records.map((record) => {
1309
+ const entries = Object.entries(record).map(([key, value]) => [key, value.value]).filter(([key]) => key !== "__hybridId");
1310
+ if (entries.length === 0) return;
1311
+ return Object.fromEntries(entries);
1312
+ }).filter(Boolean);
1313
+ }),
1314
+ records: computed(() => table.value.records.map((record) => {
1315
+ const entries = Object.entries(record).map(([key, value]) => [key, value.value]).filter(([key]) => key !== "__hybridId");
1316
+ return {
1317
+ record: entries.length > 0 ? Object.fromEntries(entries) : {},
1318
+ key: getRecordKey(record),
1319
+ execute: (action) => executeInlineAction(action, { record: getRecordKey(record) }),
1320
+ actions: table.value.inlineActions.map((action) => ({
1321
+ ...action,
1322
+ execute: () => executeInlineAction(action.name, { record: getRecordKey(record) })
1323
+ })),
1324
+ select: () => bulk.select(getRecordKey(record)),
1325
+ deselect: () => bulk.deselect(getRecordKey(record)),
1326
+ toggle: (force) => bulk.toggle(getRecordKey(record), force),
1327
+ selected: bulk.selected(getRecordKey(record)),
1328
+ value: (column) => record[typeof column === "string" ? column : column.name].value,
1329
+ extra: (column, path) => getByPath(record[typeof column === "string" ? column : column.name].extra, path)
1330
+ };
1387
1331
  })),
1388
- records: computed(() => table.value.records.map((record) => ({
1389
- record: Object.values(record).map((record$1) => record$1.value),
1390
- key: getRecordKey(record),
1391
- execute: (action) => executeInlineAction(getActionName(action), getRecordKey(record)),
1392
- actions: table.value.inlineActions.map((action) => ({
1393
- ...action,
1394
- execute: () => executeInlineAction(action.name, getRecordKey(record))
1395
- })),
1396
- select: () => bulk.select(getRecordKey(record)),
1397
- deselect: () => bulk.deselect(getRecordKey(record)),
1398
- toggle: (force) => bulk.toggle(getRecordKey(record), force),
1399
- selected: bulk.selected(getRecordKey(record)),
1400
- value: (column) => record[typeof column === "string" ? column : column.name].value,
1401
- extra: (column, path) => getByPath(record[typeof column === "string" ? column : column.name].extra, path)
1402
- }))),
1403
- paginator: computed(() => table.value.paginator),
1332
+ paginator: computed(() => createPaginator(table.value.paginator, defaultOptions)),
1404
1333
  ...refinements
1405
1334
  });
1406
1335
  }
1407
-
1408
- //#endregion
1409
- export { RouterLink, can, initializeHybridly, registerHook, route, router, setProperty, useBackForward, useBulkSelect, useDialog, useForm, useHistoryState, useProperties, useProperty, useQueryParameter, useQueryParameters, useRefinements, useRoute, useTable };
1336
+ export { RouterLink, can, createPaginator, initializeHybridly, isBooleanFilter, isCallbackFilter, isDateFilter, isSelectFilter, isTernaryFilter, isTextFilter, isTrashedFilter, registerHook, route, router, setProperty, useBackForward, useBulkSelect, useDialog, useForm, useHistoryState, useProperties, useProperty, useQueryParameter, useQueryParameters, useRefinements, useRoute, useTable };