@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.mjs CHANGED
@@ -1,35 +1,37 @@
1
- import { ref, shallowRef, unref, triggerRef, defineComponent, h, isRef, reactive, readonly, computed, toRaw, watch } from 'vue';
2
- import { registerHook, registerHookOnce, createRouter, makeUrl, router } from '@hybridly/core';
3
- export { router } from '@hybridly/core';
4
- import { debug, showPageComponentErrorModal, merge } from '@hybridly/utils';
1
+ import { ref, shallowRef, unref, triggerRef, defineComponent, toRaw, h, createApp, isRef, reactive, readonly, computed, watch, getCurrentInstance, onUnmounted } from 'vue';
2
+ import { registerHook as registerHook$1, createRouter, makeUrl, router } from '@hybridly/core';
3
+ export { can, route, router } from '@hybridly/core';
4
+ import { debug, showPageComponentErrorModal, merge, clone } from '@hybridly/utils';
5
5
  import { progress } from '@hybridly/progress-plugin';
6
6
  import { setupDevtoolsPlugin } from '@vue/devtools-api';
7
- import qs, { parse, stringify } from 'qs';
7
+ import qs from 'qs';
8
8
  import isEqual from 'lodash.isequal';
9
- import clone from 'lodash.clonedeep';
10
9
 
11
10
  const state = {
12
11
  context: ref(),
13
12
  view: shallowRef(),
13
+ properties: ref(),
14
14
  viewLayout: shallowRef(),
15
+ viewLayoutProperties: ref(),
15
16
  viewKey: ref(),
16
17
  dialog: shallowRef(),
17
18
  dialogKey: ref(),
18
- routes: ref(),
19
- setRoutes(routes) {
20
- debug.adapter("vue:state:routes", "Setting routes:", routes);
21
- if (routes) {
22
- state.routes.value = unref(routes);
23
- }
24
- },
25
19
  setView(view) {
26
20
  debug.adapter("vue:state:view", "Setting view:", view);
27
21
  state.view.value = view;
28
22
  },
23
+ setProperties(properties) {
24
+ debug.adapter("vue:state:view", "Setting properties:", properties);
25
+ state.properties.value = properties;
26
+ },
29
27
  setViewLayout(layout) {
30
- debug.adapter("vue:state:view", "Setting layout:", layout);
28
+ debug.adapter("vue:state:view", "Setting layout", layout);
31
29
  state.viewLayout.value = layout;
32
30
  },
31
+ setViewLayoutProperties(properties) {
32
+ debug.adapter("vue:state:view", "Setting layout properties:", properties);
33
+ state.viewLayoutProperties.value = properties;
34
+ },
33
35
  setDialog(dialog) {
34
36
  debug.adapter("vue:state:dialog", "Setting dialog:", dialog);
35
37
  state.dialog.value = dialog;
@@ -51,13 +53,7 @@ const state = {
51
53
 
52
54
  const wrapper = defineComponent({
53
55
  name: "Hybridly",
54
- setup(props) {
55
- if (typeof window !== "undefined") {
56
- state.setContext(props.context);
57
- if (!props.context) {
58
- throw new Error("Hybridly was not properly initialized. The context is missing.");
59
- }
60
- }
56
+ setup() {
61
57
  function renderLayout(child) {
62
58
  debug.adapter("vue:render:layout", "Rendering layout.");
63
59
  if (typeof state.view.value?.layout === "function") {
@@ -66,11 +62,17 @@ const wrapper = defineComponent({
66
62
  if (Array.isArray(state.view.value?.layout)) {
67
63
  return state.view.value.layout.concat(child).reverse().reduce((child2, layout) => {
68
64
  layout.inheritAttrs = !!layout.inheritAttrs;
69
- return h(layout, { ...state.context.value.view.properties }, () => child2);
65
+ return h(layout, {
66
+ ...state.view.value?.layoutProperties ?? {},
67
+ ...state.properties.value
68
+ }, () => child2);
70
69
  });
71
70
  }
72
71
  return [
73
- h(state.view.value?.layout, { ...state.context.value.view.properties }, () => child),
72
+ h(state.view.value?.layout, {
73
+ ...state.view.value?.layoutProperties ?? {},
74
+ ...state.properties.value
75
+ }, () => child),
74
76
  renderDialog()
75
77
  ];
76
78
  }
@@ -78,7 +80,7 @@ const wrapper = defineComponent({
78
80
  debug.adapter("vue:render:view", "Rendering view.");
79
81
  state.view.value.inheritAttrs = !!state.view.value.inheritAttrs;
80
82
  return h(state.view.value, {
81
- ...state.context.value.view.properties,
83
+ ...state.properties.value,
82
84
  key: state.viewKey.value
83
85
  });
84
86
  }
@@ -91,25 +93,24 @@ const wrapper = defineComponent({
91
93
  });
92
94
  }
93
95
  }
94
- return () => {
95
- if (state.view.value) {
96
- const view = renderView();
97
- if (state.viewLayout.value) {
98
- state.view.value.layout = state.viewLayout.value;
99
- state.viewLayout.value = void 0;
100
- }
101
- if (state.view.value.layout) {
102
- return renderLayout(view);
103
- }
104
- return [view, renderDialog()];
96
+ return (...a) => {
97
+ if (!state.view.value) {
98
+ return;
99
+ }
100
+ debug.adapter("vue:render:wrapper", "Rendering wrapper component.", a.map(toRaw));
101
+ const view = renderView();
102
+ if (state.viewLayout.value) {
103
+ state.view.value.layout = state.viewLayout.value;
104
+ }
105
+ if (state.viewLayoutProperties.value) {
106
+ state.view.value.layoutProperties = state.viewLayoutProperties.value;
107
+ state.viewLayoutProperties.value = void 0;
108
+ }
109
+ if (state.view.value.layout) {
110
+ return renderLayout(view);
105
111
  }
112
+ return [view, renderDialog()];
106
113
  };
107
- },
108
- props: {
109
- context: {
110
- type: Object,
111
- required: true
112
- }
113
114
  }
114
115
  });
115
116
 
@@ -151,8 +152,8 @@ function setupDevtools(app) {
151
152
  });
152
153
  payload.instanceData.state.push({
153
154
  type: hybridlyStateType,
154
- key: "router",
155
- value: state.routes.value
155
+ key: "routing",
156
+ value: state.context.value?.routing
156
157
  });
157
158
  });
158
159
  api.on.editComponentState((payload) => {
@@ -168,7 +169,7 @@ function setupDevtools(app) {
168
169
  const listen = [
169
170
  "start",
170
171
  "data",
171
- "navigate",
172
+ "navigated",
172
173
  "progress",
173
174
  "error",
174
175
  "abort",
@@ -178,7 +179,7 @@ function setupDevtools(app) {
178
179
  "fail",
179
180
  "after"
180
181
  ];
181
- registerHook("before", (options) => {
182
+ registerHook$1("before", (options) => {
182
183
  const groupId = (Math.random() + 1).toString(36).substring(7);
183
184
  api.addTimelineEvent({
184
185
  layerId: hybridlyEventsTimelineLayerId,
@@ -189,7 +190,7 @@ function setupDevtools(app) {
189
190
  data: options
190
191
  }
191
192
  });
192
- listen.forEach((event) => registerHookOnce(event, (data) => {
193
+ listen.forEach((event) => registerHook$1(event, (data) => {
193
194
  api.addTimelineEvent({
194
195
  layerId: hybridlyEventsTimelineLayerId,
195
196
  event: {
@@ -205,11 +206,11 @@ function setupDevtools(app) {
205
206
  api.notifyComponentUpdate();
206
207
  }, 100);
207
208
  }
208
- }));
209
+ }, { once: true }));
209
210
  });
210
211
  });
211
212
  }
212
- const plugin = {
213
+ const devtools = {
213
214
  install(app) {
214
215
  if (process.env.NODE_ENV === "development" || __VUE_PROD_DEVTOOLS__) {
215
216
  setupDevtools(app);
@@ -226,6 +227,7 @@ async function initializeHybridly(options) {
226
227
  throw new Error("No payload. Are you using `@hybridly` or the `payload` option?");
227
228
  }
228
229
  state.setContext(await createRouter({
230
+ axios: options.axios,
229
231
  plugins: options.plugins,
230
232
  serializer: options.serializer,
231
233
  adapter: {
@@ -234,6 +236,7 @@ async function initializeHybridly(options) {
234
236
  },
235
237
  swapView: async (options2) => {
236
238
  state.setView(options2.component);
239
+ state.setProperties(options2.properties);
237
240
  if (!options2.preserveState) {
238
241
  state.setViewKey(Date.now());
239
242
  }
@@ -244,13 +247,28 @@ async function initializeHybridly(options) {
244
247
  },
245
248
  payload
246
249
  }));
247
- await options.setup({
248
- element,
249
- wrapper,
250
- hybridly: plugin,
251
- props: { context: state.context.value },
252
- render: () => h(wrapper, { context: state.context.value })
253
- });
250
+ if (typeof window !== "undefined") {
251
+ window.addEventListener("hybridly:routing", (event) => {
252
+ state.context.value?.adapter.updateRoutingConfiguration(event.detail);
253
+ });
254
+ window.dispatchEvent(new CustomEvent("hybridly:routing", { detail: window?.hybridly?.routing }));
255
+ }
256
+ const render = () => h(wrapper);
257
+ if (options.setup) {
258
+ return await options.setup({
259
+ element,
260
+ wrapper,
261
+ render,
262
+ hybridly: devtools,
263
+ props: { context: state.context.value }
264
+ });
265
+ }
266
+ const app = createApp({ render });
267
+ if (options.devtools !== false) {
268
+ app.use(devtools);
269
+ }
270
+ await options.enhanceVue?.(app);
271
+ return app.mount(element);
254
272
  }
255
273
  function prepare(options) {
256
274
  debug.adapter("vue", "Preparing Hybridly with options:", options);
@@ -274,15 +292,6 @@ function prepare(options) {
274
292
  }
275
293
  throw new Error("Either `initializeHybridly#resolve` or `initializeHybridly#pages` should be defined.");
276
294
  };
277
- if (typeof window !== "undefined") {
278
- const routes = window.hybridly?.routes;
279
- if (routes) {
280
- state.setRoutes(window.hybridly?.routes);
281
- window.addEventListener("hybridly:routes", (event) => {
282
- state.setRoutes(event.detail);
283
- });
284
- }
285
- }
286
295
  if (options.progress !== false) {
287
296
  options.plugins = [
288
297
  progress(typeof options.progress === "object" ? options.progress : {}),
@@ -297,6 +306,10 @@ function prepare(options) {
297
306
  };
298
307
  }
299
308
  async function resolvePageComponent(name, pages, defaultLayout) {
309
+ if (name.includes(":")) {
310
+ const [domain, page] = name.split(":");
311
+ name = `domains.${domain}.pages.${page}`;
312
+ }
300
313
  const path = Object.keys(pages).sort((a, b) => a.length - b.length).find((path2) => path2.endsWith(`${name.replaceAll(".", "/")}.vue`));
301
314
  if (!path) {
302
315
  showPageComponentErrorModal(name);
@@ -315,7 +328,7 @@ const RouterLink = defineComponent({
315
328
  return (props) => {
316
329
  let data = props.data ?? {};
317
330
  const url = makeUrl(props.href ?? "");
318
- const method = props.method ?? "GET";
331
+ const method = props.method?.toUpperCase() ?? "GET";
319
332
  const as = typeof props.as === "object" ? props.as : props.as?.toLowerCase() ?? "a";
320
333
  if (method === "GET") {
321
334
  debug.adapter("vue", "Moving data object to URL parameters.");
@@ -347,7 +360,7 @@ Please specify a more appropriate element using the "as" attribute. For example:
347
360
  if (props.disabled) {
348
361
  return;
349
362
  }
350
- router.visit({
363
+ router.navigate({
351
364
  url,
352
365
  data,
353
366
  method,
@@ -355,13 +368,14 @@ Please specify a more appropriate element using the "as" attribute. For example:
355
368
  ...props.options
356
369
  });
357
370
  }
358
- }, slots);
371
+ }, slots.default ? slots : props.text);
359
372
  };
360
373
  },
361
374
  props: {
362
375
  href: {
363
376
  type: String,
364
- required: true
377
+ required: false,
378
+ default: void 0
365
379
  },
366
380
  as: {
367
381
  type: [String, Object],
@@ -386,6 +400,11 @@ Please specify a more appropriate element using the "as" attribute. For example:
386
400
  options: {
387
401
  type: Object,
388
402
  default: () => ({})
403
+ },
404
+ text: {
405
+ type: String,
406
+ required: false,
407
+ default: void 0
389
408
  }
390
409
  }
391
410
  });
@@ -397,6 +416,7 @@ function shouldIntercept(event) {
397
416
  const HybridlyImports = {
398
417
  "hybridly/vue": [
399
418
  "useProperty",
419
+ "useTypedProperty",
400
420
  "useProperties",
401
421
  "useRouter",
402
422
  "useBackForward",
@@ -404,13 +424,14 @@ const HybridlyImports = {
404
424
  "useForm",
405
425
  "useHistoryState",
406
426
  "usePaginator",
407
- "useLayout",
408
- "route"
427
+ "defineLayout",
428
+ "defineLayoutProperties",
429
+ "registerHook"
409
430
  ],
410
431
  "hybridly": [
411
- "registerHook",
412
- "registerHookOnce",
413
432
  "router",
433
+ "route",
434
+ "current",
414
435
  "can"
415
436
  ]
416
437
  };
@@ -472,31 +493,34 @@ function toReactive(objectRef) {
472
493
  function useProperties() {
473
494
  return readonly(toReactive(computed(() => state.context.value?.view.properties)));
474
495
  }
496
+ function useTypedProperty(path, fallback) {
497
+ return computed(
498
+ () => path.split(".").reduce((o, i) => o[i], state.context.value?.view.properties) ?? fallback
499
+ );
500
+ }
475
501
  function useProperty(path, fallback) {
476
- return computed(() => path.split(".").reduce((o, i) => o[i], state.context.value?.view.properties) ?? fallback);
502
+ return computed(
503
+ () => path.split(".").reduce((o, i) => o[i], state.context.value?.view.properties) ?? fallback
504
+ );
477
505
  }
478
506
 
479
507
  function useContext() {
480
508
  return computed(() => state.context.value);
481
509
  }
482
510
 
483
- function useRouter() {
484
- return router;
485
- }
486
-
487
511
  function safeClone(obj) {
488
512
  return clone(toRaw(obj));
489
513
  }
490
514
  function useForm(options) {
491
- const shouldRemember = options?.key !== false;
515
+ const shouldRemember = !!options?.key;
492
516
  const historyKey = options?.key ?? "form:default";
493
517
  const historyData = shouldRemember ? router.history.get(historyKey) : void 0;
494
518
  const timeoutIds = {
495
519
  recentlyFailed: void 0,
496
520
  recentlySuccessful: void 0
497
521
  };
498
- const initial = readonly(safeClone(options.fields));
499
- const loaded = readonly(safeClone(historyData?.fields ?? options.fields));
522
+ const initial = safeClone(options.fields);
523
+ const loaded = safeClone(historyData?.fields ?? options.fields);
500
524
  const fields = reactive(safeClone(historyData?.fields ?? options.fields));
501
525
  const errors = ref(historyData?.errors ?? {});
502
526
  const isDirty = ref(false);
@@ -505,61 +529,85 @@ function useForm(options) {
505
529
  const recentlyFailed = ref(false);
506
530
  const failed = ref(false);
507
531
  const processing = ref(false);
532
+ const progress = ref();
508
533
  function reset(...keys) {
509
534
  if (keys.length === 0) {
510
535
  keys = Object.keys(fields);
511
536
  }
512
- keys.forEach((key) => Reflect.set(fields, key, safeClone(Reflect.get(initial, key))));
513
- clearErrors();
537
+ keys.forEach((key) => {
538
+ Reflect.set(fields, key, safeClone(Reflect.get(initial, key)));
539
+ clearError(key);
540
+ });
514
541
  }
515
542
  function submit(optionsOverrides) {
516
543
  const url = typeof options.url === "function" ? options.url() : options.url;
517
544
  const data = typeof options.transform === "function" ? options.transform?.(fields) : fields;
518
- return router.visit({
545
+ const preserveState = optionsOverrides?.preserveState ?? options.preserveState;
546
+ return router.navigate({
547
+ ...options,
519
548
  url: url ?? state.context.value?.url,
520
549
  method: options.method ?? "POST",
521
550
  ...optionsOverrides,
522
551
  data: safeClone(data),
523
- preserveState: optionsOverrides?.preserveState === void 0 && options.method !== "GET" ? true : optionsOverrides?.preserveState,
552
+ preserveState: preserveState === void 0 && options.method !== "GET" ? true : preserveState,
524
553
  hooks: {
525
- before: (visit) => {
554
+ before: (navigation, context) => {
526
555
  failed.value = false;
527
556
  successful.value = false;
528
557
  recentlySuccessful.value = false;
529
558
  clearTimeout(timeoutIds.recentlySuccessful);
530
559
  clearTimeout(timeoutIds.recentlyFailed);
531
- clearErrors();
532
- return options.hooks?.before?.(visit);
560
+ return options.hooks?.before?.(navigation, context);
533
561
  },
534
562
  start: (context) => {
535
563
  processing.value = true;
536
564
  return options.hooks?.start?.(context);
537
565
  },
538
- error: (incoming) => {
566
+ progress: (incoming, context) => {
567
+ progress.value = incoming;
568
+ return options.hooks?.progress?.(incoming, context);
569
+ },
570
+ error: (incoming, context) => {
539
571
  setErrors(incoming);
540
572
  failed.value = true;
541
573
  recentlyFailed.value = true;
542
574
  timeoutIds.recentlyFailed = setTimeout(() => recentlyFailed.value = false, options?.timeout ?? 5e3);
543
- return options.hooks?.error?.(incoming);
575
+ return options.hooks?.error?.(incoming, context);
544
576
  },
545
- success: (payload) => {
577
+ success: (payload, context) => {
578
+ clearErrors();
546
579
  if (options?.reset !== false) {
547
580
  reset();
548
581
  }
549
582
  successful.value = true;
550
583
  recentlySuccessful.value = true;
551
584
  timeoutIds.recentlySuccessful = setTimeout(() => recentlySuccessful.value = false, options?.timeout ?? 5e3);
552
- return options.hooks?.success?.(payload);
585
+ return options.hooks?.success?.(payload, context);
553
586
  },
554
587
  after: (context) => {
588
+ progress.value = void 0;
555
589
  processing.value = false;
556
590
  return options.hooks?.after?.(context);
557
591
  }
558
592
  }
559
593
  });
560
594
  }
561
- function clearErrors() {
562
- errors.value = {};
595
+ function clearErrors(...keys) {
596
+ if (keys.length === 0) {
597
+ keys = Object.keys(fields);
598
+ }
599
+ keys.forEach((key) => {
600
+ clearError(key);
601
+ });
602
+ }
603
+ function hasDirty(...keys) {
604
+ if (keys.length === 0) {
605
+ return isDirty.value;
606
+ }
607
+ return keys.some((key) => !isEqual(toRaw(fields[key]), toRaw(initial[key])));
608
+ }
609
+ function clearError(key) {
610
+ errors.value[key] = void 0;
563
611
  }
564
612
  function setErrors(incoming) {
565
613
  errors.value = incoming;
@@ -578,21 +626,25 @@ function useForm(options) {
578
626
  }, { deep: true, immediate: true });
579
627
  return reactive({
580
628
  reset,
581
- initial,
582
629
  fields,
583
- loaded,
584
- submit,
585
630
  abort,
586
631
  setErrors,
587
632
  clearErrors,
633
+ clearError,
634
+ hasDirty,
635
+ submitWithOptions: submit,
636
+ submit: () => submit(),
588
637
  hasErrors: computed(() => Object.values(errors.value).length > 0),
589
- isDirty: readonly(isDirty),
590
- errors: readonly(errors),
591
- processing: readonly(processing),
592
- successful: readonly(successful),
593
- failed: readonly(failed),
594
- recentlySuccessful: readonly(recentlySuccessful),
595
- recentlyFailed: readonly(recentlyFailed)
638
+ initial,
639
+ loaded,
640
+ progress,
641
+ isDirty,
642
+ errors,
643
+ processing,
644
+ successful,
645
+ failed,
646
+ recentlySuccessful,
647
+ recentlyFailed
596
648
  });
597
649
  }
598
650
 
@@ -606,7 +658,7 @@ function useHistoryState(key, initial) {
606
658
 
607
659
  function useBackForward() {
608
660
  const callbacks = [];
609
- registerHook("navigate", (options) => {
661
+ registerHook$1("navigated", (options) => {
610
662
  if (options.isBackForward) {
611
663
  callbacks.forEach((fn) => fn(state.context.value));
612
664
  callbacks.splice(0, callbacks.length);
@@ -651,132 +703,22 @@ function usePaginator(paginator) {
651
703
  return { pages, items, previous, next, first, last, total, from, to };
652
704
  }
653
705
 
654
- function useLayout(layout) {
655
- state.setViewLayout(layout);
706
+ function defineLayout(...args) {
707
+ const layouts = args[0];
708
+ const properties = args[1];
709
+ state.setViewLayout(layouts);
710
+ state.setViewLayoutProperties(properties);
656
711
  }
657
-
658
- class Route {
659
- constructor(name, absolute) {
660
- this.name = name;
661
- this.absolute = absolute;
662
- this.definition = Route.getDefinition(name);
663
- }
664
- static getDefinition(name) {
665
- if (!state.routes.value) {
666
- throw new Error("Routing is not initialized. Have you enabled the Vite plugin?");
667
- }
668
- const routes = state.routes.value;
669
- const route = routes?.routes?.[name];
670
- if (!route) {
671
- throw new Error(`Route ${name.toString()} does not exist.`);
672
- }
673
- return route;
674
- }
675
- get template() {
676
- 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;
677
- return `${origin}/${this.definition.uri}`.replace(/\/+$/, "");
678
- }
679
- get parameterSegments() {
680
- return this.template.match(/{[^}?]+\??}/g)?.map((segment) => ({
681
- name: segment.replace(/{|\??}/g, ""),
682
- required: !/\?}$/.test(segment)
683
- })) ?? [];
684
- }
685
- matchesUrl(url) {
686
- if (!this.definition.methods.includes("GET")) {
687
- return false;
688
- }
689
- const pattern = this.template.replace(/(\/?){([^}?]*)(\??)}/g, (_, slash, segment, optional) => {
690
- const regex = `(?<${segment}>${this.definition.wheres?.[segment]?.replace(/(^\^)|(\$$)/g, "") || "[^/?]+"})`;
691
- return optional ? `(${slash}${regex})?` : `${slash}${regex}`;
692
- }).replace(/^\w+:\/\//, "");
693
- const [location, query] = url.replace(/^\w+:\/\//, "").split("?");
694
- const matches = new RegExp(`^${pattern}/?$`).exec(location);
695
- return matches ? { params: matches.groups, query: parse(query) } : false;
696
- }
697
- compile(params) {
698
- const segments = this.parameterSegments;
699
- if (!segments.length) {
700
- return this.template;
701
- }
702
- return this.template.replace(/{([^}?]+)(\??)}/g, (_, segment, optional) => {
703
- if (!optional && [null, void 0].includes(params?.[segment])) {
704
- throw new Error(`Router error: [${segment}] parameter is required for route [${this.name}].`);
705
- }
706
- if (segments[segments.length - 1].name === segment && this.definition?.wheres?.[segment] === ".*") {
707
- return encodeURIComponent(params[segment] ?? "").replace(/%2F/g, "/");
708
- }
709
- if (this.definition?.wheres?.[segment] && !new RegExp(`^${optional ? `(${this.definition?.wheres?.[segment]})?` : this.definition?.wheres?.[segment]}$`).test(params[segment] ?? "")) {
710
- throw new Error(`Router error: [${segment}] parameter does not match required format [${this.definition?.wheres?.[segment]}] for route [${this.name}].`);
711
- }
712
- return encodeURIComponent(params[segment] ?? "");
713
- }).replace(/\/+$/, "");
714
- }
712
+ function defineLayoutProperties(properties) {
713
+ state.setViewLayoutProperties(properties);
715
714
  }
716
715
 
717
- class Router extends String {
718
- constructor(name, parameters, absolute = true) {
719
- super();
720
- this.route = new Route(name, absolute);
721
- this.setParameters(parameters);
722
- }
723
- toString() {
724
- 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] }), {});
725
- return this.route.compile(this.parameters) + stringify({ ...unhandled, ...this.parameters._query }, {
726
- addQueryPrefix: true,
727
- arrayFormat: "indices",
728
- encodeValuesOnly: true,
729
- skipNulls: true,
730
- encoder: (value, encoder) => typeof value === "boolean" ? Number(value).toString() : encoder(value)
731
- });
732
- }
733
- static has(name) {
734
- try {
735
- Route.getDefinition(name);
736
- return true;
737
- } catch {
738
- return false;
739
- }
740
- }
741
- setParameters(parameters) {
742
- this.parameters = parameters ?? {};
743
- this.parameters = ["string", "number"].includes(typeof this.parameters) ? [this.parameters] : this.parameters;
744
- const segments = this.route.parameterSegments.filter(({ name }) => !state.routes.value?.defaults[name]);
745
- if (Array.isArray(this.parameters)) {
746
- this.parameters = this.parameters.reduce((result, current, i) => segments[i] ? { ...result, [segments[i].name]: current } : typeof current === "object" ? { ...result, ...current } : { ...result, [current]: "" }, {});
747
- } 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"))) {
748
- this.parameters = { [segments[0].name]: this.parameters };
749
- }
750
- this.parameters = {
751
- ...this.getDefaults(),
752
- ...this.substituteBindings()
753
- };
716
+ const registerHook = (hook, fn, options) => {
717
+ const unregister = registerHook$1(hook, fn, options);
718
+ if (getCurrentInstance()) {
719
+ onUnmounted(() => unregister());
754
720
  }
755
- getDefaults() {
756
- return this.route.parameterSegments.filter(({ name }) => state.routes.value?.defaults[name]).reduce((result, { name }) => ({ ...result, [name]: state.routes.value?.defaults[name] }), {});
757
- }
758
- substituteBindings() {
759
- return Object.entries(this.parameters).reduce((result, [key, value]) => {
760
- if (!value || typeof value !== "object" || Array.isArray(value) || !this.route.parameterSegments.some(({ name }) => name === key)) {
761
- return { ...result, [key]: value };
762
- }
763
- if (!Reflect.has(value, this.route.definition.bindings[key])) {
764
- if (Reflect.has(value, "id")) {
765
- this.route.definition.bindings[key] = "id";
766
- } else {
767
- throw new Error(`Router error: object passed as [${key}] parameter is missing route model binding key [${this.route.definition.bindings?.[key]}].`);
768
- }
769
- }
770
- return { ...result, [key]: value[this.route.definition.bindings[key]] };
771
- }, {});
772
- }
773
- valueOf() {
774
- return this.toString();
775
- }
776
- }
777
-
778
- function route(name, parameters, absolute) {
779
- return new Router(name, parameters, absolute).toString();
780
- }
721
+ return unregister;
722
+ };
781
723
 
782
- export { HybridlyImports, HybridlyResolver, RouterLink, initializeHybridly, route, useBackForward, useContext, useForm, useHistoryState, useLayout, usePaginator, useProperties, useProperty, useRouter };
724
+ export { HybridlyImports, HybridlyResolver, RouterLink, defineLayout, defineLayoutProperties, initializeHybridly, registerHook, resolvePageComponent, useBackForward, useContext, useForm, useHistoryState, usePaginator, useProperties, useProperty, useTypedProperty };