@vuecs/navigation 1.0.0

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.
Files changed (69) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +267 -0
  3. package/core/index.cjs +1 -0
  4. package/core/index.d.ts +2 -0
  5. package/core/index.mjs +1 -0
  6. package/core/package.json +5 -0
  7. package/dist/components/index.d.ts +3 -0
  8. package/dist/components/index.d.ts.map +1 -0
  9. package/dist/components/item.d.ts +26 -0
  10. package/dist/components/item.d.ts.map +1 -0
  11. package/dist/components/items.d.ts +27 -0
  12. package/dist/components/items.d.ts.map +1 -0
  13. package/dist/constants.d.ts +13 -0
  14. package/dist/constants.d.ts.map +1 -0
  15. package/dist/core/build.d.ts +4 -0
  16. package/dist/core/build.d.ts.map +1 -0
  17. package/dist/core/check.d.ts +3 -0
  18. package/dist/core/check.d.ts.map +1 -0
  19. package/dist/core/expansion.d.ts +6 -0
  20. package/dist/core/expansion.d.ts.map +1 -0
  21. package/dist/core/flatten.d.ts +3 -0
  22. package/dist/core/flatten.d.ts.map +1 -0
  23. package/dist/core/index.d.ts +12 -0
  24. package/dist/core/index.d.ts.map +1 -0
  25. package/dist/core/match.d.ts +3 -0
  26. package/dist/core/match.d.ts.map +1 -0
  27. package/dist/core/reduce.d.ts +8 -0
  28. package/dist/core/reduce.d.ts.map +1 -0
  29. package/dist/core/refresh.d.ts +3 -0
  30. package/dist/core/refresh.d.ts.map +1 -0
  31. package/dist/core/replace.d.ts +6 -0
  32. package/dist/core/replace.d.ts.map +1 -0
  33. package/dist/core/reset.d.ts +3 -0
  34. package/dist/core/reset.d.ts.map +1 -0
  35. package/dist/core/select.d.ts +4 -0
  36. package/dist/core/select.d.ts.map +1 -0
  37. package/dist/core/tier.d.ts +7 -0
  38. package/dist/core/tier.d.ts.map +1 -0
  39. package/dist/core/toggle.d.ts +4 -0
  40. package/dist/core/toggle.d.ts.map +1 -0
  41. package/dist/index.cjs +593 -0
  42. package/dist/index.cjs.map +1 -0
  43. package/dist/index.d.ts +15 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.mjs +579 -0
  46. package/dist/index.mjs.map +1 -0
  47. package/dist/module.d.ts +3 -0
  48. package/dist/module.d.ts.map +1 -0
  49. package/dist/provider/index.d.ts +3 -0
  50. package/dist/provider/index.d.ts.map +1 -0
  51. package/dist/provider/module.d.ts +5 -0
  52. package/dist/provider/module.d.ts.map +1 -0
  53. package/dist/provider/type.d.ts +8 -0
  54. package/dist/provider/type.d.ts.map +1 -0
  55. package/dist/store/index.d.ts +3 -0
  56. package/dist/store/index.d.ts.map +1 -0
  57. package/dist/store/module.d.ts +7 -0
  58. package/dist/store/module.d.ts.map +1 -0
  59. package/dist/store/type.d.ts +16 -0
  60. package/dist/store/type.d.ts.map +1 -0
  61. package/dist/type.d.ts +26 -0
  62. package/dist/type.d.ts.map +1 -0
  63. package/dist/utils/index.d.ts +2 -0
  64. package/dist/utils/index.d.ts.map +1 -0
  65. package/dist/utils/url.d.ts +2 -0
  66. package/dist/utils/url.d.ts.map +1 -0
  67. package/dist/vue.d.ts +8 -0
  68. package/dist/vue.d.ts.map +1 -0
  69. package/package.json +74 -0
package/dist/index.mjs ADDED
@@ -0,0 +1,579 @@
1
+ import { hasNormalizedSlot, normalizeSlot, applyPluginBaseOptions } from '@vuecs/core';
2
+ import { inject, ref, provide, hasInjectionContext, isRef, defineComponent, toRef, h, resolveComponent, computed } from 'vue';
3
+ import { VCLink } from '@vuecs/link';
4
+
5
+ let instance;
6
+ function useNavigationProvider(module) {
7
+ if (typeof instance !== 'undefined') {
8
+ return instance;
9
+ }
10
+ if (typeof module === 'undefined') {
11
+ throw new Error('A Navigation Provider must be set!');
12
+ }
13
+ instance = module;
14
+ return instance;
15
+ }
16
+ function setNavigationProvider(module) {
17
+ instance = module;
18
+ }
19
+ function createNavigationProvider(input) {
20
+ return input;
21
+ }
22
+
23
+ const StoreSymbol = Symbol.for('VLNavigationStore');
24
+ function isStoreInjected() {
25
+ if (!hasInjectionContext()) {
26
+ return false;
27
+ }
28
+ const instance = inject(StoreSymbol);
29
+ return !!instance;
30
+ }
31
+ function setupStore(app) {
32
+ const store = {
33
+ items: ref([]),
34
+ itemsActive: ref([])
35
+ };
36
+ if (typeof app === 'undefined') {
37
+ if (isStoreInjected()) {
38
+ return;
39
+ }
40
+ provide(StoreSymbol, store);
41
+ return;
42
+ }
43
+ if (app._context && app._context.provides && app._context.provides[StoreSymbol]) {
44
+ return;
45
+ }
46
+ app.provide(StoreSymbol, store);
47
+ }
48
+ function injectStore() {
49
+ const instance = inject(StoreSymbol);
50
+ if (!instance) {
51
+ throw new Error('The Store is not set.');
52
+ }
53
+ return instance;
54
+ }
55
+
56
+ function isNavigationItemMatch(one, two) {
57
+ if (!one || !two) {
58
+ return false;
59
+ }
60
+ if (one.id && two.id && one.id === two.id) {
61
+ return true;
62
+ }
63
+ if (one.name && two.name && one.name === two.name) {
64
+ return true;
65
+ }
66
+ if (one.url && two.url && one.url === two.url) {
67
+ return true;
68
+ }
69
+ if (one.children && two.children && one.children.length === two.children.length) {
70
+ for(let i = 0; i < one.children.length; i++){
71
+ if (!isNavigationItemMatch(one.children[i], two.children[i])) {
72
+ return false;
73
+ }
74
+ }
75
+ return true;
76
+ }
77
+ return false;
78
+ }
79
+
80
+ // --------------------------------------------------
81
+ function setNavigationExpansion(items, item, parentMatch = false) {
82
+ let matchInIteration = false;
83
+ for(let i = 0; i < items.length; i++){
84
+ const isMatch = isNavigationItemMatch(items[i], item);
85
+ let isChildMatch = false;
86
+ const { children } = items[i];
87
+ if (typeof children !== 'undefined') {
88
+ const { items: childItems, match: childMatch } = setNavigationExpansion(children, item, isMatch);
89
+ items[i].children = childItems;
90
+ isChildMatch = childMatch;
91
+ }
92
+ if (isMatch) {
93
+ items[i].active = true;
94
+ }
95
+ if (isMatch || isChildMatch) {
96
+ items[i].display = true;
97
+ items[i].displayChildren = true;
98
+ }
99
+ if (parentMatch) {
100
+ items[i].display = true;
101
+ }
102
+ if (!matchInIteration) {
103
+ matchInIteration = isMatch || isChildMatch;
104
+ }
105
+ }
106
+ if (matchInIteration) {
107
+ for(let i = 0; i < items.length; i++){
108
+ items[i].display = true;
109
+ }
110
+ }
111
+ return {
112
+ items,
113
+ match: matchInIteration
114
+ };
115
+ }
116
+
117
+ function resetNavigationItem(items, root = true) {
118
+ for(let i = 0; i < items.length; i++){
119
+ items[i].display = root;
120
+ items[i].displayChildren = false;
121
+ items[i].active = false;
122
+ const { children } = items[i];
123
+ if (typeof children !== 'undefined') {
124
+ items[i].children = resetNavigationItem(children, false);
125
+ }
126
+ }
127
+ return items;
128
+ }
129
+
130
+ function findNavigationItemsForTier(items, tier) {
131
+ const filterFn = (component)=>typeof component.tier !== 'undefined' && component.tier === tier;
132
+ return items.filter(filterFn);
133
+ }
134
+ function findNavigationItemForTier(items, tier) {
135
+ const data = findNavigationItemsForTier(items, tier);
136
+ if (data.length >= 1) {
137
+ return data[0];
138
+ }
139
+ return undefined;
140
+ }
141
+ function setTierForNavigationItems(items, tier) {
142
+ const mapFn = (component)=>{
143
+ component.tier = tier;
144
+ return component;
145
+ };
146
+ if (isRef(items)) {
147
+ return items.value.map(mapFn);
148
+ }
149
+ return items.map(mapFn);
150
+ }
151
+ function removeTierFromNavigationItems(items, tier) {
152
+ const filterFn = (items)=>typeof items.tier === 'undefined' || items.tier !== tier;
153
+ if (isRef(items)) {
154
+ return items.value.filter(filterFn);
155
+ }
156
+ return items.filter(filterFn);
157
+ }
158
+
159
+ function replaceNavigationTierItemActive(store, tier, item) {
160
+ const items = removeTierFromNavigationItems(store.itemsActive.value, tier);
161
+ if (item) {
162
+ item.tier = tier;
163
+ store.itemsActive.value = [
164
+ ...items,
165
+ item
166
+ ];
167
+ } else {
168
+ store.itemsActive.value = items;
169
+ }
170
+ }
171
+ function replaceNavigationTierItems(store, tier, items) {
172
+ const componentsExisting = removeTierFromNavigationItems(store.items.value, tier);
173
+ store.items.value = [
174
+ ...componentsExisting,
175
+ ...setTierForNavigationItems(items, tier)
176
+ ];
177
+ }
178
+
179
+ function refreshNavigationTierItems(store, tier) {
180
+ const components = resetNavigationItem(findNavigationItemsForTier(store.items.value, tier));
181
+ const component = findNavigationItemForTier(store.itemsActive.value, tier);
182
+ if (component) {
183
+ const { items } = setNavigationExpansion(components, component);
184
+ replaceNavigationTierItems(store, tier, items);
185
+ return;
186
+ }
187
+ replaceNavigationTierItems(store, tier, components);
188
+ }
189
+
190
+ async function buildNavigationForTier(store, tier, itemsActive) {
191
+ if (typeof itemsActive === 'undefined' || itemsActive.length === 0) {
192
+ let tierStartIndex = 0;
193
+ const tierEndIndex = tier;
194
+ itemsActive = [];
195
+ while(tierStartIndex <= tierEndIndex){
196
+ const component = findNavigationItemForTier(store.itemsActive.value, tierStartIndex);
197
+ if (!component) {
198
+ break;
199
+ }
200
+ itemsActive.push(component);
201
+ tierStartIndex++;
202
+ }
203
+ }
204
+ const items = await useNavigationProvider().getItems(tier, itemsActive);
205
+ if (typeof items === 'undefined') {
206
+ return false;
207
+ }
208
+ replaceNavigationTierItems(store, tier, items);
209
+ refreshNavigationTierItems(store, tier);
210
+ return true;
211
+ }
212
+
213
+ function flattenNestedNavigationItems(items) {
214
+ const output = [];
215
+ for(let i = 0; i < items.length; i++){
216
+ const { children, ...data } = items[i];
217
+ output.push(data);
218
+ if (children && children.length > 0) {
219
+ output.push(...flattenNestedNavigationItems([
220
+ ...children
221
+ ]));
222
+ }
223
+ }
224
+ return output;
225
+ }
226
+
227
+ async function selectNavigationTierItem(store, tier, component) {
228
+ const isMatch = isNavigationItemMatch(findNavigationItemForTier(store.itemsActive.value, tier), component);
229
+ if (isMatch) {
230
+ return;
231
+ }
232
+ replaceNavigationTierItemActive(store, tier, component);
233
+ refreshNavigationTierItems(store, tier);
234
+ tier++;
235
+ // eslint-disable-next-line no-constant-condition
236
+ while(true){
237
+ const built = await buildNavigationForTier(store, tier);
238
+ if (!built) {
239
+ break;
240
+ }
241
+ tier++;
242
+ }
243
+ }
244
+
245
+ function toggleNavigation(store, tier, component) {
246
+ const isMatch = component.displayChildren || isNavigationItemMatch(findNavigationItemForTier(store.itemsActive.value, tier), component);
247
+ if (isMatch) {
248
+ replaceNavigationTierItemActive(store, tier, undefined);
249
+ } else {
250
+ replaceNavigationTierItemActive(store, tier, component);
251
+ }
252
+ refreshNavigationTierItems(store, tier);
253
+ }
254
+
255
+ function isAbsoluteURL(str) {
256
+ return str.substring(0, 7) === 'http://' || str.substring(0, 8) === 'https://';
257
+ }
258
+
259
+ var SlotName;
260
+ (function(SlotName) {
261
+ SlotName["ITEM"] = "item";
262
+ SlotName["SEPARATOR"] = "separator";
263
+ SlotName["LINK"] = "link";
264
+ SlotName["SUB"] = "sub";
265
+ SlotName["SUB_TITLE"] = "sub-title";
266
+ SlotName["SUB_ITEMS"] = "sub-items";
267
+ })(SlotName || (SlotName = {}));
268
+ var ElementType;
269
+ (function(ElementType) {
270
+ ElementType["LINK"] = "link";
271
+ ElementType["SEPARATOR"] = "separator";
272
+ })(ElementType || (ElementType = {}));
273
+
274
+ const VCNavItem = defineComponent({
275
+ props: {
276
+ tier: {
277
+ type: Number,
278
+ default: 0
279
+ },
280
+ component: {
281
+ type: Object,
282
+ required: true
283
+ }
284
+ },
285
+ setup (props, { slots }) {
286
+ const store = injectStore();
287
+ const component = toRef(props, 'component');
288
+ const selectComponent = async (value)=>{
289
+ await selectNavigationTierItem(store, props.tier, value);
290
+ };
291
+ const toggleComponentExpansion = async (value)=>toggleNavigation(store, props.tier, value);
292
+ return ()=>{
293
+ const buildItem = ()=>{
294
+ let item;
295
+ switch(component.value.type){
296
+ case ElementType.SEPARATOR:
297
+ {
298
+ const hasSlot = hasNormalizedSlot(SlotName.SEPARATOR, slots);
299
+ if (hasSlot) {
300
+ item = normalizeSlot(SlotName.SEPARATOR, {
301
+ component: component.value
302
+ }, slots);
303
+ } else {
304
+ item = h('div', {
305
+ class: 'nav-separator'
306
+ }, component.value.name);
307
+ }
308
+ break;
309
+ }
310
+ default:
311
+ {
312
+ if (typeof component.value.children === 'undefined') {
313
+ const hasSlot = hasNormalizedSlot(SlotName.LINK, slots);
314
+ if (hasSlot) {
315
+ item = normalizeSlot(SlotName.LINK, {
316
+ component: component.value,
317
+ selectComponent,
318
+ isActive: component.value.active
319
+ }, slots);
320
+ } else {
321
+ const linkProps = {
322
+ active: component.value.active,
323
+ disabled: false,
324
+ prefetch: true
325
+ };
326
+ if (component.value.url) {
327
+ if (isAbsoluteURL(component.value.url) || component.value.url.startsWith('#')) {
328
+ linkProps.href = component.value.url;
329
+ if (component.value.urlTarget) {
330
+ linkProps.target = component.value.urlTarget;
331
+ }
332
+ } else {
333
+ linkProps.to = component.value.url;
334
+ }
335
+ }
336
+ item = h(VCLink, {
337
+ class: [
338
+ 'nav-link',
339
+ {
340
+ 'root-link': component.value.root
341
+ }
342
+ ],
343
+ ...linkProps,
344
+ onClicked () {
345
+ if (!component.value.url) {
346
+ return selectComponent.call(null, component.value);
347
+ }
348
+ return undefined;
349
+ },
350
+ onClick () {
351
+ return selectComponent.call(null, component.value);
352
+ }
353
+ }, {
354
+ default: ()=>[
355
+ ...component.value.icon ? [
356
+ h('i', {
357
+ class: component.value.icon
358
+ })
359
+ ] : [],
360
+ h('span', {
361
+ class: 'nav-link-text'
362
+ }, [
363
+ component.value.name
364
+ ])
365
+ ]
366
+ });
367
+ }
368
+ } else if (hasNormalizedSlot(SlotName.SUB, slots)) {
369
+ item = normalizeSlot(SlotName.SUB, {
370
+ component: component.value,
371
+ selectComponent,
372
+ toggleComponentExpansion
373
+ }, slots);
374
+ } else {
375
+ let title;
376
+ if (hasNormalizedSlot(SlotName.SUB_TITLE, slots)) {
377
+ title = normalizeSlot(SlotName.SUB_TITLE, {
378
+ component: component.value,
379
+ selectComponent,
380
+ toggleComponentExpansion
381
+ });
382
+ } else {
383
+ title = h('div', {
384
+ class: 'nav-sub-title',
385
+ onClick ($event) {
386
+ $event.preventDefault();
387
+ return toggleComponentExpansion.call(null, component.value);
388
+ }
389
+ }, [
390
+ [
391
+ ...component.value.icon ? [
392
+ h('i', {
393
+ class: component.value.icon
394
+ })
395
+ ] : [],
396
+ h('span', {
397
+ class: 'nav-link-text'
398
+ }, [
399
+ component.value.name
400
+ ])
401
+ ]
402
+ ]);
403
+ }
404
+ let items;
405
+ if (hasNormalizedSlot(SlotName.SUB_ITEMS, slots)) {
406
+ items = normalizeSlot(SlotName.SUB_ITEMS, {
407
+ component: component.value,
408
+ selectComponent,
409
+ toggleComponentExpansion
410
+ });
411
+ } else if (component.value.displayChildren) {
412
+ const navigationComponents = resolveComponent('VCNavItems');
413
+ items = h(navigationComponents, {
414
+ class: 'list-unstyled nav-sub-items',
415
+ tier: props.tier,
416
+ entities: component.value.children
417
+ });
418
+ }
419
+ item = [
420
+ title,
421
+ items
422
+ ];
423
+ }
424
+ break;
425
+ }
426
+ }
427
+ return item;
428
+ };
429
+ return h('div', {
430
+ class: [
431
+ 'nav-item',
432
+ {
433
+ active: component.value.active || component.value.displayChildren
434
+ }
435
+ ]
436
+ }, [
437
+ buildItem()
438
+ ]);
439
+ };
440
+ }
441
+ });
442
+
443
+ const VCNavItems = defineComponent({
444
+ props: {
445
+ tier: {
446
+ type: Number,
447
+ default: 0
448
+ },
449
+ entities: {
450
+ type: Array,
451
+ default: undefined
452
+ }
453
+ },
454
+ setup (props, { slots }) {
455
+ const store = injectStore();
456
+ const items = computed(()=>{
457
+ if (typeof props.entities !== 'undefined') {
458
+ return props.entities;
459
+ }
460
+ return findNavigationItemsForTier(store.items.value, props.tier);
461
+ });
462
+ const buildChild = (context)=>{
463
+ if (hasNormalizedSlot(SlotName.ITEM, slots)) {
464
+ return normalizeSlot(SlotName.ITEM, context, slots);
465
+ }
466
+ return h(VCNavItem, context);
467
+ };
468
+ const buildChildren = ()=>{
469
+ const entities = [];
470
+ if (items.value) {
471
+ for(let i = 0; i < items.value.length; i++){
472
+ if (items.value[i].display) {
473
+ entities.push(h('li', {
474
+ key: i
475
+ }, [
476
+ buildChild({
477
+ tier: props.tier,
478
+ component: items.value[i]
479
+ })
480
+ ]));
481
+ }
482
+ }
483
+ }
484
+ return entities;
485
+ };
486
+ return ()=>h('ul', {
487
+ class: 'nav-items'
488
+ }, [
489
+ buildChildren()
490
+ ]);
491
+ }
492
+ });
493
+
494
+ async function buildNavigation(context = {}) {
495
+ const store = injectStore();
496
+ const navigationProvider = useNavigationProvider();
497
+ let itemsActive = [];
498
+ if (typeof context.itemsActive !== 'undefined') {
499
+ itemsActive = context.itemsActive;
500
+ } else if (context.route) {
501
+ if (typeof navigationProvider.getItemsActiveByRoute !== 'undefined') {
502
+ itemsActive = await navigationProvider.getItemsActiveByRoute(context.route);
503
+ } else if (typeof navigationProvider.getItemsActiveByURL !== 'undefined') {
504
+ itemsActive = await navigationProvider.getItemsActiveByURL(context.route.fullPath);
505
+ }
506
+ } else if (context.url && typeof navigationProvider.getItemsActiveByURL !== 'undefined') {
507
+ itemsActive = await navigationProvider.getItemsActiveByURL(context.url);
508
+ }
509
+ if (itemsActive.length > 0) {
510
+ for(let i = 0; i < itemsActive.length; i++){
511
+ if (typeof itemsActive[i].tier === 'undefined') {
512
+ itemsActive[i].tier = i;
513
+ }
514
+ }
515
+ }
516
+ let tierIndex = 0;
517
+ let url;
518
+ if (typeof context.url === 'string') {
519
+ url = context.url;
520
+ } else if (typeof context.route !== 'undefined') {
521
+ url = context.route.fullPath;
522
+ }
523
+ // eslint-disable-next-line no-constant-condition
524
+ while(true){
525
+ let items = await navigationProvider.getItems(tierIndex, itemsActive);
526
+ if (!items || items.length === 0) {
527
+ break;
528
+ }
529
+ // ensure tier property
530
+ items = setTierForNavigationItems(items, tierIndex);
531
+ let currentItem = findNavigationItemForTier(itemsActive, tierIndex);
532
+ if (!currentItem) {
533
+ if (url) {
534
+ const urlMatches = items.filter((item)=>isNavigationItemMatch(item, {
535
+ url
536
+ }));
537
+ if (urlMatches.length > 0) {
538
+ [currentItem] = urlMatches;
539
+ }
540
+ }
541
+ if (!currentItem) {
542
+ const defaultItem = items.filter((item)=>item.default);
543
+ if (defaultItem.length > 0) {
544
+ currentItem = defaultItem;
545
+ } else {
546
+ [currentItem] = items;
547
+ }
548
+ }
549
+ currentItem.tier = tierIndex;
550
+ itemsActive.push(currentItem);
551
+ }
552
+ if (!currentItem) {
553
+ continue;
554
+ }
555
+ replaceNavigationTierItemActive(store, tierIndex, currentItem);
556
+ await buildNavigationForTier(store, tierIndex, itemsActive);
557
+ tierIndex++;
558
+ }
559
+ }
560
+
561
+ function install(instance, options) {
562
+ if (options.provider) {
563
+ setNavigationProvider(options.provider);
564
+ }
565
+ setupStore(instance);
566
+ applyPluginBaseOptions(instance, options);
567
+ Object.entries({
568
+ VCNavItem,
569
+ VCNavItems
570
+ }).forEach(([componentName, component])=>{
571
+ instance.component(componentName, component);
572
+ });
573
+ }
574
+ var index = {
575
+ install
576
+ };
577
+
578
+ export { VCNavItem, VCNavItems, buildNavigation, buildNavigationForTier, createNavigationProvider, index as default, flattenNestedNavigationItems, install, setNavigationProvider, useNavigationProvider };
579
+ //# sourceMappingURL=index.mjs.map