@teammodlr/ui-kit 2.1.4 → 2.1.6

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 (64) hide show
  1. package/lib/call.d.ts +7 -0
  2. package/lib/components/confirm/Confirm.vue.d.ts +46 -0
  3. package/lib/components/{dialog/DialogProvider.vue.d.ts → confirm/ConfirmProvider.vue.d.ts} +5 -5
  4. package/lib/components/{dialog → confirm}/props.d.ts +2 -2
  5. package/lib/components/confirm/types.d.ts +27 -0
  6. package/lib/components/confirm/useConfirm.d.ts +2 -0
  7. package/lib/components/dialog/Dialog.vue.d.ts +90 -41
  8. package/lib/components/dialog/style/Dialog.css.d.ts +11 -0
  9. package/lib/components/dialog/style/index.d.ts +1 -0
  10. package/lib/components/dialog/types.d.ts +0 -27
  11. package/lib/components/index.d.ts +3 -1
  12. package/lib/components/select/Select.vue.d.ts +21 -1
  13. package/lib/components/select/style/Select.css.d.ts +6 -0
  14. package/lib/components/tree/Tree.vue.d.ts +103 -0
  15. package/lib/components/tree/TreeNode.vue.d.ts +18 -0
  16. package/lib/components/tree/props.d.ts +70 -0
  17. package/lib/components/tree/types.d.ts +53 -0
  18. package/lib/composables/index.d.ts +1 -1
  19. package/lib/composables/useMergedState.d.ts +2 -0
  20. package/lib/index.cjs.js +18 -2
  21. package/lib/index.es.js +4706 -2754
  22. package/lib/style.css +1 -1
  23. package/lib/styles/theme.css.d.ts +7 -1
  24. package/lib/utils/injection.d.ts +2 -0
  25. package/package.json +6 -4
  26. package/src/call.ts +34 -0
  27. package/src/components/confirm/Confirm.vue +60 -0
  28. package/src/components/{dialog/DialogProvider.vue → confirm/ConfirmProvider.vue} +18 -18
  29. package/src/components/confirm/examples/basic.vue +48 -0
  30. package/src/components/confirm/examples/index.stories.ts +29 -0
  31. package/src/components/confirm/examples/index.ts +1 -0
  32. package/src/components/{dialog → confirm}/props.ts +2 -2
  33. package/src/components/confirm/types.ts +34 -0
  34. package/src/components/confirm/useConfirm.ts +12 -0
  35. package/src/components/dialog/Dialog.vue +102 -44
  36. package/src/components/dialog/examples/basic.vue +8 -40
  37. package/src/components/dialog/examples/index.stories.ts +11 -22
  38. package/src/components/dialog/examples/index.ts +1 -1
  39. package/src/components/dialog/style/Dialog.css.ts +99 -0
  40. package/src/components/dialog/style/index.ts +1 -0
  41. package/src/components/dialog/types.ts +0 -34
  42. package/src/components/index.ts +3 -1
  43. package/src/components/select/Select.vue +100 -34
  44. package/src/components/select/examples/multiple.vue +1 -1
  45. package/src/components/select/style/Select.css.ts +91 -11
  46. package/src/components/tag/style/Tag.css.ts +0 -1
  47. package/src/components/tree/Tree.vue +126 -0
  48. package/src/components/tree/TreeNode.vue +71 -0
  49. package/src/components/tree/examples/basic.vue +33 -0
  50. package/src/components/tree/examples/index.stories.ts +16 -0
  51. package/src/components/tree/examples/index.ts +1 -0
  52. package/src/components/tree/props.ts +87 -0
  53. package/src/components/tree/types.ts +96 -0
  54. package/src/composables/index.ts +1 -1
  55. package/src/composables/useMergedState.ts +18 -0
  56. package/src/styles/theme.css.ts +3 -1
  57. package/src/utils/injection.ts +5 -0
  58. package/lib/components/dialog/useDialog.d.ts +0 -2
  59. package/lib/styles/typography.css.d.ts +0 -22
  60. package/src/components/dialog/useDialog.ts +0 -12
  61. package/src/styles/typography.css.ts +0 -91
  62. /package/lib/components/{dialog → confirm}/providerProps.d.ts +0 -0
  63. /package/src/components/{dialog → confirm}/examples/wrapper.vue +0 -0
  64. /package/src/components/{dialog → confirm}/providerProps.ts +0 -0
@@ -14,7 +14,7 @@
14
14
  <div :class="selectComputedClasses">
15
15
  <template v-if="multiple && Array.isArray(selected) && selected.length > 0">
16
16
  <MTag
17
- v-for="value in selected"
17
+ v-for="value in selectedMax"
18
18
  :key="value"
19
19
  :closeable="true"
20
20
  @close="removeSelected(value)"
@@ -22,6 +22,11 @@
22
22
  >
23
23
  {{ getSelectedLabel(value) }}
24
24
  </MTag>
25
+ <template v-if="selectedCountNotShown > 0">
26
+ <MTag :size="size">
27
+ +{{ selectedCountNotShown }} more
28
+ </MTag>
29
+ </template>
25
30
  </template>
26
31
  <template v-else-if="!multiple && selected">
27
32
  {{ getSelectedLabel(selected) }}
@@ -40,9 +45,22 @@
40
45
  >
41
46
  <div :class="style.menu" v-if="isOpen" ref="popper" :style="computedConsistentWidth"
42
47
  :aria-hidden="isOpen ? 'false' : 'true'">
43
- <div v-if="searchable" :class="style.search">
44
- <!-- <MInput v-model="search" placeholder="Search" size="sm" ref="searchRef"/> -->
45
- <input type="text" ref="searchRef" v-model="search" placeholder="Search" :class="style.searchInput" />
48
+ <div v-if="searchable || (multiple && selectAll)" :class="style.search">
49
+ <div v-if="multiple && selectAll" :class="style.checkAll">
50
+ <button
51
+ role="button"
52
+ type="button"
53
+ :class="{
54
+ [style.checkAllButton]: true,
55
+ [style.checkAllButtonActive]: computedOptions.length > 0 && computedOptions.length === selected.length
56
+ }"
57
+ @click.prevent="toggleAllItems"
58
+ aria-description="Select all items"
59
+ >
60
+ <svg v-if="computedOptions.length > 0 && computedOptions.length === selected.length" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z" clip-rule="evenodd"></path></svg>
61
+ </button>
62
+ </div>
63
+ <input v-if="searchable" type="text" ref="searchRef" v-model="search" placeholder="Search" :class="style.searchInput" />
46
64
  </div>
47
65
  <div :class="style.options">
48
66
  <div v-for="(option, index) in computedOptions" :key="index" ref="optionsRef"
@@ -52,8 +70,12 @@
52
70
  [style.optionFocused]: selectedIndex === index
53
71
  }"
54
72
  @click.prevent="option.onClick"
55
- @hover="selectedIndex = index">
56
- {{ option.label }}
73
+ @hover="selectedIndex = index"
74
+ >
75
+ <span :class="style.optionIcon">
76
+ <svg v-if="option.active" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z" clip-rule="evenodd"></path></svg>
77
+ </span>
78
+ <span :class="style.optionLabel">{{ option.label }}</span>
57
79
  </div>
58
80
  <template v-if="computedOptions.length === 0">
59
81
  <div :class="style.noResults">
@@ -86,7 +108,6 @@ import {
86
108
  defineExpose,
87
109
  type PropType
88
110
  } from "vue";
89
- import MInput from "../input/Input.vue";
90
111
  import MTag from "../tag/Tag.vue";
91
112
  import * as style from "./style";
92
113
  import { scale } from "../../styles/transitions.css";
@@ -156,9 +177,16 @@ export default defineComponent({
156
177
  type: Boolean,
157
178
  default: false
158
179
  },
180
+ selectAll: {
181
+ type: Boolean,
182
+ default: false
183
+ },
184
+ max: {
185
+ type: Number,
186
+ default: 5
187
+ }
159
188
  },
160
189
  components: {
161
- MInput,
162
190
  MTag
163
191
  },
164
192
  setup(props, {emit}) {
@@ -176,7 +204,6 @@ export default defineComponent({
176
204
  const width = ref<number>(0);
177
205
  const keyboardNavigation = ref<boolean>(false);
178
206
 
179
-
180
207
  const {focus, blur} = useInteractive(container);
181
208
  // TODO: Add debounce on keyboard movement (hover flicks it back up)
182
209
  const {
@@ -188,7 +215,7 @@ export default defineComponent({
188
215
  formLoading
189
216
  } = useInput(props, {focus, emit, withListeners: false});
190
217
 
191
- const {popper} = usePopper({
218
+ const {popper, updatePopper} = usePopper({
192
219
  placement,
193
220
  transfer,
194
221
  reference: container,
@@ -220,6 +247,27 @@ export default defineComponent({
220
247
  }
221
248
  });
222
249
 
250
+ const selectedMax = computed(() => {
251
+ if (props.max <= 0) {
252
+ return selected.value;
253
+ }
254
+
255
+ if (props.multiple && Array.isArray(selected.value) && selected.value.length > props.max) {
256
+ return selected.value.slice(0, props.max);
257
+ }
258
+
259
+ return selected.value;
260
+ });
261
+
262
+ const selectedCountNotShown = computed(() => {
263
+
264
+ if (props.multiple && Array.isArray(selected.value) && props.max > 0) {
265
+ return selected.value.length - props.max;
266
+ }
267
+
268
+ return 0;
269
+ });
270
+
223
271
  const computedConsistentWidth = computed(() => {
224
272
  if (props.consistentWidth) {
225
273
  return {
@@ -250,7 +298,7 @@ export default defineComponent({
250
298
  }
251
299
 
252
300
  let value = option[props.optionValue];
253
- let active = false;
301
+ let active: boolean;
254
302
  if (props.multiple && Array.isArray(selected.value)) {
255
303
  active = selected.value.includes(value);
256
304
  } else {
@@ -263,13 +311,6 @@ export default defineComponent({
263
311
  active: active,
264
312
  onClick: () => handleOptionClick(value)
265
313
  }
266
- }).filter((option) => {
267
- if (props.multiple && Array.isArray(selected.value)) {
268
- // Filter out selected options
269
- return !selected.value.includes(option.value);
270
- }
271
-
272
- return true;
273
314
  });
274
315
 
275
316
  if (props.allowBlank) {
@@ -294,7 +335,7 @@ export default defineComponent({
294
335
  }
295
336
 
296
337
  let value = option[props.optionValue];
297
- let active = false;
338
+ let active: boolean;
298
339
  if (props.multiple && Array.isArray(selected.value)) {
299
340
  active = selected.value.includes(value);
300
341
  } else {
@@ -307,13 +348,6 @@ export default defineComponent({
307
348
  active: active,
308
349
  onClick: () => handleOptionClick(value)
309
350
  }
310
- }).filter((option) => {
311
- if (props.multiple && Array.isArray(selected.value)) {
312
- // Filter out selected options
313
- return !selected.value.includes(option.value);
314
- }
315
-
316
- return true;
317
351
  });
318
352
 
319
353
  if (props.allowBlank) {
@@ -328,6 +362,26 @@ export default defineComponent({
328
362
  return options;
329
363
  });
330
364
 
365
+ function toggleAllItems() {
366
+ if (!props.multiple) {
367
+ return;
368
+ }
369
+
370
+ if (computedOptions.value.length === 0) {
371
+ return;
372
+ }
373
+
374
+ if (Array.isArray(selected.value)) {
375
+ if (selected.value.length === computedOptions.value.length) {
376
+ selected.value = [];
377
+ } else {
378
+ selected.value = computedOptions.value.map((option) => option.value);
379
+ }
380
+ } else {
381
+ selected.value = [];
382
+ }
383
+ }
384
+
331
385
  function openSelect() {
332
386
  if (disabled.value || loading.value) {
333
387
  return;
@@ -379,6 +433,8 @@ export default defineComponent({
379
433
  } else {
380
434
  selected.value = [value];
381
435
  }
436
+
437
+ updatePopper();
382
438
  } else {
383
439
  selected.value = value;
384
440
  open.value = false;
@@ -396,9 +452,15 @@ export default defineComponent({
396
452
  if (index !== -1) {
397
453
  selected.value.splice(index, 1);
398
454
  }
455
+
456
+ updatePopper();
399
457
  }
400
458
  }
401
459
 
460
+ useEventListener('resize', () => {
461
+ open.value = false;
462
+ });
463
+
402
464
  function handleKeyDown(e: Event) {
403
465
  if (computedOptions.value.length === 0) {
404
466
  return;
@@ -464,11 +526,6 @@ export default defineComponent({
464
526
  }
465
527
  }
466
528
 
467
- function focusOut(e: Event) {
468
- console.log(e);
469
-
470
- }
471
-
472
529
  const transitionName = computed(() => {
473
530
  return `m-transition__${props.transitionName}`;
474
531
  });
@@ -495,7 +552,7 @@ export default defineComponent({
495
552
  return [
496
553
  style.base,
497
554
  {
498
- [style.selectedTags]: props.multiple,
555
+ [style.selectedTags]: props.multiple && Array.isArray(selected.value) && selected.value.length > 0,
499
556
  [style.focused]: open.value,
500
557
  // [`m-select--${props.size}`]: props.size,
501
558
  'm-select--disabled': disabled.value || loading.value,
@@ -539,6 +596,13 @@ export default defineComponent({
539
596
 
540
597
  watch(open, (value: boolean) => {
541
598
  nextTick(() => {
599
+ if (!value) {
600
+ search.value = "";
601
+ }
602
+
603
+ if (props.searchable && searchRef.value) {
604
+ searchRef.value.focus();
605
+ }
542
606
  if (value && optionsRef.value) {
543
607
  const index = computedOptions.value.findIndex((i) => i.value === selected.value);
544
608
  if (index !== -1) {
@@ -580,12 +644,13 @@ export default defineComponent({
580
644
  selectRef,
581
645
  popper,
582
646
  focusSelect,
583
- focusOut,
584
647
 
585
648
  id,
586
649
  isOpen,
587
650
  open,
588
651
  selected,
652
+ selectedMax,
653
+ selectedCountNotShown,
589
654
  computedOptions,
590
655
  selectedIndex,
591
656
  search,
@@ -603,7 +668,8 @@ export default defineComponent({
603
668
  inputListeners,
604
669
  style,
605
670
  focused,
606
- scale
671
+ scale,
672
+ toggleAllItems
607
673
  }
608
674
  }
609
675
  });
@@ -12,7 +12,7 @@
12
12
  <script lang="ts" setup>
13
13
  import { ref } from "vue";
14
14
 
15
- const options = Array.from({ length: 5 }, (_, i) => ({
15
+ const options = Array.from({ length: 10 }, (_, i) => ({
16
16
  id: i,
17
17
  name: `Option ${i+1}`
18
18
  }));
@@ -1,6 +1,5 @@
1
1
  import { createVar, globalStyle, style, styleVariants } from "@vanilla-extract/css";
2
2
  import { themeVars } from "../../../styles/theme.css";
3
- import { fonts } from "../../../styles/typography.css";
4
3
 
5
4
  const sizes = {
6
5
  xs: '26px',
@@ -48,7 +47,6 @@ export const base = style({
48
47
  [vars.backgroundColor]: '#FFFFFF',
49
48
  [vars.cursor]: 'text',
50
49
  },
51
- height: vars.height,
52
50
  minHeight: vars.height,
53
51
  width: '100%',
54
52
  borderRadius: vars.radius,
@@ -88,6 +86,8 @@ export const selectedTags = style({
88
86
  flexWrap: 'wrap',
89
87
  alignItems: 'center',
90
88
  gap: themeVars.space[1],
89
+ paddingTop: themeVars.space[1.5],
90
+ paddingBottom: themeVars.space[1.5],
91
91
  });
92
92
 
93
93
  export const block = style({
@@ -200,6 +200,8 @@ export const search = style({
200
200
  position: 'sticky',
201
201
  top: 0,
202
202
  zIndex: 1,
203
+ borderBottom: `1px solid ${themeVars.color.gray[200]}`,
204
+ display: 'flex',
203
205
  });
204
206
 
205
207
  export const searchInput = style({
@@ -208,8 +210,8 @@ export const searchInput = style({
208
210
  outline: 'none',
209
211
  width: '100%',
210
212
  fontSize: themeVars.fontSize.base,
211
- borderBottom: `1px solid ${themeVars.color.gray[200]}`,
212
213
  color: themeVars.color.gray[800],
214
+ flexShrink: 1
213
215
  });
214
216
 
215
217
  export const options = style({
@@ -237,6 +239,7 @@ export const option = style({
237
239
  transition: "background-color 0.05s ease-in-out",
238
240
  backgroundColor: vars.optionBackgroundColor,
239
241
  borderRadius: borderRadiuses.md,
242
+ userSelect: "none",
240
243
  ':hover': {
241
244
  vars: {
242
245
  [vars.optionColor]: themeVars.color.gray[800],
@@ -245,6 +248,7 @@ export const option = style({
245
248
  }
246
249
  });
247
250
 
251
+
248
252
  export const optionFocused = style({
249
253
  vars: {
250
254
  [vars.optionColor]: themeVars.color.gray[800],
@@ -259,18 +263,85 @@ export const optionFocused = style({
259
263
  });
260
264
 
261
265
  export const optionSelected = style({
262
- vars: {
263
- [vars.optionColor]: themeVars.color.blue[50],
264
- [vars.optionBackgroundColor]: themeVars.color.blue[500],
265
- },
266
+ // vars: {
267
+ // [vars.optionColor]: themeVars.color.blue[50],
268
+ // [vars.optionBackgroundColor]: themeVars.color.blue[500],
269
+ // },
270
+ // ':hover': {
271
+ // vars: {
272
+ // [vars.optionColor]: themeVars.color.blue[50],
273
+ // [vars.optionBackgroundColor]: themeVars.color.blue[600],
274
+ // },
275
+ // }
276
+ });
277
+
278
+ export const optionLabel = style({
279
+
280
+ });
281
+
282
+ export const checkAll = style({
283
+ display: 'flex',
284
+ alignItems: 'center',
285
+ justifyContent: 'center',
286
+ width: themeVars.space[5],
287
+ margin: `0 ${themeVars.space[2]}`,
288
+ flexShrink: 0,
289
+ flex: 1
290
+ });
291
+
292
+ export const checkAllButton = style({
293
+ flexShrink: 0,
294
+ alignItems: 'center',
295
+ cursor: 'pointer',
296
+ justifyContent: 'center',
297
+ marginLeft: themeVars.space[2.5],
298
+ width: themeVars.space[6],
299
+ height: themeVars.space[6],
300
+ borderRadius: borderRadiuses.md,
301
+ color: themeVars.color.blue[500],
302
+ border: `2px solid ${themeVars.color.gray[200]}`,
303
+ padding: 0,
304
+ background: 'none',
305
+ outline: 'none',
266
306
  ':hover': {
267
- vars: {
268
- [vars.optionColor]: themeVars.color.blue[50],
269
- [vars.optionBackgroundColor]: themeVars.color.blue[600],
270
- },
307
+ borderColor: themeVars.color.gray[300],
308
+ }
309
+ });
310
+
311
+ export const checkAllButtonActive = style({
312
+ border: `1px solid ${themeVars.color.blue[500]}`,
313
+ background: themeVars.color.blue[500],
314
+ color: 'white',
315
+ ':hover': {
316
+ borderColor: themeVars.color.blue[600],
317
+ background: themeVars.color.blue[600],
271
318
  }
272
319
  });
273
320
 
321
+ globalStyle(`${checkAllButtonActive} > svg`, {
322
+ position: 'relative',
323
+ top: '1px',
324
+ width: themeVars.space[4],
325
+ height: themeVars.space[4],
326
+ color: 'white'
327
+ });
328
+
329
+ export const optionIcon = style({
330
+ display: 'flex',
331
+ alignItems: 'center',
332
+ justifyContent: 'center',
333
+ width: themeVars.space[5],
334
+ marginLeft: '-2px'
335
+ });
336
+
337
+ globalStyle(`${optionIcon} > svg`, {
338
+ position: 'relative',
339
+ top: '1px',
340
+ width: themeVars.space[5],
341
+ height: themeVars.space[5],
342
+ color: themeVars.color.blue[500],
343
+ });
344
+
274
345
  export const noResults = style({
275
346
  padding: `${themeVars.space[2]} 0`,
276
347
  display: 'flex',
@@ -283,3 +354,12 @@ export const noResults = style({
283
354
  color: themeVars.color.gray[500],
284
355
  fontSize: themeVars.fontSize.small,
285
356
  });
357
+
358
+ export const clearable = style({
359
+ width: themeVars.space[4],
360
+ height: themeVars.space[4],
361
+ position: 'absolute',
362
+ right: themeVars.space[2],
363
+ top: '0',
364
+ display: 'flex',
365
+ })
@@ -1,6 +1,5 @@
1
1
  import { createVar, globalStyle, style, styleVariants } from "@vanilla-extract/css";
2
2
  import { themeVars } from "../../../styles/theme.css";
3
- import { fonts } from "../../../styles/typography.css";
4
3
 
5
4
  const sizes = {
6
5
  xs: '18px',
@@ -0,0 +1,126 @@
1
+ <template>
2
+ <div style="height: 200px;" ref="selfElRef">
3
+ <VVirtualList
4
+ ref="virtualListInstRef"
5
+ :items="nodesRef"
6
+ itemResizable
7
+ :itemSize="20"
8
+ >
9
+ <template v-slot:default="{ item }">
10
+ <TreeNode :node="item"/>
11
+ </template>
12
+ </VVirtualList>
13
+ </div>
14
+ </template>
15
+
16
+ <script lang="ts">
17
+ import {type VirtualListInst, VVirtualList} from 'vueuc'
18
+ import {computed, defineComponent, provide, ref, toRef} from 'vue'
19
+ import {createTreeMateOptions, treeProps} from "./props";
20
+ import type {Key, TmNode, TreeOption} from "./types";
21
+ import {
22
+ createTreeMate,
23
+ } from 'treemate'
24
+ import TreeNode from "./TreeNode.vue";
25
+ import {treeInjectionKey} from "./types";
26
+ import {call} from "../../call";
27
+ import useMergedState from "../../composables/useMergedState";
28
+
29
+ export default defineComponent({
30
+ components: {TreeNode, VVirtualList},
31
+ name: 'Tree',
32
+ props: treeProps,
33
+ setup: function (props) {
34
+ const selfElRef = ref<HTMLDivElement | null>(null);
35
+ const virtualListInstRef = ref<VirtualListInst | null>(null)
36
+ function getScrollContainer (): HTMLElement | null | undefined {
37
+ return virtualListInstRef.value?.listElRef
38
+ }
39
+ function getScrollContent (): HTMLElement | null | undefined {
40
+ return virtualListInstRef.value?.itemsElRef
41
+ }
42
+
43
+ const displayTreeMateRef = computed(() => {
44
+ return createTreeMate<TreeOption>(
45
+ props.data,
46
+ createTreeMateOptions(
47
+ props.keyField,
48
+ props.childrenField,
49
+ 'disabled',
50
+ props.getChildren
51
+ )
52
+ )
53
+ });
54
+
55
+ const dataTreeMateRef = displayTreeMateRef;
56
+ const controlledExpandedKeysRef = toRef(props, 'expandedKeys')
57
+ const uncontrolledExpandedKeysRef = ref<Key[]>([])
58
+ const mergedExpandedKeysRef = useMergedState(
59
+ controlledExpandedKeysRef,
60
+ uncontrolledExpandedKeysRef
61
+ )
62
+ const nodesRef = computed(() => {
63
+ return displayTreeMateRef.value.getFlattenedNodes(mergedExpandedKeysRef.value);
64
+ });
65
+
66
+ provide(treeInjectionKey, {
67
+ indentRef: toRef(props, 'indent'),
68
+ expandOnClickRef: toRef(props, 'expandOnClick'),
69
+ nodePropsRef: toRef(props, 'nodeProps'),
70
+ handleSwitcherClick
71
+ });
72
+
73
+ // const { watchProps } = props;
74
+ //
75
+ // watchEffect(() => {
76
+ // initUncontrolledExpandedKeys(undefined);
77
+ // })
78
+
79
+ function handleSwitcherClick(node: TmNode): void {
80
+ console.log('i')
81
+ toggleExpand(node);
82
+ }
83
+
84
+ function toggleExpand(node: TmNode): void {
85
+ const {key} = node;
86
+ const {value: mergedExpandedKeys} = mergedExpandedKeysRef
87
+ const index = mergedExpandedKeys.findIndex(
88
+ (expandedNodeId) => expandedNodeId === key
89
+ )
90
+
91
+ if (~index) {
92
+ const expandedKeysAfterChange = Array.from(mergedExpandedKeys);
93
+ expandedKeysAfterChange.splice(index, 1);
94
+ doUpdateExpandedKeys(expandedKeysAfterChange);
95
+ } else {
96
+ const nodeToBeExpanded = displayTreeMateRef.value.getNode(key);
97
+ if (!nodeToBeExpanded || nodeToBeExpanded.isLeaf) {
98
+ return;
99
+ }
100
+
101
+ let nextKeys: Key[] = mergedExpandedKeys.concat(key);
102
+
103
+ doUpdateExpandedKeys(nextKeys)
104
+ }
105
+ }
106
+
107
+ function getOptionsByKeys(keys: Key[]): Array<TreeOption | null> {
108
+ const {getNode} = dataTreeMateRef.value;
109
+ return keys.map((key) => getNode(key)?.rawNode || null);
110
+ }
111
+
112
+ function doUpdateExpandedKeys(
113
+ value: Key[],
114
+ ): void {
115
+ uncontrolledExpandedKeysRef.value = value;
116
+ }
117
+
118
+ return {
119
+ nodesRef,
120
+ displayTreeMateRef,
121
+ selfElRef
122
+ }
123
+ }
124
+ })
125
+
126
+ </script>
@@ -0,0 +1,71 @@
1
+ <template>
2
+ <div
3
+ @click="handleLineClick"
4
+ style="display: flex;"
5
+ >
6
+ <template v-if="indentCount > 0">
7
+ <div v-for="i in indentCount" style="flex-grow: 0; flex-shrink: 0; height: 0;">
8
+ <div :key="i" style="width: 16px;"></div>
9
+ </div>
10
+ </template>
11
+ {{node.rawNode.label}}
12
+ </div>
13
+ </template>
14
+
15
+ <script lang="ts">
16
+ import {ref, defineComponent, type PropType, inject, computed} from "vue";
17
+ import type {TmNode} from "./types";
18
+ import {treeInjectionKey} from "./types";
19
+
20
+ export default defineComponent({
21
+ name: 'TreeNode',
22
+ props: {
23
+ node: {
24
+ type: Object as PropType<TmNode>,
25
+ required: true
26
+ }
27
+ },
28
+ setup(props) {
29
+ const NTree = inject(treeInjectionKey)!;
30
+ const {
31
+ nodePropsRef,
32
+ indentRef,
33
+ handleSwitcherClick: handleSwitcherClickTree
34
+ } = NTree;
35
+
36
+ const selfElRef = ref<HTMLDivElement | null>(null);
37
+
38
+ function _handleClick (e: MouseEvent): void {
39
+ const { value: expandOnClick } = NTree.expandOnClickRef;
40
+ const { node } = props
41
+ if (!expandOnClick && !node.isLeaf) {
42
+ handleSwitcherClick();
43
+ }
44
+ }
45
+
46
+ function handleSwitcherClick(): void {
47
+ const { node } = props;
48
+
49
+ if (!node.isLastChild && !node.shallowLoaded) {
50
+ return;
51
+ }
52
+
53
+ handleSwitcherClickTree(node)
54
+ }
55
+
56
+ const indentCount = computed(() => {
57
+ return props.node.level;
58
+ })
59
+
60
+ function handleLineClick (e: MouseEvent) {
61
+ _handleClick(e);
62
+ }
63
+
64
+ return {
65
+ selfElRef,
66
+ handleLineClick,
67
+ indentCount
68
+ }
69
+ }
70
+ });
71
+ </script>
@@ -0,0 +1,33 @@
1
+ <template>
2
+ <MTree :data="data"/>
3
+ </template>
4
+ <script lang="ts" setup>
5
+ import {ref} from 'vue'
6
+ import type {TreeOption} from "../types";
7
+
8
+ function createData (level = 4, baseKey = ''): TreeOption[] | undefined {
9
+ if (!level) return undefined
10
+
11
+ const result: TreeOption[] = [];
12
+ for (let index = 0; index < (20 - level); index++) {
13
+ const key = '' + baseKey + level + index;
14
+ result.push({
15
+ label: createLabel(level),
16
+ key,
17
+ children: createData(level - 1, key)
18
+ });
19
+ }
20
+
21
+ return result;
22
+ }
23
+
24
+ function createLabel (level: number): string {
25
+ if (level === 4) return 'Out of Tao, One is born'
26
+ if (level === 3) return 'Out of One, Two'
27
+ if (level === 2) return 'Out of Two, Three'
28
+ if (level === 1) return 'Out of Three, the created universe'
29
+ return ''
30
+ }
31
+
32
+ const data = ref(createData());
33
+ </script>