@pequity/squirrel 5.4.1 → 5.4.2

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 (80) hide show
  1. package/dist/cjs/chunks/p-action-bar.js +10 -11
  2. package/dist/cjs/chunks/p-date-picker.js +108 -0
  3. package/dist/cjs/chunks/p-dropdown-select.js +7 -8
  4. package/dist/cjs/chunks/p-inline-date-picker.js +112 -0
  5. package/dist/cjs/chunks/p-input-percent.js +3 -5
  6. package/dist/cjs/chunks/p-pagination-info.js +1 -1
  7. package/dist/cjs/chunks/p-pagination.js +12 -16
  8. package/dist/cjs/index.js +47 -52
  9. package/dist/cjs/p-btn.js +8 -5
  10. package/dist/cjs/p-chips.js +3 -5
  11. package/dist/cjs/p-date-picker.js +2 -138
  12. package/dist/cjs/p-info-icon.js +1 -3
  13. package/dist/cjs/p-inline-date-picker.js +2 -143
  14. package/dist/cjs/p-table-td.js +3 -5
  15. package/dist/es/chunks/p-action-bar.js +10 -11
  16. package/dist/es/chunks/p-date-picker.js +109 -0
  17. package/dist/es/chunks/p-dropdown-select.js +7 -8
  18. package/dist/es/chunks/p-inline-date-picker.js +113 -0
  19. package/dist/es/chunks/p-input-percent.js +3 -5
  20. package/dist/es/chunks/p-pagination-info.js +1 -1
  21. package/dist/es/chunks/p-pagination.js +12 -16
  22. package/dist/es/index.js +96 -101
  23. package/dist/es/p-btn.js +8 -5
  24. package/dist/es/p-chips.js +4 -6
  25. package/dist/es/p-date-picker.js +2 -138
  26. package/dist/es/p-info-icon.js +2 -4
  27. package/dist/es/p-inline-date-picker.js +2 -143
  28. package/dist/es/p-table-td.js +4 -6
  29. package/dist/squirrel/components/p-action-bar/p-action-bar.vue.d.ts +5 -14
  30. package/dist/squirrel/components/p-alert/p-alert.vue.d.ts +4 -4
  31. package/dist/squirrel/components/p-avatar/p-avatar.vue.d.ts +5 -5
  32. package/dist/squirrel/components/p-btn/p-btn.vue.d.ts +40 -5
  33. package/dist/squirrel/components/p-card/p-card.vue.d.ts +4 -4
  34. package/dist/squirrel/components/p-checkbox/p-checkbox.vue.d.ts +5 -5
  35. package/dist/squirrel/components/p-chips/p-chips.vue.d.ts +5 -5
  36. package/dist/squirrel/components/p-close-btn/p-close-btn.vue.d.ts +4 -4
  37. package/dist/squirrel/components/p-date-picker/p-date-picker.vue.d.ts +35 -128
  38. package/dist/squirrel/components/p-drawer/p-drawer.vue.d.ts +37 -5
  39. package/dist/squirrel/components/p-dropdown/p-dropdown.vue.d.ts +5 -5
  40. package/dist/squirrel/components/p-dropdown-select/p-dropdown-select.vue.d.ts +128 -29
  41. package/dist/squirrel/components/p-file-upload/p-file-upload.vue.d.ts +5 -5
  42. package/dist/squirrel/components/p-icon/p-icon.vue.d.ts +1 -10
  43. package/dist/squirrel/components/p-info-icon/p-info-icon.vue.d.ts +2 -23
  44. package/dist/squirrel/components/p-input/p-input.vue.d.ts +9 -9
  45. package/dist/squirrel/components/p-input-number/p-input-number.vue.d.ts +40 -10
  46. package/dist/squirrel/components/p-input-percent/p-input-percent.vue.d.ts +5 -5
  47. package/dist/squirrel/components/p-input-search/p-input-search.vue.d.ts +121 -5
  48. package/dist/squirrel/components/p-link/p-link.vue.d.ts +1 -10
  49. package/dist/squirrel/components/p-loading/p-loading.vue.d.ts +1 -1
  50. package/dist/squirrel/components/p-modal/p-modal.vue.d.ts +5 -5
  51. package/dist/squirrel/components/p-pagination/p-pagination.vue.d.ts +5 -5
  52. package/dist/squirrel/components/p-pagination-info/p-pagination-info.vue.d.ts +4 -4
  53. package/dist/squirrel/components/p-progress-bar/p-progress-bar.vue.d.ts +3 -3
  54. package/dist/squirrel/components/p-ring-loader/p-ring-loader.vue.d.ts +4 -4
  55. package/dist/squirrel/components/p-select/p-select.vue.d.ts +5 -5
  56. package/dist/squirrel/components/p-select-btn/p-select-btn.vue.d.ts +4 -45
  57. package/dist/squirrel/components/p-select-list/p-select-list.vue.d.ts +128 -29
  58. package/dist/squirrel/components/p-select-list/useSelectList.d.ts +4 -4
  59. package/dist/squirrel/components/p-select-pill/p-select-pill.vue.d.ts +5 -5
  60. package/dist/squirrel/components/p-skeleton-loader/p-skeleton-loader.vue.d.ts +4 -4
  61. package/dist/squirrel/components/p-table/p-table.vue.d.ts +10 -47
  62. package/dist/squirrel/components/p-table/usePTableColResize.d.ts +4 -4
  63. package/dist/squirrel/components/p-table/usePTableRowVirtualizer.d.ts +10 -4
  64. package/dist/squirrel/components/p-table-header-cell/p-table-filter-icon.vue.d.ts +4 -4
  65. package/dist/squirrel/components/p-table-header-cell/p-table-header-cell.vue.d.ts +48 -5
  66. package/dist/squirrel/components/p-table-loader/p-table-loader.vue.d.ts +4 -4
  67. package/dist/squirrel/components/p-table-sort/p-table-sort.vue.d.ts +5 -5
  68. package/dist/squirrel/components/p-table-td/p-table-td.vue.d.ts +2 -25
  69. package/dist/squirrel/components/p-tabs/p-tabs.vue.d.ts +5 -5
  70. package/dist/squirrel/components/p-textarea/p-textarea.vue.d.ts +9 -9
  71. package/dist/squirrel/components/p-toggle/p-toggle.vue.d.ts +9 -9
  72. package/dist/squirrel/composables/useInputClasses.d.ts +1 -1
  73. package/dist/squirrel/utils/inputClassesMixin.d.ts +4 -4
  74. package/package.json +24 -24
  75. package/squirrel/components/p-btn/p-btn.spec.js +33 -4
  76. package/squirrel/components/p-btn/p-btn.vue +5 -2
  77. package/squirrel/components/p-date-picker/p-date-picker.vue +79 -86
  78. package/squirrel/components/p-inline-date-picker/p-inline-date-picker.spec.js +12 -14
  79. package/squirrel/components/p-inline-date-picker/p-inline-date-picker.vue +87 -99
  80. package/dist/squirrel/components/p-inline-date-picker/p-inline-date-picker.vue.d.ts +0 -154
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@pequity/squirrel",
3
3
  "description": "Squirrel component library",
4
- "version": "5.4.1",
5
- "packageManager": "pnpm@9.11.0",
4
+ "version": "5.4.2",
5
+ "packageManager": "pnpm@9.12.1",
6
6
  "type": "module",
7
7
  "scripts": {
8
8
  "preinstall": "npx only-allow pnpm",
@@ -53,33 +53,33 @@
53
53
  "@commitlint/cli": "^19.5.0",
54
54
  "@commitlint/config-conventional": "^19.5.0",
55
55
  "@pequity/eslint-config": "^0.0.13",
56
- "@playwright/test": "^1.47.2",
56
+ "@playwright/test": "^1.48.0",
57
57
  "@popperjs/core": "2.11.8",
58
58
  "@semantic-release/changelog": "^6.0.3",
59
59
  "@semantic-release/git": "^10.0.1",
60
- "@storybook/addon-a11y": "^8.3.3",
61
- "@storybook/addon-actions": "^8.3.3",
62
- "@storybook/addon-essentials": "^8.3.3",
63
- "@storybook/addon-interactions": "^8.3.3",
64
- "@storybook/addon-links": "^8.3.3",
65
- "@storybook/blocks": "^8.3.3",
66
- "@storybook/manager-api": "^8.3.3",
67
- "@storybook/test": "^8.3.3",
60
+ "@storybook/addon-a11y": "^8.3.5",
61
+ "@storybook/addon-actions": "^8.3.5",
62
+ "@storybook/addon-essentials": "^8.3.5",
63
+ "@storybook/addon-interactions": "^8.3.5",
64
+ "@storybook/addon-links": "^8.3.5",
65
+ "@storybook/blocks": "^8.3.5",
66
+ "@storybook/manager-api": "^8.3.5",
67
+ "@storybook/test": "^8.3.5",
68
68
  "@storybook/test-runner": "^0.19.1",
69
- "@storybook/theming": "^8.3.3",
70
- "@storybook/vue3": "^8.3.3",
71
- "@storybook/vue3-vite": "^8.3.3",
69
+ "@storybook/theming": "^8.3.5",
70
+ "@storybook/vue3": "^8.3.5",
71
+ "@storybook/vue3-vite": "^8.3.5",
72
72
  "@tanstack/vue-virtual": "3.10.8",
73
73
  "@types/jsdom": "^21.1.7",
74
74
  "@types/lodash-es": "^4.17.12",
75
- "@types/node": "^22.7.3",
75
+ "@types/node": "^22.7.5",
76
76
  "@vitejs/plugin-vue": "^5.1.4",
77
- "@vitest/coverage-v8": "^2.1.1",
78
- "@vue/compiler-sfc": "3.4.38",
77
+ "@vitest/coverage-v8": "^2.1.3",
78
+ "@vue/compiler-sfc": "3.5.12",
79
79
  "@vue/test-utils": "^2.4.6",
80
80
  "autoprefixer": "^10.4.20",
81
81
  "dayjs": "1.11.13",
82
- "eslint": "^8.57.0",
82
+ "eslint": "^8.57.1",
83
83
  "eslint-plugin-storybook": "^0.9.0",
84
84
  "floating-vue": "5.2.2",
85
85
  "glob": "^11.0.0",
@@ -94,16 +94,16 @@
94
94
  "prettier-plugin-tailwindcss": "^0.6.8",
95
95
  "resolve-tspaths": "^0.8.22",
96
96
  "rimraf": "^6.0.1",
97
- "sass": "^1.79.3",
97
+ "sass": "^1.79.5",
98
98
  "semantic-release": "^24.1.2",
99
- "storybook": "^8.3.3",
99
+ "storybook": "^8.3.5",
100
100
  "svgo": "^3.3.2",
101
101
  "tailwindcss": "^3.4.13",
102
- "typescript": "5.6.2",
102
+ "typescript": "5.6.3",
103
103
  "v-calendar": "3.1.2",
104
- "vite": "^5.4.8",
105
- "vitest": "^2.1.1",
106
- "vue": "3.4.38",
104
+ "vite": "^5.4.9",
105
+ "vitest": "^2.1.3",
106
+ "vue": "3.5.12",
107
107
  "vue-currency-input": "3.1.0",
108
108
  "vue-router": "4.4.5",
109
109
  "vue-toastification": "2.0.0-rc.5",
@@ -14,6 +14,18 @@ const ELEMENTS_MAP = {
14
14
  section: { name: 'home', params: { id: 1 } },
15
15
  };
16
16
 
17
+ const DEFAULT_CLASSES_ARRAY = [
18
+ 'relative',
19
+ 'inline-block',
20
+ 'outline-none',
21
+ 'disabled:opacity-50',
22
+ 'disabled:cursor-default',
23
+ 'disabled:pointer-events-none',
24
+ 'aria-disabled:opacity-50',
25
+ 'aria-disabled:cursor-default',
26
+ 'aria-disabled:pointer-events-none',
27
+ ];
28
+
17
29
  describe('PBtn.vue', () => {
18
30
  Object.keys(ELEMENTS_MAP).forEach((el) => {
19
31
  const to = ELEMENTS_MAP[el];
@@ -66,6 +78,7 @@ describe('PBtn.vue', () => {
66
78
  const element = await wrapper.find(el);
67
79
 
68
80
  expect(classes.every((c) => element.classes().includes(c))).toBe(true);
81
+ expect(DEFAULT_CLASSES_ARRAY.every((c) => element.classes().includes(c))).toBe(true);
69
82
  });
70
83
  });
71
84
 
@@ -81,26 +94,42 @@ describe('PBtn.vue', () => {
81
94
  expect(button.text()).toContain(`This is a button`);
82
95
  });
83
96
 
84
- it('gets disabled when the disabled attr is set to true', async () => {
97
+ it.each([
98
+ ['button', undefined],
99
+ ['a', 'https://pequity.com/'],
100
+ ['a', { name: 'home', params: { id: 1 } }],
101
+ ])('gets disabled when the element is a %s and points to %s', async (el, to) => {
85
102
  const wrapper = createWrapperFor(PBtn, {
103
+ props: {
104
+ to,
105
+ },
86
106
  attrs: {
87
107
  disabled: true,
88
108
  },
109
+ global: {
110
+ stubs: { RouterLink: { template: '<a class="router-link-stub"><slot /></a>' } },
111
+ },
89
112
  });
90
113
 
91
- const button = await wrapper.find('button');
114
+ const button = await wrapper.find(el);
92
115
 
93
116
  await button.trigger('click');
94
117
 
95
- expect(wrapper.emitted().click).toBeFalsy();
118
+ if (el === 'button') {
119
+ expect(wrapper.emitted().click).toBeFalsy();
120
+ }
96
121
  expect(button.attributes()).toHaveProperty('disabled');
122
+ expect(button.attributes()['aria-disabled']).toBe('true');
97
123
 
98
124
  await wrapper.setProps({ disabled: false });
99
125
 
100
126
  await button.trigger('click');
101
127
 
102
- expect(wrapper.emitted().click[0][0] instanceof MouseEvent).toBe(true);
128
+ if (el === 'button') {
129
+ expect(wrapper.emitted().click[0][0] instanceof MouseEvent).toBe(true);
130
+ }
103
131
  expect(button.attributes()).not.toHaveProperty('disabled');
132
+ expect(button.attributes()['aria-disabled']).toBe('false');
104
133
  });
105
134
 
106
135
  it('has a loading state', async () => {
@@ -5,6 +5,8 @@
5
5
  :href="sanitizeUrl(to)"
6
6
  target="_blank"
7
7
  :class="classes"
8
+ :disabled="!!$attrs.disabled ? true : null"
9
+ :aria-disabled="!!$attrs.disabled"
8
10
  >
9
11
  <slot></slot>
10
12
  </a>
@@ -16,7 +18,8 @@
16
18
  :aria-selected="selected"
17
19
  :class="classes"
18
20
  v-bind="$attrs"
19
- :disabled="($attrs.disabled as boolean) || loading"
21
+ :disabled="!!$attrs.disabled || loading ? true : null"
22
+ :aria-disabled="$attrs.disabled"
20
23
  >
21
24
  <div :class="{ invisible: loading }"><slot></slot></div>
22
25
  <PRingLoader
@@ -57,7 +60,7 @@ type ButtonNativeType = (typeof BUTTON_NATIVE_TYPES)[number];
57
60
  type ButtonTypeKeys = keyof typeof BUTTON_TYPES;
58
61
  type ButtonType = (typeof BUTTON_TYPES)[ButtonTypeKeys];
59
62
 
60
- const DEFAULT_CLASSES = `relative inline-block outline-none disabled:opacity-50 disabled:cursor-default disabled:pointer-events-none`;
63
+ const DEFAULT_CLASSES = `relative inline-block outline-none disabled:opacity-50 disabled:cursor-default disabled:pointer-events-none aria-disabled:opacity-50 aria-disabled:cursor-default aria-disabled:pointer-events-none`;
61
64
 
62
65
  type ButtonClass = {
63
66
  [key in ButtonType]: string;
@@ -15,10 +15,9 @@
15
15
  :timezone="timezone"
16
16
  >
17
17
  <template #default="{ inputValue, inputEvents }">
18
- <input
19
- :class="[inputClasses, { 'cursor-pointer': !attrs.disabled }]"
18
+ <PInput
20
19
  :value="inputValue"
21
- v-bind="attrs"
20
+ v-bind="attrsWithoutClassAndStyle"
22
21
  :placeholder="displayPlaceholder"
23
22
  v-on="inputEvents"
24
23
  />
@@ -28,12 +27,18 @@
28
27
  </div>
29
28
  </template>
30
29
 
31
- <script lang="ts">
30
+ <script setup lang="ts">
31
+ import PInput from '@squirrel/components/p-input/p-input.vue';
32
32
  import dayjs from 'dayjs';
33
- import inputClassesMixin from '@squirrel/utils/inputClassesMixin';
34
33
  import { DatePicker } from 'v-calendar';
35
- import { type PropType, type StyleValue, defineComponent } from 'vue';
34
+ import { type StyleValue, computed, nextTick, ref, useAttrs, watch } from 'vue';
36
35
  import { isDate, isString } from 'lodash-es';
36
+ import { useInputClasses } from '@squirrel/composables/useInputClasses';
37
+
38
+ defineOptions({
39
+ name: 'PDatePicker',
40
+ inheritAttrs: false,
41
+ });
37
42
 
38
43
  // The type of the select attribute is defined in node_modules/v-calendar/dist/types/src/utils/attribute.d.ts
39
44
  // but there was no way to import it, so as a workaround we cast the selectAttribute as `any`.
@@ -47,87 +52,75 @@ const DEFAULT_MASKS = {
47
52
  data: 'YYYY-MM-DD',
48
53
  };
49
54
 
50
- export default defineComponent({
51
- name: 'PDatePicker',
52
- components: {
53
- DatePicker,
54
- },
55
- mixins: [inputClassesMixin],
56
- inheritAttrs: false,
57
- props: {
58
- modelValue: {
59
- type: String as PropType<string | null | undefined>,
60
- default: '',
61
- },
62
- label: {
63
- type: String,
64
- default: '',
65
- },
66
- errorMsg: {
67
- type: String,
68
- default: '',
69
- },
70
- required: {
71
- type: Boolean,
72
- default: false,
73
- },
74
- minDate: {
75
- type: Date,
76
- default: null,
77
- },
78
- maxDate: {
79
- type: Date,
80
- default: null,
81
- },
82
- timezone: {
83
- type: String,
84
- default: '',
85
- },
86
- },
87
- emits: ['update:modelValue'],
88
- data() {
89
- return {
90
- // innerValue is a Date object
91
- innerValue: null as Date | null,
92
- stopWatch: false,
93
- selectAttribute,
94
- };
95
- },
96
- computed: {
97
- displayPlaceholder(): string {
98
- return isString(this.$attrs.placeholder) ? this.$attrs.placeholder : this.masks.input;
99
- },
100
- masks() {
101
- return Object.assign(DEFAULT_MASKS, this.$attrs.masks);
102
- },
103
- attrs() {
104
- const { class: classes, style, ...rest } = this.$attrs;
55
+ type Props = {
56
+ modelValue?: string | null;
57
+ label?: string;
58
+ errorMsg?: string;
59
+ required?: boolean;
60
+ minDate?: Date | null;
61
+ maxDate?: Date | null;
62
+ timezone?: string;
63
+ };
105
64
 
106
- return rest;
107
- },
108
- style() {
109
- return this.$attrs.style as StyleValue;
110
- },
111
- },
112
- watch: {
113
- modelValue: {
114
- handler(nV) {
115
- if (!this.stopWatch) {
116
- this.innerValue = nV ? dayjs(nV, this.masks.data).toDate() : null;
117
- }
118
- },
119
- immediate: true,
120
- },
121
- innerValue(nV) {
122
- // We're emitting back a formatted String (value) or null in case we have an invalid date
123
- const toEmit = isDate(nV) && nV.toString() !== 'Invalid Date' ? dayjs(nV).format(this.masks.data) : null;
124
- // Stop watching when updating the value by clicking in the datepicker, in order to prevent double-emit.
125
- this.stopWatch = true;
126
- this.$emit('update:modelValue', toEmit);
127
- this.$nextTick(() => {
128
- this.stopWatch = false;
129
- });
130
- },
65
+ const props = withDefaults(defineProps<Props>(), {
66
+ modelValue: '',
67
+ label: '',
68
+ errorMsg: '',
69
+ required: false,
70
+ minDate: null,
71
+ maxDate: null,
72
+ timezone: '',
73
+ });
74
+
75
+ const emit = defineEmits<{
76
+ 'update:modelValue': [value: string | null];
77
+ }>();
78
+
79
+ // Data
80
+ const { labelClasses, errorMsgClasses } = useInputClasses(props);
81
+ const attrs = useAttrs();
82
+ // innerValue is a Date object
83
+ const innerValue = ref<Date | null>(null);
84
+ const stopWatch = ref(false);
85
+
86
+ // Computed
87
+ const masks = computed(() => {
88
+ return Object.assign(DEFAULT_MASKS, attrs.masks);
89
+ });
90
+
91
+ const displayPlaceholder = computed(() => {
92
+ return isString(attrs.placeholder) ? attrs.placeholder : masks.value.input;
93
+ });
94
+
95
+ const attrsWithoutClassAndStyle = computed(() => {
96
+ const { class: classes, style, ...rest } = attrs;
97
+
98
+ return rest;
99
+ });
100
+
101
+ const style = computed(() => {
102
+ return attrs.style as StyleValue;
103
+ });
104
+
105
+ // Watch
106
+ watch(
107
+ () => props.modelValue,
108
+ (nV) => {
109
+ if (!stopWatch.value) {
110
+ innerValue.value = nV ? dayjs(nV, masks.value.data).toDate() : null;
111
+ }
131
112
  },
113
+ { immediate: true }
114
+ );
115
+
116
+ watch(innerValue, (nV) => {
117
+ // We're emitting back a formatted String (value) or null in case we have an invalid date
118
+ const toEmit = isDate(nV) && nV.toString() !== 'Invalid Date' ? dayjs(nV).format(masks.value.data) : null;
119
+ // Stop watching when updating the value by clicking in the datepicker, in order to prevent double-emit.
120
+ stopWatch.value = true;
121
+ emit('update:modelValue', toEmit);
122
+ nextTick(() => {
123
+ stopWatch.value = false;
124
+ });
132
125
  });
133
126
  </script>
@@ -1,4 +1,5 @@
1
1
  import PInlineDatePicker from '@squirrel/components/p-inline-date-picker/p-inline-date-picker.vue';
2
+ import { DatePicker } from 'v-calendar';
2
3
  import { createWrapperFor } from '@tests/vitest.helpers';
3
4
 
4
5
  const createWrapper = (props) => {
@@ -20,17 +21,17 @@ describe('PInlineDatePicker.vue', () => {
20
21
  it('renders a datepicker', () => {
21
22
  const wrapper = createWrapper();
22
23
 
23
- const datePicker = wrapper.findComponent({ name: 'DatePicker' });
24
+ const datePicker = wrapper.findComponent(DatePicker);
24
25
 
25
- expect(datePicker.props()).toEqual({
26
+ expect(datePicker.props()).toMatchObject({
26
27
  modelValue: null,
27
28
  selectAttribute: { highlight: { class: 'bg-primary', contentClass: 'text-white' } },
28
29
  minDate: null,
29
30
  maxDate: null,
30
31
  masks: { input: 'DD-MMM-YYYY', data: 'YYYY-MM-DD' },
31
- style: {},
32
32
  timezone: '',
33
33
  });
34
+
34
35
  expect(wrapper.find('label').exists()).toBe(false);
35
36
  expect(wrapper.find('div.text-xs.text-on-error.mt-1').isVisible()).toBe(false);
36
37
  });
@@ -43,7 +44,7 @@ describe('PInlineDatePicker.vue', () => {
43
44
  timezone: 'UTC',
44
45
  });
45
46
 
46
- const datePicker = wrapper.findComponent({ name: 'DatePicker' });
47
+ const datePicker = wrapper.findComponent(DatePicker);
47
48
 
48
49
  expect(datePicker.props().modelValue).toEqual(new Date('2024-05-19'));
49
50
  expect(datePicker.props().minDate).toEqual(new Date('2024-05-01'));
@@ -77,7 +78,6 @@ describe('PInlineDatePicker.vue', () => {
77
78
 
78
79
  expect(wrapper.props().required).toBe(true);
79
80
  });
80
-
81
81
  it('passes listeners to the datepicker', () => {
82
82
  // Since DatePicker emits are not defined on PInlineDatePicker we need to Spy on console.warn and mock the implementation to suppress the warning:
83
83
  // [Vue warn]: Component emitted event "update:view" but it is neither declared in the emits option nor as an "onUpdate:view" prop.
@@ -100,7 +100,7 @@ describe('PInlineDatePicker.vue', () => {
100
100
  },
101
101
  });
102
102
 
103
- const datePicker = wrapper.findComponent({ name: 'DatePicker' });
103
+ const datePicker = wrapper.findComponent(DatePicker);
104
104
 
105
105
  datePicker.vm.$emit('update:view');
106
106
 
@@ -112,9 +112,9 @@ describe('PInlineDatePicker.vue', () => {
112
112
  it('sets the disabled state correctly', () => {
113
113
  const wrapper = createWrapper({ label: 'test datepicker', disabled: true });
114
114
 
115
- const datePicker = wrapper.findComponent({ name: 'DatePicker' });
115
+ const datePickerDiv = wrapper.find('.vc-container');
116
116
 
117
- expect(['pointer-events-none', 'opacity-70'].every((c) => datePicker.classes().includes(c))).toBe(true);
117
+ expect(['pointer-events-none', 'opacity-70'].every((c) => datePickerDiv.classes().includes(c))).toBe(true);
118
118
  });
119
119
 
120
120
  it(`updates the value when a day is clicked`, async () => {
@@ -128,7 +128,7 @@ describe('PInlineDatePicker.vue', () => {
128
128
  },
129
129
  });
130
130
 
131
- const datePicker = wrapper.findComponent({ name: 'DatePicker' });
131
+ const datePicker = wrapper.findComponent(DatePicker);
132
132
 
133
133
  datePicker.vm.$emit('dayclick', { id: '2024-09-02' });
134
134
 
@@ -138,7 +138,7 @@ describe('PInlineDatePicker.vue', () => {
138
138
  it(`updates the datepicker value when modelValue is changed`, async () => {
139
139
  const wrapper = createWrapper({ modelValue: '2024-09-01' });
140
140
 
141
- const datePicker = wrapper.findComponent({ name: 'DatePicker' });
141
+ const datePicker = wrapper.findComponent(DatePicker);
142
142
  datePicker.vm.move = vi.fn();
143
143
 
144
144
  expect(datePicker.props().modelValue).toEqual(new Date('2024-09-01'));
@@ -156,11 +156,9 @@ describe('PInlineDatePicker.vue', () => {
156
156
  });
157
157
 
158
158
  expect(wrapper.attributes()['data-has-error']).toBeDefined();
159
- const datePicker = wrapper.findComponent({ name: 'DatePicker' });
159
+ const datePickerDiv = wrapper.find('.vc-container');
160
160
 
161
- expect(datePicker.props().style).toEqual({
162
- border: '1px solid var(--color-on-error)',
163
- });
161
+ expect(datePickerDiv.element.style.border).toBe('1px solid #f0453c');
164
162
  const errorDiv = wrapper.find('div.text-xs.text-on-error.mt-1');
165
163
  expect(errorDiv.isVisible()).toBe(true);
166
164
  expect(errorDiv.text()).toBe('datepicker has error');
@@ -6,7 +6,7 @@
6
6
  </label>
7
7
  </slot>
8
8
  <DatePicker
9
- ref="datepicker"
9
+ ref="datepickerRef"
10
10
  :class="{ 'pointer-events-none opacity-70': $attrs.disabled }"
11
11
  :model-value="innerValue"
12
12
  :select-attribute="selectAttribute"
@@ -22,12 +22,17 @@
22
22
  </div>
23
23
  </template>
24
24
 
25
- <script lang="ts">
25
+ <script setup lang="ts">
26
26
  import dayjs from 'dayjs';
27
- import inputClassesMixin from '@squirrel/utils/inputClassesMixin';
28
27
  import { type AttributeConfig } from 'v-calendar/dist/types/src/utils/attribute.d';
29
28
  import { DatePicker } from 'v-calendar';
30
- import { type PropType, type StyleValue, defineComponent } from 'vue';
29
+ import { type StyleValue, computed, ref, useAttrs, watch } from 'vue';
30
+ import { useInputClasses } from '@squirrel/composables/useInputClasses';
31
+
32
+ defineOptions({
33
+ name: 'PInlineDatePicker',
34
+ inheritAttrs: false,
35
+ });
31
36
 
32
37
  const selectAttribute: AttributeConfig = {
33
38
  highlight: { class: 'bg-primary', contentClass: 'text-white' },
@@ -40,100 +45,83 @@ const DEFAULT_MASKS = {
40
45
  data: 'YYYY-MM-DD',
41
46
  };
42
47
 
43
- export default defineComponent({
44
- name: 'PInlineDatePicker',
45
- components: {
46
- DatePicker,
47
- },
48
- mixins: [inputClassesMixin],
49
- inheritAttrs: false,
50
- props: {
51
- modelValue: {
52
- type: String as PropType<string | null | undefined>,
53
- default: '',
54
- },
55
- label: {
56
- type: String,
57
- default: '',
58
- },
59
- errorMsg: {
60
- type: String,
61
- default: '',
62
- },
63
- required: {
64
- type: Boolean,
65
- default: false,
66
- },
67
- minDate: {
68
- type: Date,
69
- default: null,
70
- },
71
- maxDate: {
72
- type: Date,
73
- default: null,
74
- },
75
- timezone: {
76
- type: String,
77
- default: '',
78
- },
79
- },
80
- emits: ['update:modelValue'],
81
- data() {
82
- return {
83
- // innerValue is a Date object
84
- innerValue: null as Date | null,
85
- selectAttribute,
86
- };
87
- },
88
- computed: {
89
- masks() {
90
- return Object.assign(DEFAULT_MASKS, this.$attrs.masks);
91
- },
92
- attrs() {
93
- const { class: classes, style, ...rest } = this.$attrs;
94
-
95
- return rest;
96
- },
97
- listeners() {
98
- return Object.keys(this.$attrs).reduce((acc, curr) => {
99
- if (curr.startsWith('on')) {
100
- return { ...acc, [curr]: this.$attrs[curr] };
101
- } else {
102
- return acc;
103
- }
104
- }, {});
105
- },
106
- style() {
107
- return this.$attrs.style as StyleValue;
108
- },
109
- styleDatepicker() {
110
- return this.errorMsg ? { border: '1px solid var(--color-on-error)' } : {};
111
- },
112
- },
113
- watch: {
114
- modelValue: {
115
- async handler(nV) {
116
- const date = dayjs(nV, this.masks.data).toDate();
117
-
118
- if (nV && date.toString() === 'Invalid Date') {
119
- this.$emit('update:modelValue', null);
120
- return;
121
- }
122
-
123
- this.innerValue = nV ? dayjs(nV, this.masks.data).toDate() : null;
124
- const datepicker = this.$refs.datepicker as { move: (val: unknown) => void };
125
-
126
- if (datepicker && typeof datepicker.move === 'function' && this.innerValue) {
127
- await datepicker.move(this.innerValue);
128
- }
129
- },
130
- immediate: true,
131
- },
132
- },
133
- methods: {
134
- dayclick(e: { id: string }) {
135
- this.$emit('update:modelValue', e.id);
136
- },
137
- },
48
+ type Props = {
49
+ modelValue?: string | null;
50
+ label?: string;
51
+ errorMsg?: string;
52
+ required?: boolean;
53
+ minDate?: Date | null;
54
+ maxDate?: Date | null;
55
+ timezone?: string;
56
+ };
57
+
58
+ const props = withDefaults(defineProps<Props>(), {
59
+ modelValue: '',
60
+ label: '',
61
+ errorMsg: '',
62
+ required: false,
63
+ minDate: null,
64
+ maxDate: null,
65
+ timezone: '',
138
66
  });
67
+
68
+ const emit = defineEmits<{
69
+ 'update:modelValue': [value: string | null];
70
+ }>();
71
+
72
+ // Data
73
+ const { labelClasses, errorMsgClasses } = useInputClasses(props);
74
+ const attrs = useAttrs();
75
+ // innerValue is a Date object
76
+ const innerValue = ref<Date | null>(null);
77
+ const datepickerRef = ref<InstanceType<typeof DatePicker> | null>(null);
78
+
79
+ // Computed
80
+ const masks = computed(() => {
81
+ return Object.assign(DEFAULT_MASKS, attrs.masks);
82
+ });
83
+
84
+ const listeners = computed(() => {
85
+ return Object.keys(attrs).reduce((acc, curr) => {
86
+ if (curr.startsWith('on')) {
87
+ return { ...acc, [curr]: attrs[curr] };
88
+ } else {
89
+ return acc;
90
+ }
91
+ }, {});
92
+ });
93
+
94
+ const style = computed(() => {
95
+ return attrs.style as StyleValue;
96
+ });
97
+
98
+ const styleDatepicker = computed(() => {
99
+ return props.errorMsg ? { border: '1px solid #f0453c' } : {};
100
+ });
101
+
102
+ // Methods
103
+ const dayclick = (e: { id: string }) => {
104
+ emit('update:modelValue', e.id);
105
+ };
106
+
107
+ // Watch
108
+ watch(
109
+ () => props.modelValue,
110
+ async (nV) => {
111
+ const date = dayjs(nV, masks.value.data).toDate();
112
+
113
+ if (nV && date.toString() === 'Invalid Date') {
114
+ emit('update:modelValue', null);
115
+ return;
116
+ }
117
+
118
+ innerValue.value = nV ? dayjs(nV, masks.value.data).toDate() : null;
119
+ const datepicker = datepickerRef.value as { move: (val: unknown) => void };
120
+
121
+ if (datepicker && typeof datepicker.move === 'function' && innerValue.value) {
122
+ await datepicker.move(innerValue.value);
123
+ }
124
+ },
125
+ { immediate: true }
126
+ );
139
127
  </script>