@windwalker-io/unicorn-next 0.1.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 (175) hide show
  1. package/.editorconfig +18 -0
  2. package/.gulp.json +7 -0
  3. package/bin/release.mjs +47 -0
  4. package/dist/chunks/_arrayPush.js +168 -0
  5. package/dist/chunks/_arrayPush.js.map +1 -0
  6. package/dist/chunks/_baseRest.js +73 -0
  7. package/dist/chunks/_baseRest.js.map +1 -0
  8. package/dist/chunks/_commonjsHelpers.js +7 -0
  9. package/dist/chunks/_commonjsHelpers.js.map +1 -0
  10. package/dist/chunks/_getPrototype.js +130 -0
  11. package/dist/chunks/_getPrototype.js.map +1 -0
  12. package/dist/chunks/button-radio.js +147 -0
  13. package/dist/chunks/button-radio.js.map +1 -0
  14. package/dist/chunks/checkboxes-multi-select.js +44 -0
  15. package/dist/chunks/checkboxes-multi-select.js.map +1 -0
  16. package/dist/chunks/cloneDeep.js +287 -0
  17. package/dist/chunks/cloneDeep.js.map +1 -0
  18. package/dist/chunks/cropper.min.js +5 -0
  19. package/dist/chunks/cropper.min.js.map +1 -0
  20. package/dist/chunks/field-cascade-select.js +256 -0
  21. package/dist/chunks/field-cascade-select.js.map +1 -0
  22. package/dist/chunks/field-file-drag.js +218 -0
  23. package/dist/chunks/field-file-drag.js.map +1 -0
  24. package/dist/chunks/field-flatpickr.js +893 -0
  25. package/dist/chunks/field-flatpickr.js.map +1 -0
  26. package/dist/chunks/field-modal-select.js +403 -0
  27. package/dist/chunks/field-modal-select.js.map +1 -0
  28. package/dist/chunks/field-modal-tree.js +790 -0
  29. package/dist/chunks/field-modal-tree.js.map +1 -0
  30. package/dist/chunks/field-multi-uploader.js +256 -0
  31. package/dist/chunks/field-multi-uploader.js.map +1 -0
  32. package/dist/chunks/field-repeatable.js +132 -0
  33. package/dist/chunks/field-repeatable.js.map +1 -0
  34. package/dist/chunks/field-single-image-drag.js +338 -0
  35. package/dist/chunks/field-single-image-drag.js.map +1 -0
  36. package/dist/chunks/form.js +154 -0
  37. package/dist/chunks/form.js.map +1 -0
  38. package/dist/chunks/grid.js +345 -0
  39. package/dist/chunks/grid.js.map +1 -0
  40. package/dist/chunks/http-client.js +229 -0
  41. package/dist/chunks/http-client.js.map +1 -0
  42. package/dist/chunks/iframe-modal.js +124 -0
  43. package/dist/chunks/iframe-modal.js.map +1 -0
  44. package/dist/chunks/index.js +309 -0
  45. package/dist/chunks/index.js.map +1 -0
  46. package/dist/chunks/isArguments.js +146 -0
  47. package/dist/chunks/isArguments.js.map +1 -0
  48. package/dist/chunks/keep-tab.js +101 -0
  49. package/dist/chunks/keep-tab.js.map +1 -0
  50. package/dist/chunks/legacy.js +210 -0
  51. package/dist/chunks/legacy.js.map +1 -0
  52. package/dist/chunks/list-dependent.js +231 -0
  53. package/dist/chunks/list-dependent.js.map +1 -0
  54. package/dist/chunks/s3-multipart-uploader.js +172 -0
  55. package/dist/chunks/s3-multipart-uploader.js.map +1 -0
  56. package/dist/chunks/s3-uploader.js +136 -0
  57. package/dist/chunks/s3-uploader.js.map +1 -0
  58. package/dist/chunks/show-on.js +237 -0
  59. package/dist/chunks/show-on.js.map +1 -0
  60. package/dist/chunks/tinymce.js +196 -0
  61. package/dist/chunks/tinymce.js.map +1 -0
  62. package/dist/chunks/ui-bootstrap5.js +71 -0
  63. package/dist/chunks/ui-bootstrap5.js.map +1 -0
  64. package/dist/chunks/unicorn.js +2202 -0
  65. package/dist/chunks/unicorn.js.map +1 -0
  66. package/dist/chunks/validation.js +854 -0
  67. package/dist/chunks/validation.js.map +1 -0
  68. package/dist/editor.css +1 -0
  69. package/dist/index.d.ts +1427 -0
  70. package/dist/multi-level-menu.css +1 -0
  71. package/dist/switcher.css +1 -0
  72. package/dist/unicorn-next.css +12 -0
  73. package/dist/unicorn.js +125 -0
  74. package/dist/unicorn.js.map +1 -0
  75. package/fusionfile.mjs +155 -0
  76. package/images/ajax-loader.gif +0 -0
  77. package/images/placeholder/avatar.png +0 -0
  78. package/images/placeholder/image-16x10.png +0 -0
  79. package/images/placeholder/image-16x9.png +0 -0
  80. package/images/placeholder/image-1x1.png +0 -0
  81. package/images/placeholder/image-4x3.png +0 -0
  82. package/package.json +102 -0
  83. package/scss/bootstrap/multi-level-menu.scss +121 -0
  84. package/scss/editor.scss +116 -0
  85. package/scss/field/file-drag.scss +102 -0
  86. package/scss/field/single-image-drag.scss +88 -0
  87. package/scss/field/vue-drag-uploader.scss +160 -0
  88. package/scss/switcher.scss +156 -0
  89. package/src/app.ts +128 -0
  90. package/src/bootstrap/button-radio.ts +208 -0
  91. package/src/bootstrap/keep-tab.ts +155 -0
  92. package/src/composable/index.ts +21 -0
  93. package/src/composable/useCheckboxesMultiSelect.ts +22 -0
  94. package/src/composable/useFieldCascadeSelect.ts +9 -0
  95. package/src/composable/useFieldFileDrag.ts +9 -0
  96. package/src/composable/useFieldFlatpickr.ts +3 -0
  97. package/src/composable/useFieldModalSelect.ts +6 -0
  98. package/src/composable/useFieldModalTree.ts +3 -0
  99. package/src/composable/useFieldMultiUploader.ts +3 -0
  100. package/src/composable/useFieldRepeatable.ts +9 -0
  101. package/src/composable/useFieldSingleImageDrag.ts +5 -0
  102. package/src/composable/useForm.ts +43 -0
  103. package/src/composable/useGrid.ts +57 -0
  104. package/src/composable/useHttp.ts +8 -0
  105. package/src/composable/useIframeModal.ts +9 -0
  106. package/src/composable/useListDependent.ts +26 -0
  107. package/src/composable/useQueue.ts +13 -0
  108. package/src/composable/useS3Uploader.ts +32 -0
  109. package/src/composable/useShowOn.ts +9 -0
  110. package/src/composable/useStack.ts +13 -0
  111. package/src/composable/useTinymce.ts +29 -0
  112. package/src/composable/useTomSelect.ts +72 -0
  113. package/src/composable/useUIBootstrap5.ts +48 -0
  114. package/src/composable/useUniDirective.ts +32 -0
  115. package/src/composable/useValidation.ts +39 -0
  116. package/src/data.ts +36 -0
  117. package/src/events.ts +73 -0
  118. package/src/legacy/legacy.ts +186 -0
  119. package/src/legacy/loader.ts +125 -0
  120. package/src/module/checkboxes-multi-select.ts +54 -0
  121. package/src/module/field-cascade-select.ts +292 -0
  122. package/src/module/field-file-drag.ts +292 -0
  123. package/src/module/field-flatpickr.ts +127 -0
  124. package/src/module/field-modal-select.ts +174 -0
  125. package/src/module/field-modal-tree.ts +27 -0
  126. package/src/module/field-multi-uploader.ts +361 -0
  127. package/src/module/field-repeatable.ts +202 -0
  128. package/src/module/field-single-image-drag.ts +468 -0
  129. package/src/module/form.ts +223 -0
  130. package/src/module/grid.ts +465 -0
  131. package/src/module/http-client.ts +243 -0
  132. package/src/module/iframe-modal.ts +167 -0
  133. package/src/module/list-dependent.ts +321 -0
  134. package/src/module/s3-multipart-uploader.ts +300 -0
  135. package/src/module/s3-uploader.ts +234 -0
  136. package/src/module/show-on.ts +173 -0
  137. package/src/module/tinymce.ts +263 -0
  138. package/src/module/ui-bootstrap5.ts +107 -0
  139. package/src/module/validation.ts +1019 -0
  140. package/src/plugin/index.ts +1 -0
  141. package/src/plugin/php-adapter.ts +65 -0
  142. package/src/polyfill/form-request-submit.ts +31 -0
  143. package/src/polyfill/index.ts +9 -0
  144. package/src/service/animate.ts +58 -0
  145. package/src/service/crypto.ts +27 -0
  146. package/src/service/dom-watcher.ts +62 -0
  147. package/src/service/dom.ts +265 -0
  148. package/src/service/helper.ts +48 -0
  149. package/src/service/index.ts +10 -0
  150. package/src/service/lang.ts +122 -0
  151. package/src/service/loader.ts +152 -0
  152. package/src/service/router.ts +118 -0
  153. package/src/service/ui.ts +497 -0
  154. package/src/service/uri.ts +106 -0
  155. package/src/types/base.ts +9 -0
  156. package/src/types/index.ts +4 -0
  157. package/src/types/modal-tree.ts +12 -0
  158. package/src/types/plugin.ts +6 -0
  159. package/src/types/shims.d.ts +18 -0
  160. package/src/types/ui.ts +6 -0
  161. package/src/unicorn.ts +63 -0
  162. package/src/utilities/arr.ts +25 -0
  163. package/src/utilities/base.ts +9 -0
  164. package/src/utilities/data.ts +48 -0
  165. package/src/utilities/index.ts +5 -0
  166. package/src/utilities/tree.ts +20 -0
  167. package/src/vue/components/ModalTree/ModalTreeApp.vue +175 -0
  168. package/src/vue/components/ModalTree/TreeItem.vue +262 -0
  169. package/src/vue/components/ModalTree/TreeModal.vue +225 -0
  170. package/tests/test.js +4 -0
  171. package/tsconfig.js.json +25 -0
  172. package/tsconfig.json +17 -0
  173. package/vite.assets.config.ts +61 -0
  174. package/vite.config.test.ts +36 -0
  175. package/vite.config.ts +112 -0
package/src/unicorn.ts ADDED
@@ -0,0 +1,63 @@
1
+ import { InjectionKey, UnicornApp } from './app';
2
+ import { polyfill } from './polyfill';
3
+ import { removeCloak } from './utilities';
4
+
5
+ export * from './data';
6
+ export * from './events';
7
+ export * from './service';
8
+ export * from './composable';
9
+ export * from './plugin';
10
+
11
+ export type { UnicornApp };
12
+
13
+ let app: UnicornApp;
14
+
15
+ export function createUnicorn(): UnicornApp {
16
+ polyfill();
17
+ removeCloak();
18
+
19
+ return app = new UnicornApp();
20
+ }
21
+
22
+ export function createUnicornWithPlugins(): UnicornApp {
23
+ const app = createUnicorn();
24
+
25
+ // app.use(UnicornUI);
26
+
27
+ // app.use(UnicornDom);
28
+
29
+ return app;
30
+ }
31
+
32
+ export function useUnicorn(instance?: UnicornApp): UnicornApp {
33
+ if (instance) {
34
+ app = instance;
35
+ }
36
+
37
+ return app ??= createUnicorn();
38
+ }
39
+
40
+ export const useInject: typeof UnicornApp.prototype.inject = <T = any>(id: InjectionKey<T>, def?: T): T => {
41
+ return useUnicorn().inject<T>(id, def);
42
+ }
43
+
44
+ export function pushUnicornToGlobal(app?: UnicornApp) {
45
+ // @ts-ignore
46
+ window.u = app ?? useUnicorn();
47
+ }
48
+
49
+ export function useMacro(name: string, handler: (...args: any[]) => any) {
50
+ useUnicorn().macro(name, handler);
51
+ }
52
+
53
+ export async function useLegacy(app?: UnicornApp) {
54
+ app ??= useUnicorn();
55
+
56
+ pushUnicornToGlobal(app);
57
+
58
+ const { useLegacyMethods } = await import('./legacy/legacy');
59
+
60
+ await useLegacyMethods(app);
61
+
62
+ return app;
63
+ }
@@ -0,0 +1,25 @@
1
+
2
+ export function isPlainObject(val: any): val is Record<string, any> {
3
+ return val && typeof val === "object" && !Array.isArray(val);
4
+ }
5
+
6
+ export function mergeDeep<T = Record<string, any>>(target: Partial<T>, ...sources: any[]): T {
7
+ let out: any = isPlainObject(target) ? { ...target } : target;
8
+
9
+ for (const source of sources) {
10
+ if (Array.isArray(source)) {
11
+ out = (Array.isArray(out) ? out.concat(source) : source) as T;
12
+ continue;
13
+ }
14
+ if (isPlainObject(source)) {
15
+ out = { ...(isPlainObject(out) ? out : {}) };
16
+ for (const key of Object.keys(source)) {
17
+ out[key] =
18
+ key in out ? mergeDeep(out[key], source[key]) : source[key];
19
+ }
20
+ continue;
21
+ }
22
+ out = source as T;
23
+ }
24
+ return out;
25
+ }
@@ -0,0 +1,9 @@
1
+ import { selectAll } from '../service';
2
+
3
+ export function removeCloak() {
4
+ if (globalThis.document == null) {
5
+ return;
6
+ }
7
+
8
+ selectAll('[uni-cloak]', (el) => el.removeAttribute('uni-cloak'));
9
+ }
@@ -0,0 +1,48 @@
1
+
2
+ export function getData(element: Element, name: string | undefined = undefined) {
3
+ prepareData(element);
4
+
5
+ if (name === undefined) {
6
+ return element.__unicorn;
7
+ }
8
+
9
+ return element.__unicorn[name];
10
+ }
11
+
12
+ export function setData(element: Element, name: string, value: any) {
13
+ prepareData(element);
14
+ element.__unicorn[name] = value;
15
+ }
16
+
17
+ export function defData(element: Element, name: string, defCallback: Function) {
18
+ prepareData(element);
19
+ element.__unicorn[name] = element.__unicorn[name] || defCallback(element);
20
+
21
+ return element.__unicorn[name];
22
+ }
23
+
24
+ export function removeData(element: Element, name: string) {
25
+ prepareData(element);
26
+
27
+ const v = element.__unicorn[name];
28
+ delete element.__unicorn[name];
29
+
30
+ return v;
31
+ }
32
+
33
+ export function prepareData<T extends Node>(element: T): T {
34
+ if (!element) {
35
+ return element;
36
+ }
37
+
38
+ element.__unicorn = element.__unicorn || {};
39
+ return element;
40
+ }
41
+
42
+ declare global {
43
+ interface Node {
44
+ __unicorn?: any;
45
+ }
46
+ }
47
+
48
+
@@ -0,0 +1,5 @@
1
+ export * from './arr';
2
+ export * from './data';
3
+ export * from './base';
4
+ export * from './tree';
5
+
@@ -0,0 +1,20 @@
1
+ import { TreeNode } from '../types';
2
+
3
+ export function flattenChildren(children: TreeNode[]) {
4
+ const flat: TreeNode[] = [];
5
+
6
+ function loopChildren(children: TreeNode[]) {
7
+ for (const child of children) {
8
+ if (child.children.length === 0) {
9
+ flat.push(child);
10
+ continue;
11
+ }
12
+
13
+ loopChildren(child.children);
14
+ }
15
+ }
16
+
17
+ loopChildren(children);
18
+ return flat;
19
+ }
20
+
@@ -0,0 +1,175 @@
1
+ <script lang="ts" setup>
2
+ import { cloneDeep } from 'lodash-es';
3
+ import { computed, provide, ref, watch } from 'vue';
4
+ import { forceArray } from '../../../service';
5
+ import {
6
+ ValueGetter,
7
+ ModalTreeSource,
8
+ TitleGetter,
9
+ TreeNode,
10
+ SearchMatcher,
11
+ MaybeArray,
12
+ MaybePromise
13
+ } from '../../../types';
14
+ import TreeModal from './TreeModal.vue';
15
+
16
+ const props = withDefaults(
17
+ defineProps<{
18
+ id?: string;
19
+ name?: string;
20
+ title?: string;
21
+ disabled?: boolean;
22
+ readonly?: boolean;
23
+ value?: MaybeArray<string | number>;
24
+ source?: ModalTreeSource;
25
+ items?: MaybeArray<TreeNode> | (() => MaybePromise<MaybeArray<TreeNode>>);
26
+ valueGetter?: ValueGetter;
27
+ titleGetter?: TitleGetter;
28
+ searchMatcher?: SearchMatcher;
29
+ modalTitle?: string;
30
+ vertical?: boolean;
31
+ branchSelectable?: boolean;
32
+ selectAllChildren?: boolean;
33
+ placeholder?: string;
34
+ multiple?: boolean;
35
+ buttonText?: string;
36
+ itemClass?: string;
37
+ searchText?: string;
38
+ }>(),
39
+ {
40
+ branchSelectable: false,
41
+ selectAllChildren: false,
42
+ placeholder: '- No selected -',
43
+ multiple: false,
44
+ buttonText: 'Select',
45
+ itemClass: 'badge bg-primary badge-pill',
46
+ searchText: 'Search',
47
+ valueGetter: (item: any) => item.id,
48
+ titleGetter: (item: any) => item.title,
49
+ }
50
+ );
51
+
52
+ provide('id', props.id);
53
+ provide('name', props.name);
54
+ provide('multiple', props.multiple);
55
+ provide('valueGetter', props.valueGetter);
56
+ provide('titleGetter', props.titleGetter);
57
+ provide('searchMatcher', props.searchMatcher ?? defaultSearchMatcher);
58
+
59
+ function defaultSearchMatcher(item: any, q: string) {
60
+ return props.titleGetter(item).toLowerCase().includes(q.toLowerCase());
61
+ }
62
+
63
+ const selected = ref<TreeNode[]>([]);
64
+ const value = ref<(string|number)[]>(forceArray(props.value));
65
+
66
+ // Modal
67
+ const treeModalOpen = ref(false);
68
+
69
+ function openSelector() {
70
+ treeModalOpen.value = true;
71
+ }
72
+
73
+ function deleteItem(i: number, node: TreeNode) {
74
+ selected.value = selected.value.filter((it: TreeNode) => props.valueGetter(it.value) !== props.valueGetter(node.value));
75
+ }
76
+
77
+ function handleSelected(items: any[]) {
78
+ selected.value = cloneDeep(items);
79
+ }
80
+
81
+ watch(() => props.items, async (v) => {
82
+ if (typeof v === 'function') {
83
+ v = await v();
84
+ }
85
+
86
+ selected.value = forceArray(v).filter((node: TreeNode) => {
87
+ return value.value.includes(props.valueGetter(node.value));
88
+ });
89
+ }, { immediate: true });
90
+
91
+ const selectedValues = computed(() => {
92
+ return selected.value.map(node => props.valueGetter(node.value));
93
+ });
94
+
95
+ const canModify = computed(() => {
96
+ return !props.readonly && !props.disabled;
97
+ });
98
+
99
+ </script>
100
+
101
+ <template>
102
+ <div class="c-modal-tree">
103
+ <div class="c-modal-tree__container p-2 d-flex flex-column"
104
+ :class="[ vertical ? '' : 'flex-md-row' ]">
105
+ <div v-if="canModify" class="me-2 mb-2"
106
+ :class="{ 'mb-md-0': !vertical }">
107
+ <div class="btn-group">
108
+ <button class="btn btn-secondary btn-sm btn-rounded text-nowrap" type="button"
109
+ @click="openSelector">
110
+ {{ buttonText }}
111
+ </button>
112
+ <button class="btn btn-secondary btn-sm btn-rounded" type="button"
113
+ @click="selected = []">
114
+ <span class="fa fa-times"></span>
115
+ </button>
116
+ </div>
117
+ </div>
118
+
119
+ <div v-if="selected.length > 0">
120
+ <TransitionGroup name="fade">
121
+ <span v-for="(node, i) of selected"
122
+ class="me-2 mb-2 c-item"
123
+ :class="itemClass"
124
+ :key="valueGetter(node.value)"
125
+ style="animation-duration: .3s">
126
+ <span>{{ titleGetter(node.value) }}</span>
127
+ <span type="button" v-if="canModify"
128
+ @click.prevent="deleteItem(i, node)" class="ms-2" style="cursor: pointer">
129
+ <span class="fa fa-times"></span>
130
+ </span>
131
+ </span>
132
+ </TransitionGroup>
133
+ </div>
134
+ <div v-else class="text-muted">
135
+ {{ placeholder }}
136
+ </div>
137
+ </div>
138
+
139
+ <select multiple
140
+ style="display: none;"
141
+ ref="input"
142
+ :id="id"
143
+ :name="name"
144
+ :disabled="disabled"
145
+ :readonly="readonly"
146
+ v-bind="$attrs"
147
+ >
148
+ <option v-for="id of selectedValues" :value="id" :selected="true">{{ id }}</option>
149
+ </select>
150
+
151
+ <TreeModal
152
+ :open="treeModalOpen"
153
+ @hide="treeModalOpen = false"
154
+ :id="id"
155
+ :title="modalTitle"
156
+ :source="source"
157
+ :value="selectedValues"
158
+ :branchSelectable
159
+ v-bind="$attrs"
160
+ :disabled="disabled"
161
+ :readonly="readonly"
162
+ :search-text="searchText"
163
+ @selected="handleSelected"
164
+ />
165
+ </div>
166
+ </template>
167
+
168
+ <style scoped>
169
+ .c-item {
170
+ padding-left: .75rem;
171
+ padding-right: .75rem;
172
+ padding-top: .5rem;
173
+ padding-bottom: .5rem;
174
+ }
175
+ </style>
@@ -0,0 +1,262 @@
1
+ <script setup lang="ts">
2
+ import { type ComponentPublicInstance, type ComputedRef, computed, inject, nextTick, onBeforeUpdate, onMounted, ref, watch } from 'vue';
3
+ import { Vue3SlideUpDown } from 'vue3-slide-up-down';
4
+ import { TitleGetter, TreeNode, ValueGetter } from '../../../types';
5
+ import { flattenChildren } from '../../../utilities';
6
+ import TreeItem from './TreeItem.vue';
7
+
8
+ const props = withDefaults(
9
+ defineProps<{
10
+ node: TreeNode;
11
+ level?: number;
12
+ branchSelectable?: boolean;
13
+ }>(),
14
+ {
15
+ level: 1,
16
+ branchSelectable: false,
17
+ }
18
+ );
19
+
20
+ const emit = defineEmits<{
21
+ change: [checked: boolean];
22
+ input: [checked: boolean];
23
+ }>();
24
+
25
+ const node = ref<TreeNode>(props.node);
26
+ // const selectNode = inject<(node: TreeNode, select: boolean) => any>('selectNode');
27
+ const selectedValues = inject<ComputedRef<(string | number)[]>>('selectedValues');
28
+ const id = inject('id');
29
+ const multiple = inject('multiple');
30
+ const valueGetter = inject<ValueGetter>('valueGetter');
31
+ const titleGetter = inject<TitleGetter>('titleGetter');
32
+
33
+ const selected = ref(false);
34
+ const indeterminate = computed(() => !!props.node.indeterminate);
35
+ const stopWatch = ref(false);
36
+ const open = ref(false);
37
+ const childrenComponents = ref<ComponentPublicInstance<typeof TreeItem>[]>([]);
38
+
39
+ watch(() => props.node, () => {
40
+ selected.value = !!props.node.selected;
41
+ }, { deep: true });
42
+
43
+ function setChildrenComponent(child: ComponentPublicInstance<typeof TreeItem>) {
44
+ childrenComponents.value.push(child);
45
+ }
46
+
47
+ onBeforeUpdate(() => {
48
+ childrenComponents.value = [];
49
+ });
50
+
51
+ const indentPx = computed(() => {
52
+ return (props.level - 1) * 15;
53
+ });
54
+
55
+ const isBranch = computed(() => {
56
+ return props.node.children.length > 0;
57
+ });
58
+
59
+ const isLeaf = computed(() => {
60
+ return !isBranch.value;
61
+ });
62
+
63
+ function updateSelected() {
64
+ if (isBranch.value) {
65
+ return;
66
+ }
67
+
68
+ node.value.selected = selectedValues.value.includes(valueGetter(props.node.value));
69
+ }
70
+
71
+ function select(select: boolean) {
72
+ if (selected.value === select) {
73
+ return;
74
+ }
75
+
76
+ node.value.selected = select;
77
+
78
+ checkboxChanged(select);
79
+ }
80
+
81
+ function checkboxChanged(v: boolean) {
82
+ if (isBranch.value) {
83
+ node.value.selected = v;
84
+
85
+ if (multiple) {
86
+ stopWatchThen(() => {
87
+ const flatChildren = flattenChildren(node.value.children);
88
+ for (const child of flatChildren) {
89
+ child.selected = v;
90
+ child.indeterminate = false;
91
+ }
92
+ });
93
+ // syncChildrenStatus();
94
+ }
95
+ } else {
96
+ nextTick(() => {
97
+ node.value.selected = v;
98
+ });
99
+ }
100
+ emit('change', v);
101
+ emit('input', v);
102
+ }
103
+
104
+ function childChanged(v: boolean) {
105
+ if (isLeaf.value || stopWatch.value) {
106
+ return;
107
+ }
108
+ if (!childrenComponents.value) {
109
+ return;
110
+ }
111
+ if (childrenComponents.value.length === 0) {
112
+ return;
113
+ }
114
+ syncChildrenStatus();
115
+ }
116
+
117
+ function syncChildrenStatus() {
118
+ if (isLeaf.value) {
119
+ return;
120
+ }
121
+ let selectedCount = 0;
122
+ let unselectCount = 0;
123
+ let indeterminateInner = 0;
124
+ const oldIndeterminate = indeterminate.value;
125
+ const oldSelected = selected.value;
126
+
127
+ for (const child of flattenChildren(props.node.children)) {
128
+ if (child.selected) {
129
+ selectedCount++;
130
+ } else {
131
+ unselectCount++;
132
+ }
133
+
134
+ if (child.indeterminate) {
135
+ indeterminateInner++;
136
+ }
137
+ }
138
+
139
+ // for (const childComponent of childrenComponents.value) {
140
+ // if (childComponent.selected) {
141
+ // checked++;
142
+ // } else {
143
+ // unchecked++;
144
+ // }
145
+ // if (childComponent.indeterminate) {
146
+ // indeterminateInner++;
147
+ // }
148
+ // }
149
+
150
+ if ((selectedCount !== 0 && unselectCount !== 0) || indeterminateInner > 0) {
151
+ node.value.indeterminate = true;
152
+ } else {
153
+ node.value.selected = unselectCount === 0;
154
+ node.value.indeterminate = false;
155
+ }
156
+
157
+ if (
158
+ selected.value !== oldSelected
159
+ || indeterminate.value !== oldIndeterminate
160
+ ) {
161
+ emit('change', selected.value);
162
+ emit('input', selected.value);
163
+ }
164
+ }
165
+
166
+ function stopWatchThen(callback: () => any) {
167
+ stopWatch.value = true;
168
+ callback();
169
+ stopWatch.value = false;
170
+ }
171
+
172
+ watch(() => selectedValues, async () => {
173
+ if (!isBranch.value) {
174
+ updateSelected();
175
+ }
176
+ await nextTick();
177
+
178
+ syncChildrenStatus();
179
+ }, { deep: true });
180
+
181
+ watch(selected, (v) => {
182
+ });
183
+
184
+ updateSelected();
185
+
186
+ onMounted(() => {
187
+ syncChildrenStatus();
188
+ });
189
+
190
+ defineExpose({
191
+ select,
192
+ selected,
193
+ indeterminate
194
+ });
195
+ </script>
196
+
197
+ <template>
198
+ <div class="c-tree-item"
199
+ :class="[ isBranch ? 'c-tree-item--branch' : 'c-tree-item--leaf' ]">
200
+ <div class="d-flex c-tree-item__title"
201
+ :style="{ 'padding-left': indentPx + 'px' }"
202
+ :class="[ isBranch ? 'bg-light ' : '' ]">
203
+ <div class="p-2 ms-2">
204
+ <input
205
+ :type="multiple ? 'checkbox' : 'radio'"
206
+ class="form-check-input"
207
+ v-if="isLeaf || (branchSelectable && multiple)"
208
+ :id="id + '__item-' + valueGetter(node.value)"
209
+ v-model="selected"
210
+ :value="true"
211
+ :unchecked-value="false"
212
+ :indeterminate.sync="indeterminate"
213
+ @change="checkboxChanged(($event.target as HTMLInputElement).checked)"
214
+ />
215
+ <input v-else
216
+ :type="multiple ? 'checkbox' : 'radio'"
217
+ class="form-check-input"
218
+ disabled
219
+ :checked="indeterminate" :indeterminate.sync="indeterminate" />
220
+ </div>
221
+ <a class="c-tree-item__text d-flex align-items-center flex-grow-1 py-2 text-decoration-none"
222
+ style="cursor: pointer;"
223
+ :data-level="level"
224
+ data-bs-toggle="collapse"
225
+ @click.prevent="isLeaf ? select(!selected) : open = !open">
226
+ <span class="me-2 fa" :class="[ isLeaf ? 'fa-tag' : 'fa-folder' ]"></span>
227
+
228
+ {{ node.value.title }}
229
+
230
+ <span v-if="isBranch" class="ms-auto me-3">
231
+ <span :class="[ open ? 'fa fa-chevron-up' : 'fa fa-chevron-down' ]"></span>
232
+ </span>
233
+ </a>
234
+ </div>
235
+
236
+ <Vue3SlideUpDown
237
+ v-if="node.children.length > 0"
238
+ v-model="open"
239
+ :duration="300"
240
+ class="c-tree-item__children"
241
+ >
242
+ <TreeItem v-for="(child, i) of node.children"
243
+ :node="child"
244
+ :key="valueGetter(child.value)"
245
+ :level="level + 1"
246
+ :branch-selectable="branchSelectable"
247
+ :ref="setChildrenComponent"
248
+ @change="childChanged"
249
+ />
250
+ </Vue3SlideUpDown>
251
+ </div>
252
+ </template>
253
+
254
+ <style scoped lang="scss">
255
+ .c-tree-item {
256
+ &__title {
257
+ border-bottom: 1px solid #ddd;
258
+ }
259
+
260
+ cursor: pointer;
261
+ }
262
+ </style>