@pequity/squirrel 1.2.12 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/cjs/p-loading.js +50 -17
  2. package/dist/cjs/usePLoading.js +22 -10
  3. package/dist/es/p-loading.js +51 -18
  4. package/dist/es/usePLoading.js +23 -11
  5. package/dist/squirrel/components/p-alert/p-alert.vue.d.ts +2 -2
  6. package/dist/squirrel/components/p-btn/p-btn.vue.d.ts +5 -4
  7. package/dist/squirrel/components/p-card/p-card.vue.d.ts +9 -7
  8. package/dist/squirrel/components/p-checkbox/p-checkbox.vue.d.ts +7 -5
  9. package/dist/squirrel/components/p-date-picker/p-date-picker.vue.d.ts +4 -4
  10. package/dist/squirrel/components/p-dropdown-select/p-dropdown-select.vue.d.ts +29 -27
  11. package/dist/squirrel/components/p-info-icon/p-info-icon.vue.d.ts +6 -4
  12. package/dist/squirrel/components/p-inline-date-picker/p-inline-date-picker.vue.d.ts +4 -4
  13. package/dist/squirrel/components/p-input/p-input.vue.d.ts +6 -6
  14. package/dist/squirrel/components/p-input-number/p-input-number.vue.d.ts +6 -6
  15. package/dist/squirrel/components/p-input-percent/p-input-percent.vue.d.ts +5 -4
  16. package/dist/squirrel/components/p-input-search/p-input-search.vue.d.ts +2 -2
  17. package/dist/squirrel/components/p-loading/usePLoading.d.ts +12 -4
  18. package/dist/squirrel/components/p-modal/p-modal.vue.d.ts +10 -8
  19. package/dist/squirrel/components/p-pagination-info/p-pagination-info.vue.d.ts +6 -4
  20. package/dist/squirrel/components/p-select/p-select.vue.d.ts +14 -11
  21. package/dist/squirrel/components/p-select-btn/p-select-btn.vue.d.ts +8 -6
  22. package/dist/squirrel/components/p-select-list/p-select-list.vue.d.ts +29 -27
  23. package/dist/squirrel/components/p-select-pill/p-select-pill.vue.d.ts +4 -4
  24. package/dist/squirrel/components/p-table/p-table.vue.d.ts +8 -6
  25. package/dist/squirrel/components/p-table-td/p-table-td.vue.d.ts +6 -4
  26. package/dist/squirrel/components/p-textarea/p-textarea.vue.d.ts +4 -4
  27. package/dist/squirrel/components/p-toggle/p-toggle.vue.d.ts +4 -4
  28. package/dist/squirrel/utils/inputClassesMixin.d.ts +2 -2
  29. package/dist/squirrel/utils/object.d.ts +1 -1
  30. package/dist/style.css +6 -6
  31. package/package.json +22 -22
  32. package/squirrel/components/p-loading/p-loading.spec.js +117 -16
  33. package/squirrel/components/p-loading/p-loading.stories.js +63 -3
  34. package/squirrel/components/p-loading/p-loading.vue +27 -3
  35. package/squirrel/components/p-loading/usePLoading.ts +39 -13
  36. package/squirrel/components/p-select-list/p-select-list.utils.ts +1 -1
@@ -1,5 +1,6 @@
1
1
  import PLoading from '@squirrel/components/p-loading/p-loading.vue';
2
- import { createWrapperFor, sleep, waitNT } from '@tests/jest.helpers';
2
+ import PSkeletonLoader from '@squirrel/components/p-skeleton-loader/p-skeleton-loader.vue';
3
+ import { createWrapperFor, waitNT } from '@tests/jest.helpers';
3
4
  import { usePLoading } from '@squirrel/components/p-loading/usePLoading';
4
5
 
5
6
  const createAppWrapper = () =>
@@ -9,25 +10,53 @@ const createAppWrapper = () =>
9
10
  });
10
11
 
11
12
  const createWrapper = (options) => {
12
- return createWrapperFor({
13
- template: `
14
- <button class="show" @click="loadingShow"></button>
15
- <button class="hide" @click="loadingHide"></button>
13
+ return createWrapperFor(
14
+ {
15
+ template: `
16
+ <button class="show-without-id-should-error" @click="loadingShow({content: 'test'})"></button>
17
+ <button class="show" @click="loadingShow({id: 'default'})"></button>
18
+ <button class="hide" @click="loadingHide({id: 'default'})"></button>
16
19
  <button class="request-1-sec" @click="fireRequest(1000)"></button>
17
20
  <button class="request-3-secs" @click="fireRequest(3000)"></button>
21
+ <button class="request-1-sec-text" @click="fireRequestText(1000, '1 sec')"></button>
22
+ <button class="request-3-secs-text" @click="fireRequestText(3000, '3 secs')"></button>
23
+ <button class="request-1-sec-component" @click="fireRequestComponent(1000)"></button>
18
24
  `,
19
- setup() {
20
- const { loadingShow, loadingHide } = usePLoading(options);
21
-
22
- const fireRequest = async (time) => {
23
- loadingShow();
24
- await sleep(time);
25
- loadingHide();
26
- };
27
-
28
- return { loadingShow, loadingHide, fireRequest };
25
+ setup() {
26
+ const { loadingShow, loadingHide } = usePLoading(options);
27
+
28
+ const fireRequest = async (time) => {
29
+ const id = `default-${time}`;
30
+ loadingShow({ id });
31
+ await new Promise((resolve) => setTimeout(resolve, time));
32
+ loadingHide(id);
33
+ };
34
+
35
+ const fireRequestText = async (time, message) => {
36
+ const id = `text-${time}`;
37
+ loadingShow({ id, content: message });
38
+ await new Promise((resolve) => setTimeout(resolve, time));
39
+ loadingHide(id);
40
+ };
41
+
42
+ const fireRequestComponent = async (time) => {
43
+ const id = `component-${time}`;
44
+ loadingShow({ id, content: PSkeletonLoader });
45
+ await new Promise((resolve) => setTimeout(resolve, time));
46
+ loadingHide(id);
47
+ };
48
+
49
+ return { loadingShow, loadingHide, fireRequest, fireRequestText, fireRequestComponent };
50
+ },
29
51
  },
30
- });
52
+ {
53
+ global: {
54
+ stubs: {
55
+ PSkeletonLoader: true,
56
+ },
57
+ },
58
+ }
59
+ );
31
60
  };
32
61
 
33
62
  describe('PLoading.vue', () => {
@@ -82,6 +111,24 @@ describe('PLoading.vue', () => {
82
111
  expect(appWrapper.find('[aria-busy]').exists()).toBe(false);
83
112
  });
84
113
 
114
+ it(`hides all loading indicators when loadingHide gets called without an id`, async () => {
115
+ const wrapper = createWrapper({ delay: 200 });
116
+
117
+ await wrapper.find('.request-1-sec').trigger('click');
118
+
119
+ jest.advanceTimersByTime(500);
120
+ await waitNT(appWrapper.vm);
121
+ expect(appWrapper.find('[aria-busy]').exists()).toBe(true);
122
+
123
+ await wrapper.find('.request-3-secs').trigger('click');
124
+
125
+ jest.advanceTimersByTime(500);
126
+ await waitNT(appWrapper.vm);
127
+
128
+ await wrapper.find('.hide').trigger('click');
129
+ expect(appWrapper.find('[aria-busy]').exists()).toBe(false);
130
+ });
131
+
85
132
  it(`it waits for the queue to clear in order to hide the loading indicator`, async () => {
86
133
  const wrapper = createWrapper({ delay: 200 });
87
134
 
@@ -117,4 +164,58 @@ describe('PLoading.vue', () => {
117
164
  await waitNT(appWrapper.vm);
118
165
  expect(appWrapper.find('[aria-busy]').exists()).toBe(true);
119
166
  });
167
+
168
+ it(`shows the loading indicator with variable text`, async () => {
169
+ const wrapper = createWrapper({ delay: 200 });
170
+
171
+ await wrapper.find('.request-3-secs-text').trigger('click');
172
+
173
+ jest.advanceTimersByTime(300);
174
+ await waitNT(appWrapper.vm);
175
+ expect(appWrapper.find('[aria-busy]').text()).toBe('3 secs');
176
+
177
+ await wrapper.find('.request-1-sec-text').trigger('click');
178
+
179
+ jest.advanceTimersByTime(300);
180
+ await waitNT(appWrapper.vm);
181
+ expect(appWrapper.find('[aria-busy]').text()).toBe('1 sec');
182
+
183
+ jest.advanceTimersByTime(1200);
184
+ await waitNT(appWrapper.vm);
185
+ expect(appWrapper.find('[aria-busy]').text()).toBe('3 secs');
186
+
187
+ jest.advanceTimersByTime(1200);
188
+ await waitNT(appWrapper.vm);
189
+ expect(appWrapper.find('[aria-busy]').exists()).toBe(false);
190
+ });
191
+
192
+ it(`accepts a component as the content`, async () => {
193
+ const wrapper = createWrapper({ delay: 200 });
194
+
195
+ await wrapper.find('.request-1-sec-component').trigger('click');
196
+
197
+ jest.advanceTimersByTime(300);
198
+ await waitNT(appWrapper.vm);
199
+ expect(appWrapper.find('[aria-busy]').exists()).toBe(true);
200
+ expect(appWrapper.findComponent(PSkeletonLoader).exists()).toBe(true);
201
+
202
+ jest.advanceTimersByTime(800);
203
+ await waitNT(appWrapper.vm);
204
+ expect(appWrapper.find('[aria-busy]').exists()).toBe(false);
205
+ });
206
+
207
+ it(`throws an error when loadingShow gets called without an id`, async () => {
208
+ expect(() => {
209
+ createWrapperFor({
210
+ template: `<div></div>`,
211
+ setup() {
212
+ const { loadingShow } = usePLoading({ delay: 200 });
213
+
214
+ loadingShow({ content: 'test' });
215
+
216
+ return { loadingShow };
217
+ },
218
+ });
219
+ }).toThrowError('id is required');
220
+ });
120
221
  });
@@ -1,3 +1,4 @@
1
+ import ComputationStatus from '@/playground/components/ComputationStatus.vue';
1
2
  import PBtn from '@squirrel/components/p-btn/p-btn.vue';
2
3
  import PLoading from '@squirrel/components/p-loading/p-loading.vue';
3
4
  import { usePLoading } from '@squirrel/components/p-loading/usePLoading';
@@ -26,12 +27,13 @@ export const Default = {
26
27
  const { loadingShow, loadingHide } = usePLoading();
27
28
 
28
29
  const fireRequest = async (time) => {
29
- loadingShow();
30
+ const id = `default-${time}`;
31
+ loadingShow({ id });
30
32
  await new Promise((resolve) => setTimeout(resolve, time));
31
- loadingHide();
33
+ loadingHide(id);
32
34
  };
33
35
 
34
- return { args, loadingShow, loadingHide, fireRequest };
36
+ return { args, loadingShow, fireRequest };
35
37
  },
36
38
  template: `
37
39
  <PLoading />
@@ -47,3 +49,61 @@ export const Default = {
47
49
  `,
48
50
  }),
49
51
  };
52
+
53
+ export const WithVariableText = {
54
+ render: (args) => ({
55
+ components: { PLoading, PBtn },
56
+ setup() {
57
+ const { loadingShow, loadingHide } = usePLoading();
58
+
59
+ const fireRequestText = async (time, message) => {
60
+ const id = `text-${time}`;
61
+ loadingShow({ id, content: message });
62
+ await new Promise((resolve) => setTimeout(resolve, time));
63
+ loadingHide(id);
64
+ };
65
+
66
+ return { args, loadingShow, fireRequestText };
67
+ },
68
+ template: `
69
+ <PLoading />
70
+ <div class="mt-10">
71
+ <div>Demo with variable loading text</div>
72
+ <div class="flex gap-4 mt-2">
73
+ <PBtn @click="fireRequestText(1000, '1 sec')">Fire request 1 sec</PBtn>
74
+ <PBtn @click="fireRequestText(3000, '3 secs')">Fire request 3 secs</PBtn>
75
+ <PBtn @click="fireRequestText(5000, '5 secs')">Fire request 5 secs</PBtn>
76
+ </div>
77
+ </div>
78
+ `,
79
+ }),
80
+ };
81
+
82
+ export const WithComponentAsContent = {
83
+ render: (args) => ({
84
+ components: { PLoading, PBtn },
85
+ setup() {
86
+ const { loadingShow, loadingHide } = usePLoading();
87
+
88
+ const fireRequestComponent = async (time) => {
89
+ const id = `component-${time}`;
90
+ loadingShow({ id, content: ComputationStatus });
91
+ await new Promise((resolve) => setTimeout(resolve, time));
92
+ loadingHide(id);
93
+ };
94
+
95
+ return { args, loadingShow, fireRequestComponent };
96
+ },
97
+ template: `
98
+ <PLoading />
99
+ <div class="mt-10">
100
+ <div>Demo with a component as content</div>
101
+ <div class="flex gap-4 mt-2">
102
+ <PBtn @click="fireRequestComponent(1000)">Fire request 1 sec</PBtn>
103
+ <PBtn @click="fireRequestComponent(3000)">Fire request 3 secs</PBtn>
104
+ <PBtn @click="fireRequestComponent(5000)">Fire request 5 secs</PBtn>
105
+ </div>
106
+ </div>
107
+ `,
108
+ }),
109
+ };
@@ -2,22 +2,46 @@
2
2
  <Transition name="pm-backdrop-transition" enter-active-class="fadeInDown" leave-active-class="fadeOutUp">
3
3
  <div v-if="show" class="fixed left-0 top-0 z-[120] flex w-full justify-center" aria-live="polite" aria-busy="true">
4
4
  <div
5
- class="h-8 rounded-b border-x border-b border-p-gray-30 bg-p-blue-10 px-4 pt-2 text-sm font-semibold leading-none text-p-purple-60 shadow-sm"
5
+ :style="{ width: `${width}px` }"
6
+ class="h-8 overflow-hidden whitespace-nowrap rounded-b border-x border-b border-p-gray-30 bg-p-blue-10 px-4 pt-2 text-center text-sm font-semibold leading-none text-p-purple-60 shadow-sm transition-all duration-500"
6
7
  >
7
- Loading...
8
+ <Component :is="content" v-if="isComponent(content)" />
9
+ <template v-else>{{ content }}</template>
8
10
  </div>
9
11
  </div>
10
12
  </Transition>
13
+ <div v-if="content" class="invisible fixed">
14
+ <div ref="widthReference" class="inline-flex px-4">
15
+ <Component :is="content" v-if="isComponent(content)" />
16
+ <template v-else>{{ content }}</template>
17
+ </div>
18
+ </div>
11
19
  </template>
12
20
 
13
21
  <script setup lang="ts">
22
+ import { type Component, ref, toValue, watch } from 'vue';
14
23
  import { usePLoading } from '@squirrel/components/p-loading/usePLoading';
15
24
 
16
25
  defineOptions({
17
26
  name: 'PLoading',
18
27
  });
19
28
 
20
- const { show } = usePLoading();
29
+ const { show, content } = usePLoading();
30
+ const widthReference = ref<HTMLElement | null>(null);
31
+ const width = ref(0);
32
+
33
+ watch(
34
+ () => toValue(content),
35
+ () => {
36
+ if (widthReference.value) {
37
+ width.value = widthReference.value.offsetWidth;
38
+ }
39
+ },
40
+ { flush: 'post' }
41
+ );
42
+
43
+ const isComponent = (content: unknown): content is Component =>
44
+ typeof content === 'object' && content !== null && 'render' in content && typeof content.render === 'function';
21
45
  </script>
22
46
 
23
47
  <style scoped lang="scss">
@@ -1,20 +1,39 @@
1
- import { getCurrentScope, onScopeDispose, ref } from 'vue';
2
- import { uniqueId } from 'lodash-es';
1
+ import { type Component, type Ref, computed, getCurrentScope, markRaw, onScopeDispose, ref } from 'vue';
3
2
 
4
3
  type Options = {
5
4
  delay?: number;
6
5
  };
7
6
 
7
+ type Content = string | Component;
8
+
9
+ type LoadingItem = {
10
+ id: string;
11
+ content?: Content;
12
+ };
13
+
14
+ type UsePLoading = {
15
+ show: Ref<boolean>;
16
+ content: Content;
17
+ loadingShow: (loadingItem: LoadingItem) => void;
18
+ loadingHide: (id: LoadingItem['id']) => void;
19
+ };
20
+
21
+ const LOADING_TEXT = 'Loading...';
8
22
  const show = ref(false);
9
- const loadingIds: string[] = [];
23
+ const loadingItems = ref<LoadingItem[]>([]);
24
+ const content = computed(() => loadingItems.value[loadingItems.value.length - 1]?.content);
10
25
  let timer: ReturnType<typeof setTimeout>;
11
26
 
12
- export const usePLoading = (options?: Options) => {
27
+ export const usePLoading = (options?: Options): UsePLoading => {
13
28
  const { delay } = { delay: 200, ...options };
14
29
  const scope = getCurrentScope();
15
30
 
16
- const loadingShow = () => {
17
- if (loadingIds.length === 0 && delay > 0) {
31
+ const loadingShow = (loadingItem: LoadingItem) => {
32
+ if (!loadingItem.id) {
33
+ throw new Error('id is required');
34
+ }
35
+
36
+ if (loadingItems.value.length === 0 && delay > 0) {
18
37
  timer = setTimeout(() => {
19
38
  show.value = true;
20
39
  }, delay);
@@ -22,23 +41,30 @@ export const usePLoading = (options?: Options) => {
22
41
  show.value = true;
23
42
  }
24
43
 
25
- loadingIds.push(uniqueId());
44
+ loadingItems.value.push({
45
+ id: loadingItem.id,
46
+ content:
47
+ loadingItem.content && typeof loadingItem.content === 'object'
48
+ ? markRaw(loadingItem.content)
49
+ : loadingItem.content || LOADING_TEXT,
50
+ });
26
51
  };
27
52
 
28
- const loadingHide = () => {
29
- if (loadingIds.length === 1) {
53
+ const loadingHide = (id?: LoadingItem['id']) => {
54
+ loadingItems.value = id && typeof id === 'string' ? loadingItems.value.filter((item) => item.id !== id) : [];
55
+ if (loadingItems.value.length === 0) {
30
56
  show.value = false;
31
57
  }
32
-
33
58
  clearTimeout(timer);
34
- loadingIds.pop();
35
59
  };
36
60
 
37
61
  if (scope) {
38
62
  onScopeDispose(() => {
39
- loadingHide();
63
+ loadingItems.value = [];
64
+ show.value = false;
65
+ clearTimeout(timer);
40
66
  });
41
67
  }
42
68
 
43
- return { show, loadingShow, loadingHide };
69
+ return { show, content, loadingShow, loadingHide };
44
70
  };
@@ -27,7 +27,7 @@ export const createInternalValue = (
27
27
  if (multiple) {
28
28
  val = (modelValue as AnyObject[]).map((v) => v[itemValue as keyof typeof modelValue]) as AnyValue[];
29
29
  } else {
30
- val = modelValue ? modelValue[itemValue as keyof typeof modelValue] : modelValue;
30
+ val = modelValue ? (modelValue as AnyObject)[itemValue as keyof typeof modelValue] : modelValue;
31
31
  }
32
32
  }
33
33