@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@citizenplane/pimp",
3
- "version": "10.0.2",
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.26",
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.5",
48
- "@chromatic-com/storybook": "^4.1.3",
49
- "@commitlint/cli": "^20.2.0",
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.6.1",
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.50.1",
62
- "@typescript-eslint/parser": "^8.50.1",
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": "^3.2.4",
65
- "@vitest/coverage-v8": "^3.2.4",
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.1.0",
75
- "eslint-plugin-prettier": "^5.5.3",
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.6.2",
78
- "globals": "^16.5.0",
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.57.0",
84
- "prettier": "^3.7.4",
85
- "sass": "~1.97.1",
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.50.1",
90
- "vite": "^7.3.0",
91
- "vitest": "^3.1.4"
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="(column, index) in visibleColumns"
20
+ v-for="column in visibleColumns"
21
21
  :key="column.id"
22
22
  class="cpTable__column"
23
- :style="getColumnStyle(column, index)"
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 isLastColumn = (idx: number) => idx === visibleColumns.value.length - 1
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, idx: number) => {
544
+ const getColumnStyle = (column: CpTableColumnObject) => {
502
545
  let width: string | undefined
503
546
 
504
- if (!hasAFullWidthColumnVisible.value && isLastColumn(idx)) {
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:has(> :nth-child(2)) {
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: true },
74
- { id: 'age', name: 'Age', isHidden: true, isProtected: true },
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: true },
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) => ({