@citizenplane/pimp 10.0.2 → 10.0.4
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.
- package/dist/pimp.es.js +4347 -4340
- package/dist/pimp.umd.js +44 -44
- package/dist/style.css +1 -1
- package/package.json +20 -21
- package/src/components/CpTable.vue +79 -21
- package/src/components/CpTableColumnEditor.vue +5 -1
- package/src/stories/CpTable.stories.ts +10 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@citizenplane/pimp",
|
|
3
|
-
"version": "10.0.
|
|
3
|
+
"version": "10.0.4",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "storybook dev -p 8080",
|
|
6
6
|
"build-storybook": "storybook build --output-dir ./docs",
|
|
@@ -39,15 +39,14 @@
|
|
|
39
39
|
"maska": "^3.1.1",
|
|
40
40
|
"modern-normalize": "^3.0.1",
|
|
41
41
|
"primevue": "^4.5.4",
|
|
42
|
-
"vue": "^3.5.
|
|
42
|
+
"vue": "^3.5.27",
|
|
43
43
|
"vue-bind-once": "^0.2.1",
|
|
44
44
|
"vue-tel-input": "^9.5.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@babel/core": "^7.28.
|
|
48
|
-
"@
|
|
49
|
-
"@commitlint/
|
|
50
|
-
"@commitlint/config-conventional": "^20.2.0",
|
|
47
|
+
"@babel/core": "^7.28.6",
|
|
48
|
+
"@commitlint/cli": "^20.3.1",
|
|
49
|
+
"@commitlint/config-conventional": "^20.3.1",
|
|
51
50
|
"@eslint/eslintrc": "^3.3.3",
|
|
52
51
|
"@eslint/js": "^9.39.2",
|
|
53
52
|
"@storybook/addon-onboarding": "^9.1.17",
|
|
@@ -55,14 +54,14 @@
|
|
|
55
54
|
"@storybook/preset-scss": "^1.0.3",
|
|
56
55
|
"@storybook/vue3": "^9.1.17",
|
|
57
56
|
"@storybook/vue3-vite": "^9.1.17",
|
|
58
|
-
"@stylistic/eslint-plugin": "^5.
|
|
57
|
+
"@stylistic/eslint-plugin": "^5.7.1",
|
|
59
58
|
"@types/feather-icons": "^4.29.4",
|
|
60
59
|
"@types/vue-tel-input": "^2.1.7",
|
|
61
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
62
|
-
"@typescript-eslint/parser": "^8.
|
|
60
|
+
"@typescript-eslint/eslint-plugin": "^8.54.0",
|
|
61
|
+
"@typescript-eslint/parser": "^8.54.0",
|
|
63
62
|
"@vitejs/plugin-vue": "^6.0.3",
|
|
64
|
-
"@vitest/browser": "^
|
|
65
|
-
"@vitest/coverage-v8": "^
|
|
63
|
+
"@vitest/browser": "^4.0.18",
|
|
64
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
66
65
|
"@vue/babel-preset-app": "^5.0.8",
|
|
67
66
|
"@vue/test-utils": "^2.4.6",
|
|
68
67
|
"@vue/vue3-jest": "^29.2.6",
|
|
@@ -71,24 +70,24 @@
|
|
|
71
70
|
"cz-conventional-changelog": "^3.3.0",
|
|
72
71
|
"eslint": "^9.39.2",
|
|
73
72
|
"eslint-config-prettier": "^10.1.8",
|
|
74
|
-
"eslint-plugin-perfectionist": "^5.
|
|
75
|
-
"eslint-plugin-prettier": "^5.5.
|
|
73
|
+
"eslint-plugin-perfectionist": "^5.4.0",
|
|
74
|
+
"eslint-plugin-prettier": "^5.5.5",
|
|
76
75
|
"eslint-plugin-storybook": "^9.1.17",
|
|
77
|
-
"eslint-plugin-vue": "^10.
|
|
78
|
-
"globals": "^
|
|
76
|
+
"eslint-plugin-vue": "^10.7.0",
|
|
77
|
+
"globals": "^17.2.0",
|
|
79
78
|
"husky": "^9.1.7",
|
|
80
79
|
"jest": "~29.7.0",
|
|
81
80
|
"jest-environment-jsdom": "^30.2.0",
|
|
82
81
|
"lint-staged": "^16.2.7",
|
|
83
|
-
"playwright": "^1.
|
|
84
|
-
"prettier": "^3.
|
|
85
|
-
"sass": "~1.97.
|
|
82
|
+
"playwright": "^1.58.0",
|
|
83
|
+
"prettier": "^3.8.1",
|
|
84
|
+
"sass": "~1.97.3",
|
|
86
85
|
"sass-loader": "^16.0.6",
|
|
87
86
|
"storybook": "^9.1.17",
|
|
88
87
|
"ts-jest": "^29.4.6",
|
|
89
|
-
"typescript-eslint": "^8.
|
|
90
|
-
"vite": "^7.3.
|
|
91
|
-
"vitest": "^
|
|
88
|
+
"typescript-eslint": "^8.54.0",
|
|
89
|
+
"vite": "^7.3.1",
|
|
90
|
+
"vitest": "^4.0.18"
|
|
92
91
|
},
|
|
93
92
|
"eslintConfig": {
|
|
94
93
|
"extends": [
|
|
@@ -17,10 +17,10 @@
|
|
|
17
17
|
<thead class="cpTable__header">
|
|
18
18
|
<tr class="cpTable__row cpTable__row--header">
|
|
19
19
|
<th
|
|
20
|
-
v-for="
|
|
20
|
+
v-for="column in visibleColumns"
|
|
21
21
|
:key="column.id"
|
|
22
22
|
class="cpTable__column"
|
|
23
|
-
:style="getColumnStyle(column
|
|
23
|
+
:style="getColumnStyle(column)"
|
|
24
24
|
>
|
|
25
25
|
<slot :column="column" name="column">
|
|
26
26
|
{{ column.name }}
|
|
@@ -76,7 +76,6 @@
|
|
|
76
76
|
>
|
|
77
77
|
<cp-icon size="16" :type="option.icon" />
|
|
78
78
|
</button>
|
|
79
|
-
|
|
80
79
|
<button
|
|
81
80
|
class="cpTable__action cpTable__action--isDefault"
|
|
82
81
|
type="button"
|
|
@@ -266,23 +265,70 @@ const getVisibleColumnsIds = () => {
|
|
|
266
265
|
|
|
267
266
|
const fullWidthColumn = computed(() => normalizedColumns.value.find(({ isFull }) => isFull))
|
|
268
267
|
|
|
268
|
+
const fullWidthColumnIndex = computed(() => {
|
|
269
|
+
if (!fullWidthColumn.value) return -1
|
|
270
|
+
return normalizedColumns.value.findIndex((col) => col.id === fullWidthColumn.value!.id)
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* When the isFull column is hidden:
|
|
275
|
+
* - If there is at least one visible column before the full column in the normalized order → we keep the last of those columns at 1000px width.
|
|
276
|
+
* - If there are none (e.g. A, B, C with A full and in the first position, and we hide A) → we apply the 1000px width to the first visible column (here B).
|
|
277
|
+
*/
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Last visible column before the full column in the normalized order (to apply FULL_WIDTH_SIZE when full is hidden).
|
|
281
|
+
*/
|
|
282
|
+
const columnBeforeFullWidthId = computed(() => {
|
|
283
|
+
if (fullWidthColumnIndex.value <= 0) return null
|
|
284
|
+
|
|
285
|
+
const maxIndex = fullWidthColumnIndex.value
|
|
286
|
+
|
|
287
|
+
const visibleColumnsWithIndex = columnsModel.value
|
|
288
|
+
.map((id) => ({ id, index: normalizedColumns.value.findIndex((col) => col.id === id) }))
|
|
289
|
+
.filter(({ index }) => index >= 0 && index < maxIndex)
|
|
290
|
+
|
|
291
|
+
const bestColumnBeforeFullWidth = visibleColumnsWithIndex.reduce<{ id: string; index: number } | null>(
|
|
292
|
+
(acc, curr) => (curr.index > (acc?.index || -1) ? curr : acc),
|
|
293
|
+
null,
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
return bestColumnBeforeFullWidth?.id || null
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* First visible column in the normalized order (fallback when the full column is hidden and no visible column precedes it).
|
|
301
|
+
*/
|
|
302
|
+
const firstVisibleColumnId = computed(() => {
|
|
303
|
+
const visibleWithIndex = columnsModel.value
|
|
304
|
+
.map((id) => ({ id, index: normalizedColumns.value.findIndex((col) => col.id === id) }))
|
|
305
|
+
.filter(({ index }) => index >= 0)
|
|
306
|
+
|
|
307
|
+
const firstVisibleColumn = visibleWithIndex.reduce<{ id: string; index: number } | null>(
|
|
308
|
+
(acc, curr) => (curr.index < (acc?.index || Infinity) ? curr : acc),
|
|
309
|
+
null,
|
|
310
|
+
)
|
|
311
|
+
return firstVisibleColumn?.id || null
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Column that receives FULL_WIDTH_SIZE when the isFull column is hidden (last visible before full, or first visible if none precedes full).
|
|
316
|
+
*/
|
|
317
|
+
const columnThatGetsFullWidthId = computed(() => columnBeforeFullWidthId.value || firstVisibleColumnId.value)
|
|
318
|
+
|
|
269
319
|
const hasAFullWidthColumnVisible = computed(() => {
|
|
270
320
|
if (!fullWidthColumn.value) return true
|
|
271
321
|
return columnsModel.value.includes(fullWidthColumn.value.id)
|
|
272
322
|
})
|
|
273
323
|
|
|
274
|
-
const
|
|
324
|
+
const isColumnThatGetsFullWidth = (column: CpTableColumnObject) => {
|
|
325
|
+
return !!(columnThatGetsFullWidthId.value && column.id === columnThatGetsFullWidthId.value)
|
|
326
|
+
}
|
|
275
327
|
|
|
276
328
|
const columnsModel = ref<string[]>(getVisibleColumnsIds())
|
|
277
329
|
|
|
278
330
|
watch(columnsModel, (newColumnsModel) => {
|
|
279
|
-
const newColumns = normalizedColumns.value.map((col) => {
|
|
280
|
-
return {
|
|
281
|
-
...col,
|
|
282
|
-
isHidden: !newColumnsModel.includes(col.id),
|
|
283
|
-
}
|
|
284
|
-
})
|
|
285
|
-
|
|
331
|
+
const newColumns = normalizedColumns.value.map((col) => ({ ...col, isHidden: !newColumnsModel.includes(col.id) }))
|
|
286
332
|
emit('onColumnsChanged', newColumns)
|
|
287
333
|
})
|
|
288
334
|
|
|
@@ -307,10 +353,9 @@ const numberOfResults = computed(() => {
|
|
|
307
353
|
})
|
|
308
354
|
|
|
309
355
|
const hasNoResult = computed(() => numberOfResults.value === 0)
|
|
356
|
+
|
|
310
357
|
const rowsPerPageLimit = computed(() => {
|
|
311
|
-
if (typeof props.pagination === 'object' && props.pagination.limit)
|
|
312
|
-
return props.pagination.limit
|
|
313
|
-
}
|
|
358
|
+
if (typeof props.pagination === 'object' && props.pagination.limit) return props.pagination.limit
|
|
314
359
|
return VISIBLE_ROWS_MAX
|
|
315
360
|
})
|
|
316
361
|
|
|
@@ -347,9 +392,7 @@ const paginationState = computed(() => {
|
|
|
347
392
|
|
|
348
393
|
const hasPagination = computed(() => paginationState.value || numberOfResults.value > VISIBLE_ROWS_MAX)
|
|
349
394
|
const paginationFormat = computed(() => {
|
|
350
|
-
if (typeof props.pagination === 'object' && props.pagination.format)
|
|
351
|
-
return props.pagination.format
|
|
352
|
-
}
|
|
395
|
+
if (typeof props.pagination === 'object' && props.pagination.format) return props.pagination.format
|
|
353
396
|
return PAGINATION_FORMATS.PAGES
|
|
354
397
|
})
|
|
355
398
|
const hasRemainingPages = computed(() => numberOfPages.value > activePage.value)
|
|
@@ -498,10 +541,10 @@ const resetScrollPosition = () => {
|
|
|
498
541
|
}
|
|
499
542
|
}
|
|
500
543
|
|
|
501
|
-
const getColumnStyle = (column: CpTableColumnObject
|
|
544
|
+
const getColumnStyle = (column: CpTableColumnObject) => {
|
|
502
545
|
let width: string | undefined
|
|
503
546
|
|
|
504
|
-
if (!hasAFullWidthColumnVisible.value &&
|
|
547
|
+
if (!hasAFullWidthColumnVisible.value && isColumnThatGetsFullWidth(column)) {
|
|
505
548
|
width = `${FULL_WIDTH_SIZE}px`
|
|
506
549
|
} else if (column.isFull) {
|
|
507
550
|
width = `${FULL_WIDTH_SIZE}px`
|
|
@@ -707,6 +750,10 @@ defineExpose({ hideContextualMenu, resetPagination, currentRowData })
|
|
|
707
750
|
|
|
708
751
|
&__columnEditor {
|
|
709
752
|
padding-right: calc(sp.$space-md + fn.px-to-rem(2));
|
|
753
|
+
|
|
754
|
+
@media (hover: none) and (pointer: coarse) {
|
|
755
|
+
padding-right: sp.$space-md;
|
|
756
|
+
}
|
|
710
757
|
}
|
|
711
758
|
|
|
712
759
|
&__body {
|
|
@@ -804,6 +851,10 @@ defineExpose({ hideContextualMenu, resetPagination, currentRowData })
|
|
|
804
851
|
}
|
|
805
852
|
}
|
|
806
853
|
|
|
854
|
+
&__actions {
|
|
855
|
+
text-align: right;
|
|
856
|
+
}
|
|
857
|
+
|
|
807
858
|
// On desktop devices, display options only on row focus or hover
|
|
808
859
|
@media (hover: hover) and (pointer: fine) {
|
|
809
860
|
&__cell--isOptions {
|
|
@@ -813,19 +864,26 @@ defineExpose({ hideContextualMenu, resetPagination, currentRowData })
|
|
|
813
864
|
}
|
|
814
865
|
|
|
815
866
|
// Actions wrapper on desktop : displayed only if there are quick actions inside
|
|
816
|
-
&__cell--isOptions .cpTable__actions
|
|
867
|
+
&__cell--isOptions .cpTable__actions {
|
|
817
868
|
position: absolute;
|
|
818
869
|
top: 50%;
|
|
819
870
|
transform: translateY(-50%);
|
|
820
871
|
right: fn.px-to-rem(12);
|
|
821
872
|
display: inline-flex;
|
|
822
873
|
overflow: hidden;
|
|
823
|
-
border: 1px solid colors.$border-color;
|
|
824
874
|
border-radius: fn.px-to-rem(8);
|
|
825
875
|
background-color: colors.$neutral-light;
|
|
826
876
|
box-shadow:
|
|
827
877
|
0 1px 4px rgba(colors.$neutral-dark, 0.1),
|
|
828
878
|
0 0 1px rgba(colors.$neutral-dark, 0.15);
|
|
879
|
+
|
|
880
|
+
&:has(.cpTable__action:only-child) {
|
|
881
|
+
right: fn.px-to-rem(15);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
&__cell--isOptions .cpTable__actions:has(> :nth-child(2)) {
|
|
886
|
+
border: 1px solid colors.$border-color;
|
|
829
887
|
}
|
|
830
888
|
|
|
831
889
|
&__cell--isOptions .cpTable__action {
|
|
@@ -107,9 +107,11 @@ const triggerDynamicClass = computed(() => {
|
|
|
107
107
|
|
|
108
108
|
const protectedColumns = computed(() => {
|
|
109
109
|
const filteredProtectedColumns = props.columns.filter((column) => isColumnProtected(column))
|
|
110
|
+
|
|
110
111
|
if (filteredProtectedColumns.length) return filteredProtectedColumns
|
|
111
112
|
|
|
112
113
|
const firstColumn = props.columns[0]
|
|
114
|
+
|
|
113
115
|
return [firstColumn]
|
|
114
116
|
})
|
|
115
117
|
|
|
@@ -119,8 +121,8 @@ const filteredVisibleColumns = computed(() => {
|
|
|
119
121
|
return props.columns.filter((column) => {
|
|
120
122
|
const isMatchingSearch = column.name.toLowerCase().includes(searchQuery.value.toLowerCase())
|
|
121
123
|
const isProtected = protectedColumns.value.some(({ id }) => id === column.id)
|
|
122
|
-
|
|
123
124
|
const conditions = [isMatchingSearch, isColumnSelected(column), !isProtected]
|
|
125
|
+
|
|
124
126
|
return conditions.every((condition) => condition)
|
|
125
127
|
})
|
|
126
128
|
})
|
|
@@ -146,11 +148,13 @@ const handleDropdownShown = () => {
|
|
|
146
148
|
&__trigger {
|
|
147
149
|
@extend %u-focus-outline;
|
|
148
150
|
|
|
151
|
+
color: colors.$neutral-dark-1;
|
|
149
152
|
border-radius: fn.px-to-rem(8);
|
|
150
153
|
|
|
151
154
|
&--isOpen,
|
|
152
155
|
&:hover,
|
|
153
156
|
&:focus-within {
|
|
157
|
+
color: colors.$neutral-dark;
|
|
154
158
|
background-color: colors.$neutral-dark-5;
|
|
155
159
|
}
|
|
156
160
|
}
|
|
@@ -27,11 +27,11 @@ type Story = StoryObj<typeof meta>
|
|
|
27
27
|
const sampleColumns = ['name', 'age', 'email', 'status']
|
|
28
28
|
|
|
29
29
|
const sampleData = [
|
|
30
|
-
{ name: 'John Doe', age: 30, email: 'john@example.com', status: 'Active' },
|
|
31
|
-
{ name: 'Jane Smith', age: 25, email: 'jane@example.com', status: 'Inactive' },
|
|
32
|
-
{ name: 'Bob Johnson', age: 35, email: 'bob@example.com', status: 'Active' },
|
|
33
|
-
{ name: 'Alice Brown', age: 28, email: 'alice@example.com', status: 'Active' },
|
|
34
|
-
{ name: 'Charlie Wilson', age: 42, email: 'charlie@example.com', status: 'Inactive' },
|
|
30
|
+
{ name: 'John Doe', age: 30, email: 'john@example.com', status: 'Active', date: '2026-01-01' },
|
|
31
|
+
{ name: 'Jane Smith', age: 25, email: 'jane@example.com', status: 'Inactive', date: '2026-01-02' },
|
|
32
|
+
{ name: 'Bob Johnson', age: 35, email: 'bob@example.com', status: 'Active', date: '2026-01-03' },
|
|
33
|
+
{ name: 'Alice Brown', age: 28, email: 'alice@example.com', status: 'Active', date: '2026-01-04' },
|
|
34
|
+
{ name: 'Charlie Wilson', age: 42, email: 'charlie@example.com', status: 'Inactive', date: '2026-01-05' },
|
|
35
35
|
]
|
|
36
36
|
|
|
37
37
|
export const Default: Story = {
|
|
@@ -70,10 +70,11 @@ export const EnableColumnEdition: Story = {
|
|
|
70
70
|
args: {
|
|
71
71
|
...Default.args,
|
|
72
72
|
columns: [
|
|
73
|
-
{ id: 'name', name: 'Name', isProtected:
|
|
74
|
-
{ id: 'age', name: 'Age', isHidden:
|
|
73
|
+
{ id: 'name', name: 'Name', isProtected: false },
|
|
74
|
+
{ id: 'age', name: 'Age', isHidden: false, isProtected: false, isFull: true },
|
|
75
75
|
{ id: 'email', name: 'Email', isHidden: false },
|
|
76
|
-
{ id: 'status', name: 'Status', isFull:
|
|
76
|
+
{ id: 'status', name: 'Status', isFull: false },
|
|
77
|
+
{ id: 'date', name: 'Date', isHidden: false, isProtected: true },
|
|
77
78
|
],
|
|
78
79
|
enableColumnEdition: true,
|
|
79
80
|
},
|
|
@@ -222,6 +223,7 @@ export const WithOnlyDefaultAction: Story = {
|
|
|
222
223
|
args: {
|
|
223
224
|
...Default.args,
|
|
224
225
|
enableRowOptions: true,
|
|
226
|
+
enableColumnEdition: true,
|
|
225
227
|
quickOptionsLimit: 0,
|
|
226
228
|
},
|
|
227
229
|
render: (args) => ({
|