@hybridly/vue 0.0.1-alpha.2 → 0.0.1-alpha.20

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.cjs CHANGED
@@ -9,36 +9,37 @@ const progressPlugin = require('@hybridly/progress-plugin');
9
9
  const devtoolsApi = require('@vue/devtools-api');
10
10
  const qs = require('qs');
11
11
  const isEqual = require('lodash.isequal');
12
- const clone = require('lodash.clonedeep');
13
12
 
14
13
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e["default"] : e; }
15
14
 
16
15
  const qs__default = /*#__PURE__*/_interopDefaultLegacy(qs);
17
16
  const isEqual__default = /*#__PURE__*/_interopDefaultLegacy(isEqual);
18
- const clone__default = /*#__PURE__*/_interopDefaultLegacy(clone);
19
17
 
20
18
  const state = {
21
19
  context: vue.ref(),
22
20
  view: vue.shallowRef(),
21
+ properties: vue.ref(),
23
22
  viewLayout: vue.shallowRef(),
23
+ viewLayoutProperties: vue.ref(),
24
24
  viewKey: vue.ref(),
25
25
  dialog: vue.shallowRef(),
26
26
  dialogKey: vue.ref(),
27
- routes: vue.ref(),
28
- setRoutes(routes) {
29
- utils.debug.adapter("vue:state:routes", "Setting routes:", routes);
30
- if (routes) {
31
- state.routes.value = vue.unref(routes);
32
- }
33
- },
34
27
  setView(view) {
35
28
  utils.debug.adapter("vue:state:view", "Setting view:", view);
36
29
  state.view.value = view;
37
30
  },
31
+ setProperties(properties) {
32
+ utils.debug.adapter("vue:state:view", "Setting properties:", properties);
33
+ state.properties.value = properties;
34
+ },
38
35
  setViewLayout(layout) {
39
- utils.debug.adapter("vue:state:view", "Setting layout:", layout);
36
+ utils.debug.adapter("vue:state:view", "Setting layout", layout);
40
37
  state.viewLayout.value = layout;
41
38
  },
39
+ setViewLayoutProperties(properties) {
40
+ utils.debug.adapter("vue:state:view", "Setting layout properties:", properties);
41
+ state.viewLayoutProperties.value = properties;
42
+ },
42
43
  setDialog(dialog) {
43
44
  utils.debug.adapter("vue:state:dialog", "Setting dialog:", dialog);
44
45
  state.dialog.value = dialog;
@@ -60,13 +61,7 @@ const state = {
60
61
 
61
62
  const wrapper = vue.defineComponent({
62
63
  name: "Hybridly",
63
- setup(props) {
64
- if (typeof window !== "undefined") {
65
- state.setContext(props.context);
66
- if (!props.context) {
67
- throw new Error("Hybridly was not properly initialized. The context is missing.");
68
- }
69
- }
64
+ setup() {
70
65
  function renderLayout(child) {
71
66
  utils.debug.adapter("vue:render:layout", "Rendering layout.");
72
67
  if (typeof state.view.value?.layout === "function") {
@@ -75,11 +70,17 @@ const wrapper = vue.defineComponent({
75
70
  if (Array.isArray(state.view.value?.layout)) {
76
71
  return state.view.value.layout.concat(child).reverse().reduce((child2, layout) => {
77
72
  layout.inheritAttrs = !!layout.inheritAttrs;
78
- return vue.h(layout, { ...state.context.value.view.properties }, () => child2);
73
+ return vue.h(layout, {
74
+ ...state.view.value?.layoutProperties ?? {},
75
+ ...state.properties.value
76
+ }, () => child2);
79
77
  });
80
78
  }
81
79
  return [
82
- vue.h(state.view.value?.layout, { ...state.context.value.view.properties }, () => child),
80
+ vue.h(state.view.value?.layout, {
81
+ ...state.view.value?.layoutProperties ?? {},
82
+ ...state.properties.value
83
+ }, () => child),
83
84
  renderDialog()
84
85
  ];
85
86
  }
@@ -87,7 +88,7 @@ const wrapper = vue.defineComponent({
87
88
  utils.debug.adapter("vue:render:view", "Rendering view.");
88
89
  state.view.value.inheritAttrs = !!state.view.value.inheritAttrs;
89
90
  return vue.h(state.view.value, {
90
- ...state.context.value.view.properties,
91
+ ...state.properties.value,
91
92
  key: state.viewKey.value
92
93
  });
93
94
  }
@@ -100,25 +101,24 @@ const wrapper = vue.defineComponent({
100
101
  });
101
102
  }
102
103
  }
103
- return () => {
104
- if (state.view.value) {
105
- const view = renderView();
106
- if (state.viewLayout.value) {
107
- state.view.value.layout = state.viewLayout.value;
108
- state.viewLayout.value = void 0;
109
- }
110
- if (state.view.value.layout) {
111
- return renderLayout(view);
112
- }
113
- return [view, renderDialog()];
104
+ return (...a) => {
105
+ if (!state.view.value) {
106
+ return;
107
+ }
108
+ utils.debug.adapter("vue:render:wrapper", "Rendering wrapper component.", a.map(vue.toRaw));
109
+ const view = renderView();
110
+ if (state.viewLayout.value) {
111
+ state.view.value.layout = state.viewLayout.value;
112
+ }
113
+ if (state.viewLayoutProperties.value) {
114
+ state.view.value.layoutProperties = state.viewLayoutProperties.value;
115
+ state.viewLayoutProperties.value = void 0;
116
+ }
117
+ if (state.view.value.layout) {
118
+ return renderLayout(view);
114
119
  }
120
+ return [view, renderDialog()];
115
121
  };
116
- },
117
- props: {
118
- context: {
119
- type: Object,
120
- required: true
121
- }
122
122
  }
123
123
  });
124
124
 
@@ -160,8 +160,8 @@ function setupDevtools(app) {
160
160
  });
161
161
  payload.instanceData.state.push({
162
162
  type: hybridlyStateType,
163
- key: "router",
164
- value: state.routes.value
163
+ key: "routing",
164
+ value: state.context.value?.routing
165
165
  });
166
166
  });
167
167
  api.on.editComponentState((payload) => {
@@ -177,7 +177,7 @@ function setupDevtools(app) {
177
177
  const listen = [
178
178
  "start",
179
179
  "data",
180
- "navigate",
180
+ "navigated",
181
181
  "progress",
182
182
  "error",
183
183
  "abort",
@@ -198,7 +198,7 @@ function setupDevtools(app) {
198
198
  data: options
199
199
  }
200
200
  });
201
- listen.forEach((event) => core.registerHookOnce(event, (data) => {
201
+ listen.forEach((event) => core.registerHook(event, (data) => {
202
202
  api.addTimelineEvent({
203
203
  layerId: hybridlyEventsTimelineLayerId,
204
204
  event: {
@@ -214,11 +214,11 @@ function setupDevtools(app) {
214
214
  api.notifyComponentUpdate();
215
215
  }, 100);
216
216
  }
217
- }));
217
+ }, { once: true }));
218
218
  });
219
219
  });
220
220
  }
221
- const plugin = {
221
+ const devtools = {
222
222
  install(app) {
223
223
  if (process.env.NODE_ENV === "development" || __VUE_PROD_DEVTOOLS__) {
224
224
  setupDevtools(app);
@@ -235,6 +235,7 @@ async function initializeHybridly(options) {
235
235
  throw new Error("No payload. Are you using `@hybridly` or the `payload` option?");
236
236
  }
237
237
  state.setContext(await core.createRouter({
238
+ axios: options.axios,
238
239
  plugins: options.plugins,
239
240
  serializer: options.serializer,
240
241
  adapter: {
@@ -243,6 +244,7 @@ async function initializeHybridly(options) {
243
244
  },
244
245
  swapView: async (options2) => {
245
246
  state.setView(options2.component);
247
+ state.setProperties(options2.properties);
246
248
  if (!options2.preserveState) {
247
249
  state.setViewKey(Date.now());
248
250
  }
@@ -253,13 +255,28 @@ async function initializeHybridly(options) {
253
255
  },
254
256
  payload
255
257
  }));
256
- await options.setup({
257
- element,
258
- wrapper,
259
- hybridly: plugin,
260
- props: { context: state.context.value },
261
- render: () => vue.h(wrapper, { context: state.context.value })
262
- });
258
+ if (typeof window !== "undefined") {
259
+ window.addEventListener("hybridly:routing", (event) => {
260
+ state.context.value?.adapter.updateRoutingConfiguration(event.detail);
261
+ });
262
+ window.dispatchEvent(new CustomEvent("hybridly:routing", { detail: window?.hybridly?.routing }));
263
+ }
264
+ const render = () => vue.h(wrapper);
265
+ if (options.setup) {
266
+ return await options.setup({
267
+ element,
268
+ wrapper,
269
+ render,
270
+ hybridly: devtools,
271
+ props: { context: state.context.value }
272
+ });
273
+ }
274
+ const app = vue.createApp({ render });
275
+ if (options.devtools !== false) {
276
+ app.use(devtools);
277
+ }
278
+ await options.enhanceVue?.(app);
279
+ return app.mount(element);
263
280
  }
264
281
  function prepare(options) {
265
282
  utils.debug.adapter("vue", "Preparing Hybridly with options:", options);
@@ -283,15 +300,6 @@ function prepare(options) {
283
300
  }
284
301
  throw new Error("Either `initializeHybridly#resolve` or `initializeHybridly#pages` should be defined.");
285
302
  };
286
- if (typeof window !== "undefined") {
287
- const routes = window.hybridly?.routes;
288
- if (routes) {
289
- state.setRoutes(window.hybridly?.routes);
290
- window.addEventListener("hybridly:routes", (event) => {
291
- state.setRoutes(event.detail);
292
- });
293
- }
294
- }
295
303
  if (options.progress !== false) {
296
304
  options.plugins = [
297
305
  progressPlugin.progress(typeof options.progress === "object" ? options.progress : {}),
@@ -306,6 +314,10 @@ function prepare(options) {
306
314
  };
307
315
  }
308
316
  async function resolvePageComponent(name, pages, defaultLayout) {
317
+ if (name.includes(":")) {
318
+ const [domain, page] = name.split(":");
319
+ name = `domains.${domain}.pages.${page}`;
320
+ }
309
321
  const path = Object.keys(pages).sort((a, b) => a.length - b.length).find((path2) => path2.endsWith(`${name.replaceAll(".", "/")}.vue`));
310
322
  if (!path) {
311
323
  utils.showPageComponentErrorModal(name);
@@ -324,7 +336,7 @@ const RouterLink = vue.defineComponent({
324
336
  return (props) => {
325
337
  let data = props.data ?? {};
326
338
  const url = core.makeUrl(props.href ?? "");
327
- const method = props.method ?? "GET";
339
+ const method = props.method?.toUpperCase() ?? "GET";
328
340
  const as = typeof props.as === "object" ? props.as : props.as?.toLowerCase() ?? "a";
329
341
  if (method === "GET") {
330
342
  utils.debug.adapter("vue", "Moving data object to URL parameters.");
@@ -356,7 +368,7 @@ Please specify a more appropriate element using the "as" attribute. For example:
356
368
  if (props.disabled) {
357
369
  return;
358
370
  }
359
- core.router.visit({
371
+ core.router.navigate({
360
372
  url,
361
373
  data,
362
374
  method,
@@ -364,13 +376,14 @@ Please specify a more appropriate element using the "as" attribute. For example:
364
376
  ...props.options
365
377
  });
366
378
  }
367
- }, slots);
379
+ }, slots.default ? slots : props.text);
368
380
  };
369
381
  },
370
382
  props: {
371
383
  href: {
372
384
  type: String,
373
- required: true
385
+ required: false,
386
+ default: void 0
374
387
  },
375
388
  as: {
376
389
  type: [String, Object],
@@ -395,6 +408,11 @@ Please specify a more appropriate element using the "as" attribute. For example:
395
408
  options: {
396
409
  type: Object,
397
410
  default: () => ({})
411
+ },
412
+ text: {
413
+ type: String,
414
+ required: false,
415
+ default: void 0
398
416
  }
399
417
  }
400
418
  });
@@ -406,6 +424,7 @@ function shouldIntercept(event) {
406
424
  const HybridlyImports = {
407
425
  "hybridly/vue": [
408
426
  "useProperty",
427
+ "useTypedProperty",
409
428
  "useProperties",
410
429
  "useRouter",
411
430
  "useBackForward",
@@ -413,13 +432,14 @@ const HybridlyImports = {
413
432
  "useForm",
414
433
  "useHistoryState",
415
434
  "usePaginator",
416
- "useLayout",
417
- "route"
435
+ "defineLayout",
436
+ "defineLayoutProperties",
437
+ "registerHook"
418
438
  ],
419
439
  "hybridly": [
420
- "registerHook",
421
- "registerHookOnce",
422
440
  "router",
441
+ "route",
442
+ "current",
423
443
  "can"
424
444
  ]
425
445
  };
@@ -481,31 +501,34 @@ function toReactive(objectRef) {
481
501
  function useProperties() {
482
502
  return vue.readonly(toReactive(vue.computed(() => state.context.value?.view.properties)));
483
503
  }
504
+ function useTypedProperty(path, fallback) {
505
+ return vue.computed(
506
+ () => path.split(".").reduce((o, i) => o[i], state.context.value?.view.properties) ?? fallback
507
+ );
508
+ }
484
509
  function useProperty(path, fallback) {
485
- return vue.computed(() => path.split(".").reduce((o, i) => o[i], state.context.value?.view.properties) ?? fallback);
510
+ return vue.computed(
511
+ () => path.split(".").reduce((o, i) => o[i], state.context.value?.view.properties) ?? fallback
512
+ );
486
513
  }
487
514
 
488
515
  function useContext() {
489
516
  return vue.computed(() => state.context.value);
490
517
  }
491
518
 
492
- function useRouter() {
493
- return core.router;
494
- }
495
-
496
519
  function safeClone(obj) {
497
- return clone__default(vue.toRaw(obj));
520
+ return utils.clone(vue.toRaw(obj));
498
521
  }
499
522
  function useForm(options) {
500
- const shouldRemember = options?.key !== false;
523
+ const shouldRemember = !!options?.key;
501
524
  const historyKey = options?.key ?? "form:default";
502
525
  const historyData = shouldRemember ? core.router.history.get(historyKey) : void 0;
503
526
  const timeoutIds = {
504
527
  recentlyFailed: void 0,
505
528
  recentlySuccessful: void 0
506
529
  };
507
- const initial = vue.readonly(safeClone(options.fields));
508
- const loaded = vue.readonly(safeClone(historyData?.fields ?? options.fields));
530
+ const initial = safeClone(options.fields);
531
+ const loaded = safeClone(historyData?.fields ?? options.fields);
509
532
  const fields = vue.reactive(safeClone(historyData?.fields ?? options.fields));
510
533
  const errors = vue.ref(historyData?.errors ?? {});
511
534
  const isDirty = vue.ref(false);
@@ -514,61 +537,85 @@ function useForm(options) {
514
537
  const recentlyFailed = vue.ref(false);
515
538
  const failed = vue.ref(false);
516
539
  const processing = vue.ref(false);
540
+ const progress = vue.ref();
517
541
  function reset(...keys) {
518
542
  if (keys.length === 0) {
519
543
  keys = Object.keys(fields);
520
544
  }
521
- keys.forEach((key) => Reflect.set(fields, key, safeClone(Reflect.get(initial, key))));
522
- clearErrors();
545
+ keys.forEach((key) => {
546
+ Reflect.set(fields, key, safeClone(Reflect.get(initial, key)));
547
+ clearError(key);
548
+ });
523
549
  }
524
550
  function submit(optionsOverrides) {
525
551
  const url = typeof options.url === "function" ? options.url() : options.url;
526
552
  const data = typeof options.transform === "function" ? options.transform?.(fields) : fields;
527
- return core.router.visit({
553
+ const preserveState = optionsOverrides?.preserveState ?? options.preserveState;
554
+ return core.router.navigate({
555
+ ...options,
528
556
  url: url ?? state.context.value?.url,
529
557
  method: options.method ?? "POST",
530
558
  ...optionsOverrides,
531
559
  data: safeClone(data),
532
- preserveState: optionsOverrides?.preserveState === void 0 && options.method !== "GET" ? true : optionsOverrides?.preserveState,
560
+ preserveState: preserveState === void 0 && options.method !== "GET" ? true : preserveState,
533
561
  hooks: {
534
- before: (visit) => {
562
+ before: (navigation, context) => {
535
563
  failed.value = false;
536
564
  successful.value = false;
537
565
  recentlySuccessful.value = false;
538
566
  clearTimeout(timeoutIds.recentlySuccessful);
539
567
  clearTimeout(timeoutIds.recentlyFailed);
540
- clearErrors();
541
- return options.hooks?.before?.(visit);
568
+ return options.hooks?.before?.(navigation, context);
542
569
  },
543
570
  start: (context) => {
544
571
  processing.value = true;
545
572
  return options.hooks?.start?.(context);
546
573
  },
547
- error: (incoming) => {
574
+ progress: (incoming, context) => {
575
+ progress.value = incoming;
576
+ return options.hooks?.progress?.(incoming, context);
577
+ },
578
+ error: (incoming, context) => {
548
579
  setErrors(incoming);
549
580
  failed.value = true;
550
581
  recentlyFailed.value = true;
551
582
  timeoutIds.recentlyFailed = setTimeout(() => recentlyFailed.value = false, options?.timeout ?? 5e3);
552
- return options.hooks?.error?.(incoming);
583
+ return options.hooks?.error?.(incoming, context);
553
584
  },
554
- success: (payload) => {
585
+ success: (payload, context) => {
586
+ clearErrors();
555
587
  if (options?.reset !== false) {
556
588
  reset();
557
589
  }
558
590
  successful.value = true;
559
591
  recentlySuccessful.value = true;
560
592
  timeoutIds.recentlySuccessful = setTimeout(() => recentlySuccessful.value = false, options?.timeout ?? 5e3);
561
- return options.hooks?.success?.(payload);
593
+ return options.hooks?.success?.(payload, context);
562
594
  },
563
595
  after: (context) => {
596
+ progress.value = void 0;
564
597
  processing.value = false;
565
598
  return options.hooks?.after?.(context);
566
599
  }
567
600
  }
568
601
  });
569
602
  }
570
- function clearErrors() {
571
- errors.value = {};
603
+ function clearErrors(...keys) {
604
+ if (keys.length === 0) {
605
+ keys = Object.keys(fields);
606
+ }
607
+ keys.forEach((key) => {
608
+ clearError(key);
609
+ });
610
+ }
611
+ function hasDirty(...keys) {
612
+ if (keys.length === 0) {
613
+ return isDirty.value;
614
+ }
615
+ return keys.some((key) => !isEqual__default(vue.toRaw(fields[key]), vue.toRaw(initial[key])));
616
+ }
617
+ function clearError(key) {
618
+ errors.value[key] = void 0;
572
619
  }
573
620
  function setErrors(incoming) {
574
621
  errors.value = incoming;
@@ -587,21 +634,25 @@ function useForm(options) {
587
634
  }, { deep: true, immediate: true });
588
635
  return vue.reactive({
589
636
  reset,
590
- initial,
591
637
  fields,
592
- loaded,
593
- submit,
594
638
  abort,
595
639
  setErrors,
596
640
  clearErrors,
641
+ clearError,
642
+ hasDirty,
643
+ submitWithOptions: submit,
644
+ submit: () => submit(),
597
645
  hasErrors: vue.computed(() => Object.values(errors.value).length > 0),
598
- isDirty: vue.readonly(isDirty),
599
- errors: vue.readonly(errors),
600
- processing: vue.readonly(processing),
601
- successful: vue.readonly(successful),
602
- failed: vue.readonly(failed),
603
- recentlySuccessful: vue.readonly(recentlySuccessful),
604
- recentlyFailed: vue.readonly(recentlyFailed)
646
+ initial,
647
+ loaded,
648
+ progress,
649
+ isDirty,
650
+ errors,
651
+ processing,
652
+ successful,
653
+ failed,
654
+ recentlySuccessful,
655
+ recentlyFailed
605
656
  });
606
657
  }
607
658
 
@@ -615,7 +666,7 @@ function useHistoryState(key, initial) {
615
666
 
616
667
  function useBackForward() {
617
668
  const callbacks = [];
618
- core.registerHook("navigate", (options) => {
669
+ core.registerHook("navigated", (options) => {
619
670
  if (options.isBackForward) {
620
671
  callbacks.forEach((fn) => fn(state.context.value));
621
672
  callbacks.splice(0, callbacks.length);
@@ -660,146 +711,40 @@ function usePaginator(paginator) {
660
711
  return { pages, items, previous, next, first, last, total, from, to };
661
712
  }
662
713
 
663
- function useLayout(layout) {
664
- state.setViewLayout(layout);
714
+ function defineLayout(...args) {
715
+ const layouts = args[0];
716
+ const properties = args[1];
717
+ state.setViewLayout(layouts);
718
+ state.setViewLayoutProperties(properties);
665
719
  }
666
-
667
- class Route {
668
- constructor(name, absolute) {
669
- this.name = name;
670
- this.absolute = absolute;
671
- this.definition = Route.getDefinition(name);
672
- }
673
- static getDefinition(name) {
674
- if (!state.routes.value) {
675
- throw new Error("Routing is not initialized. Have you enabled the Vite plugin?");
676
- }
677
- const routes = state.routes.value;
678
- const route = routes?.routes?.[name];
679
- if (!route) {
680
- throw new Error(`Route ${name.toString()} does not exist.`);
681
- }
682
- return route;
683
- }
684
- get template() {
685
- const origin = !this.absolute ? "" : this.definition.domain ? `${state.routes.value?.url.match(/^\w+:\/\//)?.[0]}${this.definition.domain}${state.routes.value?.port ? `:${state.routes.value?.port}` : ""}` : state.routes.value?.url;
686
- return `${origin}/${this.definition.uri}`.replace(/\/+$/, "");
687
- }
688
- get parameterSegments() {
689
- return this.template.match(/{[^}?]+\??}/g)?.map((segment) => ({
690
- name: segment.replace(/{|\??}/g, ""),
691
- required: !/\?}$/.test(segment)
692
- })) ?? [];
693
- }
694
- matchesUrl(url) {
695
- if (!this.definition.methods.includes("GET")) {
696
- return false;
697
- }
698
- const pattern = this.template.replace(/(\/?){([^}?]*)(\??)}/g, (_, slash, segment, optional) => {
699
- const regex = `(?<${segment}>${this.definition.wheres?.[segment]?.replace(/(^\^)|(\$$)/g, "") || "[^/?]+"})`;
700
- return optional ? `(${slash}${regex})?` : `${slash}${regex}`;
701
- }).replace(/^\w+:\/\//, "");
702
- const [location, query] = url.replace(/^\w+:\/\//, "").split("?");
703
- const matches = new RegExp(`^${pattern}/?$`).exec(location);
704
- return matches ? { params: matches.groups, query: qs.parse(query) } : false;
705
- }
706
- compile(params) {
707
- const segments = this.parameterSegments;
708
- if (!segments.length) {
709
- return this.template;
710
- }
711
- return this.template.replace(/{([^}?]+)(\??)}/g, (_, segment, optional) => {
712
- if (!optional && [null, void 0].includes(params?.[segment])) {
713
- throw new Error(`Router error: [${segment}] parameter is required for route [${this.name}].`);
714
- }
715
- if (segments[segments.length - 1].name === segment && this.definition?.wheres?.[segment] === ".*") {
716
- return encodeURIComponent(params[segment] ?? "").replace(/%2F/g, "/");
717
- }
718
- if (this.definition?.wheres?.[segment] && !new RegExp(`^${optional ? `(${this.definition?.wheres?.[segment]})?` : this.definition?.wheres?.[segment]}$`).test(params[segment] ?? "")) {
719
- throw new Error(`Router error: [${segment}] parameter does not match required format [${this.definition?.wheres?.[segment]}] for route [${this.name}].`);
720
- }
721
- return encodeURIComponent(params[segment] ?? "");
722
- }).replace(/\/+$/, "");
723
- }
720
+ function defineLayoutProperties(properties) {
721
+ state.setViewLayoutProperties(properties);
724
722
  }
725
723
 
726
- class Router extends String {
727
- constructor(name, parameters, absolute = true) {
728
- super();
729
- this.route = new Route(name, absolute);
730
- this.setParameters(parameters);
731
- }
732
- toString() {
733
- const unhandled = Object.keys(this.parameters).filter((key) => !this.route.parameterSegments.some(({ name }) => name === key)).filter((key) => key !== "_query").reduce((result, current) => ({ ...result, [current]: this.parameters[current] }), {});
734
- return this.route.compile(this.parameters) + qs.stringify({ ...unhandled, ...this.parameters._query }, {
735
- addQueryPrefix: true,
736
- arrayFormat: "indices",
737
- encodeValuesOnly: true,
738
- skipNulls: true,
739
- encoder: (value, encoder) => typeof value === "boolean" ? Number(value).toString() : encoder(value)
740
- });
741
- }
742
- static has(name) {
743
- try {
744
- Route.getDefinition(name);
745
- return true;
746
- } catch {
747
- return false;
748
- }
749
- }
750
- setParameters(parameters) {
751
- this.parameters = parameters ?? {};
752
- this.parameters = ["string", "number"].includes(typeof this.parameters) ? [this.parameters] : this.parameters;
753
- const segments = this.route.parameterSegments.filter(({ name }) => !state.routes.value?.defaults[name]);
754
- if (Array.isArray(this.parameters)) {
755
- this.parameters = this.parameters.reduce((result, current, i) => segments[i] ? { ...result, [segments[i].name]: current } : typeof current === "object" ? { ...result, ...current } : { ...result, [current]: "" }, {});
756
- } else if (segments.length === 1 && !this.parameters[segments[0].name] && (Reflect.has(this.parameters, Object.values(this.route.definition.bindings)[0]) || Reflect.has(this.parameters, "id"))) {
757
- this.parameters = { [segments[0].name]: this.parameters };
758
- }
759
- this.parameters = {
760
- ...this.getDefaults(),
761
- ...this.substituteBindings()
762
- };
724
+ const registerHook = (hook, fn, options) => {
725
+ const unregister = core.registerHook(hook, fn, options);
726
+ if (vue.getCurrentInstance()) {
727
+ vue.onUnmounted(() => unregister());
763
728
  }
764
- getDefaults() {
765
- return this.route.parameterSegments.filter(({ name }) => state.routes.value?.defaults[name]).reduce((result, { name }) => ({ ...result, [name]: state.routes.value?.defaults[name] }), {});
766
- }
767
- substituteBindings() {
768
- return Object.entries(this.parameters).reduce((result, [key, value]) => {
769
- if (!value || typeof value !== "object" || Array.isArray(value) || !this.route.parameterSegments.some(({ name }) => name === key)) {
770
- return { ...result, [key]: value };
771
- }
772
- if (!Reflect.has(value, this.route.definition.bindings[key])) {
773
- if (Reflect.has(value, "id")) {
774
- this.route.definition.bindings[key] = "id";
775
- } else {
776
- throw new Error(`Router error: object passed as [${key}] parameter is missing route model binding key [${this.route.definition.bindings?.[key]}].`);
777
- }
778
- }
779
- return { ...result, [key]: value[this.route.definition.bindings[key]] };
780
- }, {});
781
- }
782
- valueOf() {
783
- return this.toString();
784
- }
785
- }
786
-
787
- function route(name, parameters, absolute) {
788
- return new Router(name, parameters, absolute).toString();
789
- }
729
+ return unregister;
730
+ };
790
731
 
732
+ exports.can = core.can;
733
+ exports.route = core.route;
791
734
  exports.router = core.router;
792
735
  exports.HybridlyImports = HybridlyImports;
793
736
  exports.HybridlyResolver = HybridlyResolver;
794
737
  exports.RouterLink = RouterLink;
738
+ exports.defineLayout = defineLayout;
739
+ exports.defineLayoutProperties = defineLayoutProperties;
795
740
  exports.initializeHybridly = initializeHybridly;
796
- exports.route = route;
741
+ exports.registerHook = registerHook;
742
+ exports.resolvePageComponent = resolvePageComponent;
797
743
  exports.useBackForward = useBackForward;
798
744
  exports.useContext = useContext;
799
745
  exports.useForm = useForm;
800
746
  exports.useHistoryState = useHistoryState;
801
- exports.useLayout = useLayout;
802
747
  exports.usePaginator = usePaginator;
803
748
  exports.useProperties = useProperties;
804
749
  exports.useProperty = useProperty;
805
- exports.useRouter = useRouter;
750
+ exports.useTypedProperty = useTypedProperty;