@xwiki/cristal-dsvuetify 0.12.0 → 0.14.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 (71) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/{index-DCILSBLH.js → index-C4ioHAez.js} +4114 -4063
  3. package/dist/index-C4ioHAez.js.map +1 -0
  4. package/dist/index.es.js +6 -4
  5. package/dist/index.umd.js +10 -8
  6. package/dist/index.umd.js.map +1 -1
  7. package/dist/vue/form/x-checkbox.vue.d.ts +11 -0
  8. package/dist/vue/form/x-file-input.vue.d.ts +6 -6
  9. package/dist/vue/form/x-form.vue.d.ts +2 -1
  10. package/dist/vue/form/x-text-field.vue.d.ts +9 -1
  11. package/dist/vue/x-alert.vue.d.ts +2 -1
  12. package/dist/vue/x-avatar.vue.d.ts +2 -19
  13. package/dist/vue/x-btn.vue.d.ts +2 -1
  14. package/dist/vue/x-dialog.vue.d.ts +8 -8
  15. package/dist/vue/x-load.vue.d.ts +1 -1
  16. package/dist/vue/x-menu-item.vue.d.ts +6 -6
  17. package/dist/vue/x-menu-label.vue.d.ts +2 -1
  18. package/dist/vue/x-menu.vue.d.ts +2 -1
  19. package/dist/vue/x-navigation-tree.vue.d.ts +3 -5
  20. package/dist/vue/x-tab-group.vue.d.ts +4 -3
  21. package/dist/vue/x-tab-panel.vue.d.ts +2 -1
  22. package/dist/vue/x-tab.vue.d.ts +2 -1
  23. package/dist/x-alert-CTAhsN6L.js +46 -0
  24. package/dist/x-alert-CTAhsN6L.js.map +1 -0
  25. package/dist/x-avatar-Ds9aYRI9.js +28 -0
  26. package/dist/x-avatar-Ds9aYRI9.js.map +1 -0
  27. package/dist/x-breadcrumb-C2iUd4Sf.js.map +1 -1
  28. package/dist/{x-btn-DNlhiqjD.js → x-btn-BsiSIiiO.js} +9 -7
  29. package/dist/{x-btn-DNlhiqjD.js.map → x-btn-BsiSIiiO.js.map} +1 -1
  30. package/dist/x-card-CRM9KUby.js.map +1 -1
  31. package/dist/x-checkbox-CJ3_IJ-Y.js +28 -0
  32. package/dist/x-checkbox-CJ3_IJ-Y.js.map +1 -0
  33. package/dist/x-dialog-C4gz0mOu.js +48 -0
  34. package/dist/x-dialog-C4gz0mOu.js.map +1 -0
  35. package/dist/{x-file-input-BtGTZtfq.js → x-file-input-CMM5DWJc.js} +11 -9
  36. package/dist/{x-file-input-BtGTZtfq.js.map → x-file-input-CMM5DWJc.js.map} +1 -1
  37. package/dist/{x-form-pSj3365X.js → x-form-BLg386BG.js} +2 -2
  38. package/dist/{x-form-pSj3365X.js.map → x-form-BLg386BG.js.map} +1 -1
  39. package/dist/x-img-BYgs_O1F.js.map +1 -1
  40. package/dist/x-navigation-tree-BW05Yivz.js +502 -0
  41. package/dist/x-navigation-tree-BW05Yivz.js.map +1 -0
  42. package/dist/{x-tab-group-ngNByee3.js → x-tab-group-Bp7V46k9.js} +5 -5
  43. package/dist/{x-tab-group-ngNByee3.js.map → x-tab-group-Bp7V46k9.js.map} +1 -1
  44. package/dist/x-text-field-BxSiyeFc.js +38 -0
  45. package/dist/x-text-field-BxSiyeFc.js.map +1 -0
  46. package/langs/translation-de.json +4 -0
  47. package/langs/translation-fr.json +4 -1
  48. package/package.json +18 -11
  49. package/src/components/vuetifyDesignSystemLoader.ts +7 -0
  50. package/src/vue/css/style.css +17 -4
  51. package/src/vue/form/x-checkbox.vue +35 -0
  52. package/src/vue/form/x-file-input.vue +2 -2
  53. package/src/vue/form/x-text-field.vue +8 -1
  54. package/src/vue/x-alert.vue +10 -0
  55. package/src/vue/x-avatar.vue +9 -9
  56. package/src/vue/x-dialog.vue +10 -3
  57. package/src/vue/x-navigation-tree.vue +109 -68
  58. package/src/vue/x-tab-group.vue +2 -2
  59. package/src/vue/x-tab-panel.vue +0 -2
  60. package/tsconfig.json +14 -8
  61. package/dist/index-DCILSBLH.js.map +0 -1
  62. package/dist/x-alert-C3nY6ghP.js +0 -42
  63. package/dist/x-alert-C3nY6ghP.js.map +0 -1
  64. package/dist/x-avatar-But_Z7ic.js +0 -20
  65. package/dist/x-avatar-But_Z7ic.js.map +0 -1
  66. package/dist/x-dialog-Ce8KWptm.js +0 -38
  67. package/dist/x-dialog-Ce8KWptm.js.map +0 -1
  68. package/dist/x-navigation-tree-W6QDS4uh.js +0 -499
  69. package/dist/x-navigation-tree-W6QDS4uh.js.map +0 -1
  70. package/dist/x-text-field-CAVD3OjY.js +0 -29
  71. package/dist/x-text-field-CAVD3OjY.js.map +0 -1
@@ -0,0 +1,38 @@
1
+ import { defineComponent as n, mergeModels as m, useModel as d, computed as i, openBlock as p, createBlock as f, unref as c } from "vue";
2
+ import { t as V } from "./translations-BNEIsfPR.js";
3
+ import { useI18n as b } from "vue-i18n";
4
+ import { a as v } from "./index-C4ioHAez.js";
5
+ const g = /* @__PURE__ */ n({
6
+ __name: "x-text-field",
7
+ props: /* @__PURE__ */ m({
8
+ name: {},
9
+ label: {},
10
+ required: { type: Boolean },
11
+ autofocus: { type: Boolean },
12
+ modelValue: {}
13
+ }, {
14
+ modelValue: {},
15
+ modelModifiers: {}
16
+ }),
17
+ emits: ["update:modelValue"],
18
+ setup(l) {
19
+ const { t: u } = b({
20
+ messages: V
21
+ }), a = l, t = d(l, "modelValue"), r = i(() => {
22
+ const e = [];
23
+ return a.required && e.push((o) => o ? !0 : u("vuetify.text.input.mandatory")), e;
24
+ });
25
+ return (e, o) => (p(), f(c(v), {
26
+ modelValue: t.value,
27
+ "onUpdate:modelValue": o[0] || (o[0] = (s) => t.value = s),
28
+ label: e.label,
29
+ name: e.name,
30
+ autofocus: e.autofocus,
31
+ rules: r.value
32
+ }, null, 8, ["modelValue", "label", "name", "autofocus", "rules"]));
33
+ }
34
+ });
35
+ export {
36
+ g as default
37
+ };
38
+ //# sourceMappingURL=x-text-field-BxSiyeFc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"x-text-field-BxSiyeFc.js","sources":["../src/vue/form/x-text-field.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport messages from \"../../translations\";\nimport { computed } from \"vue\";\nimport { useI18n } from \"vue-i18n\";\nimport { VTextField } from \"vuetify/components/VTextField\";\nimport type { TextFieldProps } from \"@xwiki/cristal-dsapi\";\n\nconst { t } = useI18n({\n messages,\n});\nconst props = defineProps<TextFieldProps>();\nconst input = defineModel<string>();\n\nconst rules = computed(() => {\n const rulesList = [];\n if (props.required) {\n rulesList.push((value: unknown) => {\n if (value) {\n return true;\n } else {\n return t(\"vuetify.text.input.mandatory\");\n }\n });\n }\n return rulesList;\n});\n</script>\n\n<template>\n <v-text-field\n v-model=\"input\"\n :label=\"label\"\n :name=\"name\"\n :autofocus=\"autofocus\"\n :rules=\"rules\"\n ></v-text-field>\n</template>\n\n<style scoped></style>\n"],"names":["t","useI18n","messages","props","__props","input","_useModel","rules","computed","rulesList","value"],"mappings":";;;;;;;;;;;;;;;;;;AAOM,UAAA,EAAE,GAAAA,EAAE,IAAIC,EAAQ;AAAA,MACpBC,UAAAA;AAAAA,IAAA,CACD,GACKC,IAAQC,GACRC,IAAQC,EAAmBF,GAAA,YAAC,GAE5BG,IAAQC,EAAS,MAAM;AAC3B,YAAMC,IAAY,CAAC;AACnB,aAAIN,EAAM,YACEM,EAAA,KAAK,CAACC,MACVA,IACK,KAEAV,EAAE,8BAA8B,CAE1C,GAEIS;AAAA,IAAA,CACR;;;;;;;;;;;"}
@@ -0,0 +1,4 @@
1
+ {
2
+ "vuetify.file.input.mandatory": "Pflichtfeld",
3
+ "vuetify.text.input.mandatory": "Pflichtfeld"
4
+ }
@@ -1 +1,4 @@
1
- {}
1
+ {
2
+ "vuetify.file.input.mandatory": "Champ obligatoire",
3
+ "vuetify.text.input.mandatory": "Champ obligatoire"
4
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xwiki/cristal-dsvuetify",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "license": "LGPL 2.1",
5
5
  "author": "XWiki Org Community <contact@xwiki.org>",
6
6
  "homepage": "https://cristal.xwiki.org/",
@@ -22,19 +22,26 @@
22
22
  },
23
23
  "main": "./dist/index.es.js",
24
24
  "dependencies": {
25
- "inversify": "6.0.3",
26
- "vue": "3.5.12",
27
- "vue-i18n": "10.0.4",
28
- "vuetify": "3.7.3",
29
- "@xwiki/cristal-api": "0.12.0",
30
- "@xwiki/cristal-dsapi": "0.12.0",
31
- "@xwiki/cristal-navigation-tree-api": "0.12.0",
32
- "@xwiki/cristal-document-api": "0.12.0"
25
+ "inversify": "6.2.1",
26
+ "vue": "3.5.13",
27
+ "vue-i18n": "11.0.1",
28
+ "vuetify": "3.7.6",
29
+ "@xwiki/cristal-api": "0.14.0",
30
+ "@xwiki/cristal-model-api": "0.14.0",
31
+ "@xwiki/cristal-document-api": "0.14.0",
32
+ "@xwiki/cristal-navigation-tree-api": "0.14.0"
33
+ },
34
+ "peerDependencies": {
35
+ "reflect-metadata": "0.2.2"
33
36
  },
34
37
  "devDependencies": {
35
38
  "@vue/test-utils": "2.4.6",
36
- "vue-tsc": "2.1.10",
37
- "@xwiki/cristal-dev-config": "0.12.0"
39
+ "reflect-metadata": "0.2.2",
40
+ "typescript": "5.7.3",
41
+ "vite": "6.0.11",
42
+ "vue-tsc": "2.2.0",
43
+ "@xwiki/cristal-dev-config": "0.14.0",
44
+ "@xwiki/cristal-dsapi": "0.14.0"
38
45
  },
39
46
  "scripts": {
40
47
  "build": "pnpm compile",
@@ -33,6 +33,8 @@ import type { App } from "vue";
33
33
 
34
34
  @injectable()
35
35
  export class VuetifyDesignSystemLoader implements DesignSystemLoader {
36
+ // TODO: reduce the number of statements in the following method and reactivate the disabled eslint rule.
37
+ // eslint-disable-next-line max-statements
36
38
  loadDesignSystem(app: App): void {
37
39
  /*
38
40
  // Manuel importing to reduce build size
@@ -139,5 +141,10 @@ export class VuetifyDesignSystemLoader implements DesignSystemLoader {
139
141
  "XNavigationTree",
140
142
  () => import("../vue/x-navigation-tree.vue"),
141
143
  );
144
+ registerAsyncComponent(
145
+ app,
146
+ "XCheckbox",
147
+ () => import("../vue/form/x-checkbox.vue"),
148
+ );
142
149
  }
143
150
  }
@@ -25,6 +25,10 @@
25
25
  height: 64px;
26
26
  }
27
27
 
28
+ main ul {
29
+ padding-inline-start: var(--cr-spacing-x-large);
30
+ }
31
+
28
32
  :root {
29
33
  --cr-sizes-max-page-width: 960px; /*This value controls the width of the main content. It should be 100% for spanning text along the whole width or an arbitraty value*/
30
34
  --cr-sizes-main-sidebar-width: 15%; /*The default width of the main sidebar*/
@@ -32,12 +36,15 @@
32
36
  --cr-sizes-collapsed-main-sidebar-width: 32px; /*The width of the bar that gets shown containing the menu button to reopen the standard main main sidebar*/
33
37
  --cr-base-font-size: 16px;
34
38
  --cr-base-text-color: var(--cr-color-gray-950);
35
-
39
+ --cr-base-link-color: var(--cr-color-sky-700);
40
+ --cr-base-visited-link-color: var(--cr-color-blue-700);
36
41
 
37
42
  /*
38
43
  * Typography
39
44
  */
40
45
 
46
+
47
+
41
48
  /* Fonts */
42
49
  --cr-font-mono: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace;
43
50
  --cr-font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif,
@@ -82,9 +89,9 @@
82
89
  --cr-focus-ring: var(--cr-focus-ring-style) var(--cr-focus-ring-width) var(--cr-focus-ring-color);
83
90
  --cr-focus-ring-offset: 1px;
84
91
 
85
- /*
86
- * Color Primitives
87
- */
92
+ /*
93
+ * Color Primitives
94
+ */
88
95
 
89
96
  /* Gray */
90
97
  --cr-color-gray-50: hsl(0 0% 97.5%);
@@ -535,6 +542,12 @@
535
542
  --cr-panel-border-color: var(--cr-color-neutral-200);
536
543
  --cr-panel-border-width: 1px;
537
544
 
545
+ /*
546
+ * Header
547
+ */
548
+
549
+ --cr-panel-background-color: var(--cr-color-neutral-0);
550
+
538
551
  /*
539
552
  * Tooltips
540
553
  */
@@ -0,0 +1,35 @@
1
+ <!--
2
+ See the LICENSE file distributed with this work for additional
3
+ information regarding copyright ownership.
4
+
5
+ This is free software; you can redistribute it and/or modify it
6
+ under the terms of the GNU Lesser General Public License as
7
+ published by the Free Software Foundation; either version 2.1 of
8
+ the License, or (at your option) any later version.
9
+
10
+ This software is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ Lesser General Public License for more details.
14
+
15
+ You should have received a copy of the GNU Lesser General Public
16
+ License along with this software; if not, write to the Free
17
+ Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18
+ 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19
+ -->
20
+ <script lang="ts" setup>
21
+ import { VCheckbox } from "vuetify/components/VCheckbox";
22
+ import type { CheckboxProps } from "@xwiki/cristal-dsapi";
23
+
24
+ defineProps<CheckboxProps>();
25
+
26
+ const checked = defineModel<boolean>();
27
+ </script>
28
+ <template>
29
+ <v-checkbox
30
+ v-model="checked"
31
+ :label="label"
32
+ :messages="help"
33
+ density="compact"
34
+ ></v-checkbox>
35
+ </template>
@@ -3,13 +3,13 @@ import messages from "../../translations";
3
3
  import { computed, defineModel } from "vue";
4
4
  import { useI18n } from "vue-i18n";
5
5
  import { VFileInput } from "vuetify/components/VFileInput";
6
- import type { TextFieldModel, TextFieldProps } from "@xwiki/cristal-dsapi";
6
+ import type { FileInputModel, TextFieldProps } from "@xwiki/cristal-dsapi";
7
7
 
8
8
  const { t } = useI18n({
9
9
  messages,
10
10
  });
11
11
 
12
- const model = defineModel<TextFieldModel>();
12
+ const model = defineModel<FileInputModel>();
13
13
  const props = defineProps<TextFieldProps>();
14
14
 
15
15
  const rules = computed(() => {
@@ -9,6 +9,7 @@ const { t } = useI18n({
9
9
  messages,
10
10
  });
11
11
  const props = defineProps<TextFieldProps>();
12
+ const input = defineModel<string>();
12
13
 
13
14
  const rules = computed(() => {
14
15
  const rulesList = [];
@@ -26,7 +27,13 @@ const rules = computed(() => {
26
27
  </script>
27
28
 
28
29
  <template>
29
- <v-text-field :label="label" :name="name" :rules="rules"></v-text-field>
30
+ <v-text-field
31
+ v-model="input"
32
+ :label="label"
33
+ :name="name"
34
+ :autofocus="autofocus"
35
+ :rules="rules"
36
+ ></v-text-field>
30
37
  </template>
31
38
 
32
39
  <style scoped></style>
@@ -32,6 +32,16 @@ defineProps<AlertProps>();
32
32
  @click="action.callback"
33
33
  >{{ action.name }}</x-btn
34
34
  >
35
+ <br v-if="details" />
36
+ <small v-if="details">{{ details }}</small>
35
37
  <slot />
36
38
  </v-alert>
37
39
  </template>
40
+
41
+ <style scoped>
42
+ :deep(.v-alert__content a.router-link-active),
43
+ :deep(.v-alert__content a.router-link-active:visited) {
44
+ color: inherit;
45
+ text-decoration-color: inherit;
46
+ }
47
+ </style>
@@ -17,14 +17,14 @@ License along with this software; if not, write to the Free
17
17
  Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18
18
  02110-1301 USA, or see the FSF site: http://www.fsf.org.
19
19
  -->
20
+ <script lang="ts" setup>
21
+ import type { AvatarProps } from "@xwiki/cristal-dsapi";
22
+
23
+ defineProps<AvatarProps>();
24
+ </script>
20
25
  <template>
21
- <v-avatar :image="image" :size="size"></v-avatar>
26
+ <v-avatar :size="size">
27
+ <v-img v-if="image" :alt="name" :src="image"></v-img>
28
+ <span v-else>{{ name }}</span>
29
+ </v-avatar>
22
30
  </template>
23
- <script lang="ts">
24
- export default {
25
- props: {
26
- image: { type: String, required: true },
27
- size: { type: String, required: true },
28
- },
29
- };
30
- </script>
@@ -22,12 +22,10 @@ defineProps<{
22
22
  title: string;
23
23
  width: string | number | undefined;
24
24
  }>();
25
-
26
- // const innerIsActive = ref(props.isActive);
27
25
  </script>
28
26
 
29
27
  <template>
30
- <v-dialog :width="width">
28
+ <v-dialog :width="width" scrollable>
31
29
  <template #activator="{ props }">
32
30
  <span v-bind="props">
33
31
  <slot name="activator" />
@@ -38,7 +36,16 @@ defineProps<{
38
36
  <v-card-text>
39
37
  <slot name="default" />
40
38
  </v-card-text>
39
+ <v-card-actions v-if="$slots.footer">
40
+ <slot name="footer" />
41
+ </v-card-actions>
41
42
  </v-card>
42
43
  </template>
43
44
  </v-dialog>
44
45
  </template>
46
+
47
+ <style scoped>
48
+ :deep(.v-card-actions) {
49
+ padding: var(--cr-spacing-large);
50
+ }
51
+ </style>
@@ -33,6 +33,7 @@ import { inject, onBeforeMount, ref, watch } from "vue";
33
33
  import { VTreeview } from "vuetify/labs/VTreeview";
34
34
  import type { CristalApp, PageData } from "@xwiki/cristal-api";
35
35
  import type { DocumentService } from "@xwiki/cristal-document-api";
36
+ import type { SpaceReference } from "@xwiki/cristal-model-api";
36
37
  import type {
37
38
  NavigationTreeNode,
38
39
  NavigationTreeSource,
@@ -45,7 +46,7 @@ type TreeItem = {
45
46
  title: string;
46
47
  href: string;
47
48
  children?: Array<TreeItem>;
48
- _location: string;
49
+ _location: SpaceReference;
49
50
  };
50
51
 
51
52
  type OnClickAction = (node: NavigationTreeNode) => void;
@@ -60,10 +61,10 @@ const treeSource: NavigationTreeSource = cristal
60
61
  .get();
61
62
 
62
63
  const rootNodes: Ref<Array<TreeItem>> = ref([]);
63
- const tree: Ref<VTreeview | undefined> = ref(undefined);
64
64
 
65
65
  const activatedNodes: Ref<Array<string>> = ref(new Array<string>());
66
66
  const expandedNodes: Ref<Array<string>> = ref(new Array<string>());
67
+ var isExpanding: boolean = false;
67
68
 
68
69
  const props = defineProps<{
69
70
  clickAction?: OnClickAction;
@@ -80,59 +81,56 @@ onBeforeMount(async () => {
80
81
  _location: node.location,
81
82
  });
82
83
  }
83
- if (props.currentPage !== undefined) {
84
- await expandTree();
85
- }
84
+ await expandTree();
86
85
 
87
86
  documentService.registerDocumentChangeListener("delete", onDocumentDelete);
88
87
  documentService.registerDocumentChangeListener("update", onDocumentUpdate);
89
88
  });
90
89
 
91
- watch(
92
- () => props.currentPage,
93
- async () => {
94
- if (props.currentPage) {
95
- await expandTree();
96
- }
97
- },
98
- );
90
+ watch(() => props.currentPage, expandTree);
99
91
 
92
+ // TODO: reduce the number of statements in the following method and reactivate the disabled eslint rule.
93
+ // eslint-disable-next-line max-statements
100
94
  async function expandTree() {
101
- const newExpandedNodes = treeSource.getParentNodesId(props.currentPage);
102
- let i;
103
- let currentNodes = rootNodes.value;
104
- for (i = 0; i < newExpandedNodes.length - 1; i++) {
105
- if (currentNodes) {
106
- for (const node of currentNodes) {
107
- if (node.id == newExpandedNodes[i]) {
108
- if (node.children?.length == 0) {
109
- await lazyLoadChildren(node);
110
- }
111
- if (!expandedNodes.value.includes(node.id)) {
112
- expandedNodes.value.push(node.id);
95
+ if (props.currentPage && !isExpanding) {
96
+ isExpanding = true;
97
+ const newExpandedNodes = treeSource.getParentNodesId(props.currentPage);
98
+ let i;
99
+ let currentNodes = rootNodes.value;
100
+ for (i = 0; i < newExpandedNodes.length - 1; i++) {
101
+ if (currentNodes) {
102
+ for (const node of currentNodes) {
103
+ if (node.id == newExpandedNodes[i]) {
104
+ if (node.children?.length == 0) {
105
+ await lazyLoadChildren(node);
106
+ }
107
+ if (!expandedNodes.value.includes(node.id)) {
108
+ expandedNodes.value.push(node.id);
109
+ }
110
+ currentNodes = node.children!;
113
111
  }
114
- currentNodes = node.children!;
115
112
  }
116
113
  }
117
114
  }
118
- }
119
- if (currentNodes) {
120
- for (const node of currentNodes) {
121
- if (node.id == newExpandedNodes[i]) {
122
- activatedNodes.value = [node.id];
123
- // If we have a custom click action, we want to use it on dynamic
124
- // selection.
125
- if (props.clickAction) {
126
- props.clickAction({
127
- id: node.id,
128
- label: node.title,
129
- location: node._location,
130
- url: node.href,
131
- has_children: node.children !== undefined,
132
- });
115
+ if (currentNodes) {
116
+ for (const node of currentNodes) {
117
+ if (node.id == newExpandedNodes[i]) {
118
+ activatedNodes.value = [node.id];
119
+ // If we have a custom click action, we want to use it on dynamic
120
+ // selection.
121
+ if (props.clickAction) {
122
+ props.clickAction({
123
+ id: node.id,
124
+ label: node.title,
125
+ location: node._location,
126
+ url: node.href,
127
+ has_children: node.children !== undefined,
128
+ });
129
+ }
133
130
  }
134
131
  }
135
132
  }
133
+ isExpanding = false;
136
134
  }
137
135
  }
138
136
 
@@ -163,31 +161,45 @@ function clearSelection() {
163
161
  }
164
162
  }
165
163
 
164
+ // TODO: reduce the number of statements in the following method and reactivate the disabled eslint rule.
165
+ // eslint-disable-next-line max-statements
166
166
  async function onDocumentDelete(page: PageData) {
167
167
  const parents = treeSource.getParentNodesId(page);
168
- let currentItems: TreeItem[] | undefined = rootNodes.value;
169
- while (currentItems) {
170
- for (const i of currentItems.keys()) {
171
- if (currentItems[i].id == parents[0]) {
168
+ let currentItem: TreeItem | undefined = undefined;
169
+ let currentItemChildren: TreeItem[] | undefined = rootNodes.value;
170
+ let notFound = false;
171
+
172
+ currentItemsLoop: while (currentItemChildren && !notFound) {
173
+ for (const i of currentItemChildren.keys()) {
174
+ if (currentItemChildren[i].id == parents[0]) {
172
175
  if (parents.length == 1) {
173
- currentItems.splice(i, 1);
176
+ currentItemChildren.splice(i, 1);
177
+ if (currentItem && currentItemChildren.length == 0) {
178
+ currentItem!.children = undefined;
179
+ }
174
180
  return;
175
181
  } else {
176
- currentItems = currentItems[i].children;
182
+ currentItem = currentItemChildren[i];
183
+ currentItemChildren = currentItem.children;
177
184
  parents.shift();
178
- break;
185
+ continue currentItemsLoop;
179
186
  }
180
187
  }
181
188
  }
189
+ notFound = true;
182
190
  }
183
191
  }
184
192
 
193
+ // TODO: reduce the number of statements in the following method and reactivate the disabled eslint rule.
194
+ // eslint-disable-next-line max-statements
185
195
  async function onDocumentUpdate(page: PageData) {
186
196
  const parents = treeSource.getParentNodesId(page);
187
197
  let currentParent: string | undefined = undefined;
188
198
  let currentItems: TreeItem[] | undefined = rootNodes.value;
199
+ let notFound = false;
200
+ let isRoot = true;
189
201
 
190
- while (currentItems) {
202
+ currentItemsLoop: while (currentItems && !notFound) {
191
203
  for (const i of currentItems.keys()) {
192
204
  if (currentItems[i].id == parents[0]) {
193
205
  if (parents.length == 1) {
@@ -203,30 +215,49 @@ async function onDocumentUpdate(page: PageData) {
203
215
  }
204
216
  } else {
205
217
  currentParent = currentItems[i].id;
218
+ if (!currentItems[i].children) {
219
+ // This node had no children, we reset this so new ones can be
220
+ // loaded lazily.
221
+ currentItems[i].children = [];
222
+ }
206
223
  currentItems = currentItems[i].children;
207
224
  parents.shift();
208
- break;
225
+ isRoot = false;
226
+ continue currentItemsLoop;
209
227
  }
210
228
  }
211
229
  }
230
+ notFound = true;
231
+ }
212
232
 
213
- // New page
214
- const newItems = await treeSource.getChildNodes(
215
- currentParent ? currentParent : "",
216
- );
217
- newItemsLoop: for (const newItem of newItems) {
218
- for (const i of currentItems!.keys()) {
219
- if (newItem.id == currentItems![i].id) {
220
- continue newItemsLoop;
221
- }
233
+ // New page
234
+ if (currentItems.length == 0 && !isRoot) {
235
+ // We don't do anything because this node will be loaded lazily anyway.
236
+ return;
237
+ }
238
+ const newItems = await treeSource.getChildNodes(
239
+ currentParent ? currentParent : "",
240
+ );
241
+ const currentPageParents = treeSource.getParentNodesId(props.currentPage);
242
+ newItemsLoop: for (const newItem of newItems) {
243
+ for (const i of currentItems!.keys()) {
244
+ if (newItem.id == currentItems![i].id) {
245
+ continue newItemsLoop;
222
246
  }
223
- currentItems!.push({
224
- id: newItem.id,
225
- title: newItem.label,
226
- href: newItem.url,
227
- children: newItem.has_children ? [] : undefined,
228
- _location: newItem.location,
229
- });
247
+ }
248
+ currentItems!.push({
249
+ id: newItem.id,
250
+ title: newItem.label,
251
+ href: newItem.url,
252
+ children: newItem.has_children ? [] : undefined,
253
+ _location: newItem.location,
254
+ });
255
+ if (
256
+ currentPageParents.length > 0 &&
257
+ currentPageParents[currentPageParents.length - 1] === newItem.id
258
+ ) {
259
+ // If we add a node, and it's the current page, it should be activated.
260
+ activatedNodes.value = [newItem.id];
230
261
  }
231
262
  }
232
263
  }
@@ -234,7 +265,6 @@ async function onDocumentUpdate(page: PageData) {
234
265
 
235
266
  <template>
236
267
  <v-treeview
237
- ref="tree"
238
268
  v-model:opened="expandedNodes"
239
269
  density="compact"
240
270
  :activated="activatedNodes"
@@ -243,7 +273,6 @@ async function onDocumentUpdate(page: PageData) {
243
273
  activatable
244
274
  active-strategy="independent"
245
275
  item-value="id"
246
- open-strategy="multiple"
247
276
  @update:activated="clearSelection"
248
277
  >
249
278
  <template #title="{ item }: { item: any }">
@@ -283,4 +312,16 @@ async function onDocumentUpdate(page: PageData) {
283
312
  :deep(.v-list-item--link) {
284
313
  cursor: default;
285
314
  }
315
+ /*TODO: This section needs to be removed when the oficial fix is released on Vuetify
316
+ https://github.com/vuetifyjs/vuetify/issues/20421#event-16001533054
317
+ START FIX
318
+ */
319
+ :deep(.v-treeview-item:not(.v-list-group__header)) {
320
+ padding-inline-start: calc(39px + 6px) !important;
321
+ }
322
+
323
+ :deep(.v-treeview-item .v-list-item__spacer) {
324
+ width: 4px !important;
325
+ }
326
+ /*END FIX*/
286
327
  </style>
@@ -21,11 +21,11 @@ Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
21
21
  import { ref } from "vue";
22
22
  const tab = ref(null);
23
23
 
24
- const emits = defineEmits(["change"]);
24
+ const emits = defineEmits(["tab-change"]);
25
25
 
26
26
  function change(tabId: unknown) {
27
27
  if (typeof tabId == "string") {
28
- emits("change", tabId);
28
+ emits("tab-change", tabId);
29
29
  }
30
30
  }
31
31
  </script>
@@ -26,5 +26,3 @@ defineProps<TabPanelProps>();
26
26
  <template>
27
27
  <v-tabs-window-item :value="tabId"><slot></slot></v-tabs-window-item>
28
28
  </template>
29
-
30
- <style scoped></style>
package/tsconfig.json CHANGED
@@ -1,10 +1,16 @@
1
1
  {
2
- "compilerOptions": {
3
- "rootDir" : "src",
4
- "outDir" : "dist",
5
- "isolatedDeclarations": false
6
- },
7
- "extends": "./../../tsconfig.json",
8
- "include": ["./src/index.ts", "./src/**/*"],
9
- "exclude": ["**/*.spec.ts", "**/*.test.ts"]
2
+ "compilerOptions": {
3
+ "rootDir": "src",
4
+ "outDir": "dist",
5
+ "isolatedDeclarations": false
6
+ },
7
+ "extends": "./../../tsconfig.json",
8
+ "include": [
9
+ "./src/index.ts",
10
+ "./src/**/*"
11
+ ],
12
+ "exclude": [
13
+ "**/*.spec.ts",
14
+ "**/*.test.ts"
15
+ ]
10
16
  }