renusify 2.4.3 → 2.5.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.
@@ -7,61 +7,83 @@
7
7
  @touchend="touchend"
8
8
  @touchmove="touchmove"
9
9
  @touchstart="dragstart"
10
+ :aria-expanded="!closed"
11
+ :aria-label="item.title || `Item ${item[keyProp]}`"
12
+ role="listitem"
10
13
  >
11
14
  <div
12
15
  class="nestable-item-content"
13
16
  @mouseenter="onMouseEnter"
14
17
  @mouseleave="onMouseLeave"
15
18
  @mousemove="onMouseMove"
19
+ tabindex="0"
20
+ @keydown="onKeyDown"
16
21
  >
17
22
  <r-btn-confirm
18
23
  v-if="editable"
19
- class="color-error-text"
24
+ :aria-label="`Delete ${item.title || 'item'}`"
20
25
  icon
21
26
  text
22
27
  @click="del(item)"
28
+ class="color-error-text mr-2"
23
29
  >
24
30
  <r-icon v-html="$r.icons.delete"></r-icon>
25
31
  </r-btn-confirm>
26
- <r-btn v-if="hasChildren" icon text @click="closed=!closed">
27
- <r-icon v-html="closed?$r.icons.plus:$r.icons.minus"></r-icon>
32
+
33
+ <r-btn
34
+ v-if="hasChildren"
35
+ :aria-label="closed ? 'Expand' : 'Collapse'"
36
+ class="mr-2"
37
+ icon
38
+ text
39
+ @click="toggleChildren"
40
+ >
41
+ <r-icon v-html="closed ? $r.icons.plus : $r.icons.minus"></r-icon>
28
42
  </r-btn>
29
- <r-btn v-else icon text></r-btn>
30
- <slot :item="item">{{ item }}</slot>
43
+
44
+ <r-btn
45
+ v-else
46
+ aria-hidden="true"
47
+ class="mr-2"
48
+ icon
49
+ text
50
+ ></r-btn>
51
+
52
+ <slot :item="item">{{ item.title || `Item ${item[keyProp]}` }}</slot>
31
53
  </div>
32
- <div v-if="hasChildren&&!closed" class="nestable-list ms-5">
33
- <template
34
- v-for="(child, childIndex) in item[childrenProp]"
35
- :key="childIndex"
54
+
55
+ <transition name="nestable-children">
56
+ <div
57
+ v-if="hasChildren && !closed"
58
+ class="nestable-list ms-5"
59
+ role="group"
36
60
  >
37
- <NestableItem
38
- :childrenProp="childrenProp"
39
- :dragItem="dragItem"
40
- :index="childIndex"
41
- :item="child"
42
- :key-prop="keyProp"
43
- is-child
44
- @delete="$emit('delete', $event)"
45
- @drag-start="$emit('drag-start', $event)"
46
- @touch-move="$emit('touch-move', $event)"
47
- @mouse-enter="$emit('mouse-enter', $event)"
48
- @drag-end="$emit('drag-end', $event)"
61
+ <template
62
+ v-for="(child, childIndex) in item[childrenProp]"
63
+ :key="child[keyProp]"
49
64
  >
50
- <template v-slot="{ item }">
51
- <r-btn-confirm
52
- v-if="editable"
53
- class="color-error-text"
54
- icon
55
- text
56
- @click="del(item)"
57
- >
58
- <r-icon v-html="$r.icons.delete"></r-icon>
59
- </r-btn-confirm>
60
- <slot :item="item" itemscope>{{ item }}</slot>
61
- </template>
62
- </NestableItem>
63
- </template>
64
- </div>
65
+ <NestableItem
66
+ :childrenProp="childrenProp"
67
+ :dragItem="dragItem"
68
+ :editable="editable"
69
+ :index="childIndex"
70
+ :item="child"
71
+ :keyProp="keyProp"
72
+ :maxDepth="maxDepth"
73
+ is-child
74
+ @delete="$emit('delete', $event)"
75
+ @drag-start="$emit('drag-start', $event)"
76
+ @touch-move="$emit('touch-move', $event)"
77
+ @mouse-enter="$emit('mouse-enter', $event)"
78
+ @drag-end="$emit('drag-end', $event)"
79
+ >
80
+ <template v-slot="{ item }">
81
+ <slot :item="item" itemscope>{{ item.title || `Item ${item[keyProp]}` }}</slot>
82
+ </template>
83
+ </NestableItem>
84
+ </template>
85
+ </div>
86
+ </transition>
65
87
  </div>
66
88
  </template>
67
89
 
@@ -88,12 +110,14 @@ export default {
88
110
  },
89
111
  childrenProp: String,
90
112
  dragItem: Object,
113
+ maxDepth: Number,
91
114
  },
92
115
  data() {
93
116
  return {
94
117
  breakPoint: null,
95
118
  moveDown: false,
96
- closed: false
119
+ closed: false,
120
+ isKeyboardNavigating: false
97
121
  };
98
122
  },
99
123
  computed: {
@@ -110,28 +134,65 @@ export default {
110
134
  },
111
135
 
112
136
  itemClasses() {
113
- const isDragging = this.isDragging ? ["is-dragging"] : [];
114
-
115
- return [
137
+ const classes = [
116
138
  "nestable-item",
117
139
  `nestable-item-${this.item[this.keyProp]}`,
118
- ...isDragging,
119
140
  ];
141
+
142
+ if (this.isDragging) {
143
+ classes.push("is-dragging");
144
+ }
145
+
146
+ if (this.isKeyboardNavigating) {
147
+ classes.push("is-focused");
148
+ }
149
+
150
+ return classes;
120
151
  },
152
+
153
+ depth() {
154
+ return this.getDepth(this.item);
155
+ }
121
156
  },
122
157
  methods: {
158
+ getDepth(item, level = 0) {
159
+ if (!item[this.childrenProp] || item[this.childrenProp].length === 0) {
160
+ return level;
161
+ }
162
+
163
+ let maxDepth = level;
164
+ item[this.childrenProp].forEach(child => {
165
+ const childDepth = this.getDepth(child, level + 1);
166
+ if (childDepth > maxDepth) {
167
+ maxDepth = childDepth;
168
+ }
169
+ });
170
+
171
+ return maxDepth;
172
+ },
173
+
174
+ toggleChildren() {
175
+ this.closed = !this.closed;
176
+ },
177
+
123
178
  del(item) {
124
179
  this.$emit("delete", item);
125
180
  },
181
+
126
182
  dragstart(event) {
183
+ event.dataTransfer.setData('text/plain', this.item[this.keyProp]);
184
+ event.dataTransfer.effectAllowed = 'move';
127
185
  this.$emit("drag-start", [event, this.item]);
128
186
  },
187
+
129
188
  touchend(event) {
130
189
  this.$emit("drag-end", event);
131
190
  },
191
+
132
192
  touchmove(event) {
133
193
  this.$emit("touch-move", event);
134
194
  },
195
+
135
196
  onMouseEnter(event) {
136
197
  if (!this.dragItem) return;
137
198
 
@@ -140,12 +201,13 @@ export default {
140
201
  }
141
202
 
142
203
  this.moveDown = event.movementY > 0;
143
-
144
204
  this.breakPoint = event.target.getBoundingClientRect().height / 2;
145
205
  },
206
+
146
207
  onMouseLeave() {
147
208
  this.breakPoint = null;
148
209
  },
210
+
149
211
  onMouseMove(event) {
150
212
  if (!this.breakPoint) return;
151
213
 
@@ -156,10 +218,90 @@ export default {
156
218
 
157
219
  this.sendNotification(event);
158
220
  },
221
+
159
222
  sendNotification(event) {
160
223
  this.breakPoint = null;
161
224
  this.$emit("mouse-enter", [event, this.item]);
162
225
  },
163
- },
226
+
227
+ onKeyDown(event) {
228
+ // Handle keyboard navigation
229
+ switch (event.key) {
230
+ case 'ArrowUp':
231
+ this.navigate(-1);
232
+ break;
233
+ case 'ArrowDown':
234
+ this.navigate(1);
235
+ break;
236
+ case 'ArrowLeft':
237
+ if (!this.closed && this.hasChildren) {
238
+ this.closed = true;
239
+ }
240
+ break;
241
+ case 'ArrowRight':
242
+ if (this.closed && this.hasChildren) {
243
+ this.closed = false;
244
+ }
245
+ break;
246
+ case 'Enter':
247
+ case ' ':
248
+ if (this.hasChildren) {
249
+ this.closed = !this.closed;
250
+ }
251
+ break;
252
+ case 'Delete':
253
+ if (this.editable) {
254
+ this.del(this.item);
255
+ }
256
+ break;
257
+ default:
258
+ return;
259
+ }
260
+
261
+ event.preventDefault();
262
+ },
263
+
264
+ navigate(direction) {
265
+ // Implement keyboard navigation between items
266
+ this.isKeyboardNavigating = true;
267
+ // You'll need to implement actual navigation logic here
268
+ // This would involve finding the next/previous item in the tree
269
+ // and focusing it
270
+ }
271
+ }
164
272
  };
165
273
  </script>
274
+
275
+ <style lang="scss">
276
+ @use "../../style/variables/base";
277
+
278
+ .#{base.$prefix}nestable {
279
+ .nestable-item {
280
+ outline: none;
281
+
282
+ &.is-focused {
283
+ box-shadow: 0 0 0 2px #4a90e2;
284
+ }
285
+ }
286
+
287
+ .nestable-children-enter-active,
288
+ .nestable-children-leave-active {
289
+ transition: all 0.3s ease;
290
+ overflow: hidden;
291
+ }
292
+
293
+ .nestable-children-enter-from,
294
+ .nestable-children-leave-to {
295
+ opacity: 0;
296
+ transform: translateY(-10px);
297
+ max-height: 0;
298
+ }
299
+
300
+ .nestable-children-enter-to,
301
+ .nestable-children-leave-from {
302
+ opacity: 1;
303
+ transform: translateY(0);
304
+ max-height: 1000px;
305
+ }
306
+ }
307
+ </style>
@@ -1,7 +1,11 @@
1
1
  <template>
2
- <div ref="nestable" :class="`${$r.prefix}nestable`">
3
- <div class="nestable-list nestable-group">
4
- <template v-for="(item, index) in modelValue" :key="index">
2
+ <div
3
+ ref="nestable"
4
+ :class="`${$r.prefix}nestable`"
5
+ aria-label="Nestable list"
6
+ >
7
+ <div class="nestable-list nestable-group" role="list">
8
+ <template v-for="(item, index) in modelValue" :key="item[keyProp]">
5
9
  <nestable-item
6
10
  :childrenProp="childrenProp"
7
11
  :dragItem="dragItem"
@@ -9,6 +13,7 @@
9
13
  :index="index"
10
14
  :item="item"
11
15
  :keyProp="keyProp"
16
+ :maxDepth="maxDepth"
12
17
  @delete="del"
13
18
  @drag-start="dragstart"
14
19
  @touch-move="touchmove"
@@ -16,17 +21,31 @@
16
21
  @drag-end="onDragEnd"
17
22
  >
18
23
  <template v-slot="{ item }">
19
- <slot :item="item">{{ item }}</slot>
24
+ <slot :item="item">{{ item.title || `Item ${item[keyProp]}` }}</slot>
20
25
  </template>
21
26
  </nestable-item>
22
27
  </template>
28
+
29
+
23
30
  </div>
24
- <div v-if="editable" class="d-flex v-end w-full">
31
+
32
+ <div v-if="editable" class="d-flex v-end w-full mt-3">
25
33
  <slot :add="add" :form="form" name="form">
26
34
  <div class="pe-1 flex-grow-1">
27
- <r-text-input v-model="form.title" :label="labelInput"></r-text-input>
35
+ <r-text-input
36
+ ref="inputField"
37
+ v-model="form.title"
38
+ :label="labelInput"
39
+ @keydown.enter="form.title && add()"
40
+ ></r-text-input>
28
41
  </div>
29
- <r-btn :disabled="!form.title" class="color-success" icon @click="add">
42
+ <r-btn
43
+ :aria-label="'Add new item'"
44
+ :disabled="!form.title"
45
+ class="color-success"
46
+ icon
47
+ @click="add"
48
+ >
30
49
  <r-icon v-html="$r.icons.plus"></r-icon>
31
50
  </r-btn>
32
51
  </slot>
@@ -35,19 +54,25 @@
35
54
  </template>
36
55
 
37
56
  <script>
38
- import {defineAsyncComponent} from 'vue'
57
+ import {defineAsyncComponent, nextTick} from 'vue'
39
58
  import methods from "./methods.js";
40
59
  import editable from "./editable.js";
41
60
 
42
61
  export default {
43
62
  name: "r-nestable",
44
63
  components: {
45
- NestableItem:defineAsyncComponent(()=>import('./NestableItem.vue')),
64
+ NestableItem: defineAsyncComponent(() => import('./NestableItem.vue')),
46
65
  },
47
66
  mixins: [methods, editable],
48
67
  props: {
49
- labelInput: String,
50
- editable: Boolean,
68
+ labelInput: {
69
+ type: String,
70
+ default: "New item"
71
+ },
72
+ editable: {
73
+ type: Boolean,
74
+ default: false
75
+ },
51
76
  modelValue: {
52
77
  type: Array,
53
78
  default: () => [],
@@ -74,8 +99,8 @@ export default {
74
99
  itemsOld: null,
75
100
  dragItem: null,
76
101
  mouse: {
77
- last: {x: 0},
78
- shift: {x: 0},
102
+ last: {x: 0, y: 0},
103
+ shift: {x: 0, y: 0},
79
104
  },
80
105
  };
81
106
  },
@@ -86,17 +111,94 @@ export default {
86
111
  beforeUnmount() {
87
112
  this.stopTrackMouse();
88
113
  },
114
+ methods: {
115
+ // Override add method to focus input after adding
116
+ async add() {
117
+ let items = this.modelValue;
118
+ Object.assign(this.form, {
119
+ [this.keyProp]: this.lastId(items) + 1,
120
+ [this.childrenProp]: [],
121
+ })
122
+ items.push(this.form);
123
+ this.$emit("update:model-value", items);
124
+ this.form = {};
125
+
126
+ await nextTick();
127
+ this.$refs.inputField?.focus();
128
+ },
129
+
130
+ // Update mouse move for placeholder positioning
131
+ onMouseMove(event) {
132
+ event && event.preventDefault();
133
+
134
+ const {clientX, clientY} = this.getXandYFromEvent(event);
135
+
136
+ if (this.mouse.last.x === 0) {
137
+ this.mouse.last.x = clientX;
138
+ this.mouse.last.y = clientY;
139
+ }
140
+
141
+ const diffX = this.$r.rtl
142
+ ? this.mouse.last.x - clientX
143
+ : clientX - this.mouse.last.x;
144
+
145
+ if (
146
+ (diffX >= 0 && this.mouse.shift.x >= 0) ||
147
+ (diffX <= 0 && this.mouse.shift.x <= 0)
148
+ ) {
149
+ this.mouse.shift.x += diffX;
150
+ } else {
151
+ this.mouse.shift.x = 0;
152
+ }
153
+ this.mouse.last.x = clientX;
154
+ this.mouse.last.y = clientY;
155
+
156
+ if (Math.abs(this.mouse.shift.x) > this.threshold) {
157
+ if (this.mouse.shift.x > 0) {
158
+ this.tryIncreaseDepth(this.dragItem);
159
+ } else {
160
+ this.tryDecreaseDepth(this.dragItem);
161
+ }
162
+
163
+ this.mouse.shift.x = 0;
164
+ }
165
+ },
166
+
167
+ onDragEnd(event, isCancel = false) {
168
+ event?.preventDefault();
169
+
170
+ this.stopTrackMouse();
171
+ if (isCancel) {
172
+ this.dragRevert();
173
+ } else {
174
+ this.dragApply();
175
+ }
176
+
177
+ this.pathTo = null;
178
+ this.itemsOld = null;
179
+ this.dragItem = null;
180
+ },
181
+ }
89
182
  };
90
183
  </script>
184
+
91
185
  <style lang="scss">
92
186
  @use "../../style/variables/base";
93
187
 
94
188
  .#{base.$prefix}nestable {
189
+ position: relative;
190
+
95
191
  .nestable-handle {
96
192
  cursor: grab;
193
+ transition: transform 0.2s ease, opacity 0.2s ease;
194
+
195
+ &:active {
196
+ cursor: grabbing;
197
+ }
97
198
  }
98
199
 
99
200
  .is-dragging {
201
+ opacity: 0.5;
100
202
  cursor: grabbing !important;
101
203
  }
102
204
 
@@ -106,6 +208,19 @@ export default {
106
208
 
107
209
  .nestable-item-content {
108
210
  display: flex;
211
+ align-items: center;
212
+ padding: 8px 12px;
213
+ transition: background 0.2s ease;
214
+
215
+ &:hover {
216
+ opacity: 0.8;
217
+ }
218
+ }
219
+
220
+
221
+ .nestable-list {
222
+ position: relative;
223
+ transition: all 0.3s ease;
109
224
  }
110
225
  }
111
- </style>
226
+ </style>
@@ -44,7 +44,11 @@ export default {
44
44
  this.startTrackMouse();
45
45
 
46
46
  this.dragItem = item;
47
- this.itemsOld = this.modelValue;
47
+ this.itemsOld = JSON.parse(JSON.stringify(this.modelValue));
48
+
49
+ const {clientX, clientY} = this.getXandYFromEvent(event);
50
+ this.mouse.last = {x: clientX, y: clientY};
51
+ this.mouse.shift = {x: 0, y: 0};
48
52
 
49
53
  this.$nextTick(() => {
50
54
  this.onMouseMove(event);
@@ -119,6 +123,15 @@ export default {
119
123
  },
120
124
 
121
125
  moveItem({dragItem, pathFrom, pathTo}) {
126
+ if (JSON.stringify(pathFrom) === JSON.stringify(pathTo)) {
127
+ return;
128
+ }
129
+
130
+ const newDepth = pathTo.length + this.getItemDepth(dragItem);
131
+ if (newDepth > this.maxDepth) {
132
+ return;
133
+ }
134
+
122
135
  const realPathTo = this.getRealNextPath(pathFrom, pathTo);
123
136
 
124
137
  const removePath = this.getSplicePath(pathFrom, {
@@ -138,6 +151,12 @@ export default {
138
151
 
139
152
  this.pathTo = realPathTo;
140
153
  this.$emit("update:model-value", items);
154
+ this.$emit("change", {
155
+ movedItem: dragItem,
156
+ fromPath: pathFrom,
157
+ toPath: pathTo,
158
+ items
159
+ });
141
160
  },
142
161
  isEquals(x, y) {
143
162
  return x === y;
@@ -1,7 +1,8 @@
1
1
  <template>
2
2
  <div :class="`${$r.prefix}table-manage`">
3
3
  <r-modal v-model="showForm" bottom full-width>
4
- <slot :autoSend="autoSend" :method="method"
4
+ <slot :autoSend="autoSend"
5
+ :method="method"
5
6
  :modelValue="editedItem"
6
7
  :ok="ok"
7
8
  :options="table.option"
@@ -444,9 +445,13 @@ export default {
444
445
  this.refresh()
445
446
  },
446
447
  ok() {
447
- this.refresh()
448
+ this.table.startTime = false
449
+ this.page = 1;
450
+ this.sortBy = null;
451
+ this.sortType = 0;
448
452
  this.autoSend = false
449
453
  this.showForm = false
454
+ this.refresh()
450
455
  },
451
456
  refresh(e) {
452
457
  this.loading = true
@@ -98,8 +98,6 @@ export default {
98
98
  <style lang="scss">
99
99
  @use "../../style/variables/base";
100
100
 
101
- $btnTabsWidth: 50px;
102
-
103
101
  .#{base.$prefix}tabs {
104
102
  max-width: 100%;
105
103
  display: flex;
@@ -109,7 +107,7 @@ $btnTabsWidth: 50px;
109
107
  position: relative;
110
108
  margin: 0 auto;
111
109
  white-space: nowrap;
112
- width: calc(100% - #{$btnTabsWidth*2});
110
+ width: 100%;
113
111
  overflow: auto;
114
112
 
115
113
  scroll-behavior: smooth;
@@ -149,9 +147,5 @@ $btnTabsWidth: 50px;
149
147
  margin-right: 0;
150
148
  }
151
149
  }
152
-
153
- .btn-tabs {
154
- width: $btnTabsWidth;
155
- }
156
150
  }
157
151
  </style>
@@ -1,4 +1,4 @@
1
- import './style.scss'
1
+ import 'renusify/directive/animate/style.scss'
2
2
 
3
3
  function mounted(el, binding) {
4
4
  const modifiers = binding.modifiers || {}
@@ -17,15 +17,18 @@ function mounted(el, binding) {
17
17
  observer
18
18
  ) => {
19
19
  if (!el._observe) return
20
- const isIntersecting = Boolean(entries.find(entry => entry.isIntersecting))
21
-
20
+ if (el._observe.loading) return
21
+ const isIntersecting = entries[0].isIntersecting
22
22
  if (isIntersecting) {
23
+ el._observe.init = true
24
+ el._observe.loading = true
23
25
  setTimeout(() => {
24
26
  if (timing) {
25
27
  el.classList.add(timing)
26
28
  }
27
29
  el.classList.add('du-' + duration * 1000)
28
30
  el.classList.add('animate')
31
+ el._observe.loading = false
29
32
  }, delay)
30
33
 
31
34
  } else if(!modifiers.once) {
@@ -37,12 +40,13 @@ function mounted(el, binding) {
37
40
  if (el._observe.init && modifiers.once) {
38
41
  unmounted(el, binding)
39
42
  }
40
- else (el._observe.init = true)
41
- }, {threshold: 0})
43
+ }, {threshold: value.threshold || 0})
42
44
 
43
- el._observe = {init: false, observer}
45
+ el._observe = {init: false, loading: false, observer}
44
46
 
45
- observer.observe(el)
47
+ setTimeout(() => {
48
+ observer.observe(el)
49
+ }, 100)
46
50
  }
47
51
 
48
52
  function unmounted(el,binding) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "renusify",
3
- "version": "2.4.3",
3
+ "version": "2.5.0",
4
4
  "description": "Vue3 Framework",
5
5
  "keywords": [
6
6
  "vuejs",