@wishbone-media/spark 0.45.0 → 0.47.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.
package/formkit.config.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // Note: Consumer apps should wrap the returned config with defaultConfig()
2
- import { createAutoAnimatePlugin } from '@formkit/addons';
2
+ import { createAutoAnimatePlugin } from '@formkit/addons'
3
3
  import {
4
4
  createProPlugin,
5
5
  autocomplete,
@@ -12,9 +12,14 @@ import {
12
12
  taglist,
13
13
  toggle,
14
14
  togglebuttons,
15
- } from '@formkit/pro';
16
- import { rootClasses } from '@wishbone-media/spark/formkit.theme.mjs';
17
- import { formKitIcons, formKitIconLoader, formKitGenesisOverride } from '@wishbone-media/spark';
15
+ } from '@formkit/pro'
16
+ import { rootClasses } from '@wishbone-media/spark/formkit.theme.mjs'
17
+ import {
18
+ formKitIcons,
19
+ formKitIconLoader,
20
+ formKitGenesisOverride,
21
+ createPasswordTogglePlugin,
22
+ } from '@wishbone-media/spark'
18
23
 
19
24
  const autoAnimatePlugin = createAutoAnimatePlugin(
20
25
  {
@@ -22,20 +27,22 @@ const autoAnimatePlugin = createAutoAnimatePlugin(
22
27
  // default:
23
28
  duration: 250,
24
29
  easing: 'ease-in-out',
25
- delay: 0
30
+ delay: 0,
26
31
  },
27
32
  {
28
33
  /* optional animation targets object */
29
34
  // default:
30
35
  global: ['outer', 'inner'],
31
36
  form: ['form'],
32
- repeater: ['items']
33
- }
34
- );
37
+ repeater: ['items'],
38
+ },
39
+ )
35
40
 
36
41
  export default function createFormKitConfig(formKitProKey) {
37
42
  if (!formKitProKey) {
38
- throw new Error('FormKit Pro key is required. Please provide VITE_FORMKIT_PRO_KEY in your environment variables.');
43
+ throw new Error(
44
+ 'FormKit Pro key is required. Please provide VITE_FORMKIT_PRO_KEY in your environment variables.',
45
+ )
39
46
  }
40
47
 
41
48
  const proPlugin = createProPlugin(formKitProKey, {
@@ -49,18 +56,18 @@ export default function createFormKitConfig(formKitProKey) {
49
56
  taglist,
50
57
  toggle,
51
58
  togglebuttons,
52
- });
59
+ })
53
60
 
54
61
  // Return plain config object - consumer apps wrap with defaultConfig()
55
62
  return {
56
63
  config: {
57
- rootClasses
64
+ rootClasses,
58
65
  },
59
66
  icons: {
60
67
  ...formKitGenesisOverride, // FontAwesome replacements for FormKit's built-in icons
61
- ...formKitIcons, // Additional FA icons available via prefix-icon/suffix-icon
68
+ ...formKitIcons, // Additional FA icons available via prefix-icon/suffix-icon
62
69
  },
63
70
  iconLoader: formKitIconLoader,
64
- plugins: [proPlugin, autoAnimatePlugin]
65
- };
71
+ plugins: [proPlugin, autoAnimatePlugin, createPasswordTogglePlugin()],
72
+ }
66
73
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wishbone-media/spark",
3
- "version": "0.45.0",
3
+ "version": "0.47.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -37,7 +37,8 @@
37
37
  }
38
38
 
39
39
  td {
40
- @apply px-6 py-4 whitespace-nowrap text-gray-500 text-sm align-middle h-auto;
40
+ @apply whitespace-nowrap text-gray-500 text-sm align-middle h-auto;
41
+ padding: var(--spark-table-cell-py, 16px) var(--spark-table-cell-px, 24px);
41
42
  @apply !border-l-0 !border-r-0 !border-gray-200;
42
43
 
43
44
  &:not(.read-only) {
@@ -48,6 +49,10 @@
48
49
  td:not(:last-child) {
49
50
  @apply !border-r-0;
50
51
  }
52
+
53
+ td.spark-table-word-wrap {
54
+ @apply whitespace-normal break-words;
55
+ }
51
56
  }
52
57
  }
53
58
 
@@ -66,7 +71,7 @@
66
71
  @apply text-left text-xs font-semibold bg-gray-50 text-gray-800 whitespace-normal
67
72
  tracking-wider focus:outline-hidden filter flex items-center;
68
73
 
69
- @apply !px-5 !py-3;
74
+ padding: var(--spark-table-header-py, 12px) var(--spark-table-header-px, 20px) !important;
70
75
 
71
76
  .fa-sort-up,
72
77
  .fa-sort-down {
@@ -119,7 +124,8 @@
119
124
  }
120
125
 
121
126
  .spark-table-head-sorting {
122
- @apply absolute right-5 w-5 h-5 border border-gray-200 rounded-md grid place-items-center;
127
+ @apply absolute w-5 h-5 border border-gray-200 rounded-md grid place-items-center;
128
+ right: var(--spark-table-header-px, 20px);
123
129
  }
124
130
 
125
131
  .spark-table-head-title-wrapper {
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="spark-table">
2
+ <div class="spark-table" :style="spacingStyles">
3
3
  <!-- Header Toolbar: All plugins flow left to right -->
4
4
  <SparkTableToolbar
5
5
  v-if="sparkTable.computed.ready && headerPlugins && headerPlugins.length"
@@ -75,6 +75,12 @@ const defaultTableSettings = {
75
75
  readOnlyCellClassName: 'read-only',
76
76
  licenseKey: 'non-commercial-and-evaluation',
77
77
  }
78
+
79
+ const spacingPresets = {
80
+ sm: { cellPx: 12, cellPy: 8, headerPx: 12, headerPy: 12 },
81
+ base: { cellPx: 24, cellPy: 16, headerPx: 20, headerPy: 12 },
82
+ lg: { cellPx: 32, cellPy: 20, headerPx: 24, headerPy: 16 },
83
+ }
78
84
  </script>
79
85
  <script setup>
80
86
  import { computed, onMounted, onUnmounted, ref, reactive, inject, watch } from 'vue'
@@ -178,6 +184,23 @@ const props = defineProps({
178
184
  type: Boolean,
179
185
  default: false,
180
186
  },
187
+
188
+ spacing: {
189
+ type: [String, Object],
190
+ default: null,
191
+ },
192
+ })
193
+
194
+ const spacingStyles = computed(() => {
195
+ if (!props.spacing) return {}
196
+ const values = typeof props.spacing === 'string' ? spacingPresets[props.spacing] : props.spacing
197
+ if (!values) return {}
198
+ const styles = {}
199
+ if (values.cellPx !== undefined) styles['--spark-table-cell-px'] = `${values.cellPx}px`
200
+ if (values.cellPy !== undefined) styles['--spark-table-cell-py'] = `${values.cellPy}px`
201
+ if (values.headerPx !== undefined) styles['--spark-table-header-px'] = `${values.headerPx}px`
202
+ if (values.headerPy !== undefined) styles['--spark-table-header-py'] = `${values.headerPy}px`
203
+ return styles
181
204
  })
182
205
 
183
206
  registerAllCellTypes()
@@ -356,40 +379,75 @@ const sparkTable = reactive({
356
379
  ...props.options,
357
380
  })),
358
381
 
359
- tableSettings: computed(() => ({
360
- ...defaultTableSettings,
361
- nestedHeaders: get(props.settings, 'nestedHeaders', []),
362
- ...(!get(props.settings, 'nestedHeaders') && {
363
- afterGetColHeader: (col, th) => customiseHeader(col, th, sparkTable),
364
- }),
365
- afterChange: (changes, source) => updateRow(changes, source, sparkTable),
366
- afterRender: () => syncSortClasses(sparkTable),
367
- beforeStretchingColumnWidth: (stretchedWidth, column) => {
368
- const columns = get(props.settings, 'columns', [])
369
- const columnSettings = columns[column]
370
- if (columnSettings && columnSettings.width !== undefined) {
371
- return columnSettings.width
382
+ tableSettings: computed(() => {
383
+ const columns = get(props.settings, 'columns', [])
384
+
385
+ const transformedColumns = columns.map((col) => {
386
+ if (!col.wordWrap) return col
387
+ return {
388
+ ...col,
389
+ className: [col.className, 'spark-table-word-wrap'].filter(Boolean).join(' '),
372
390
  }
373
- return stretchedWidth
374
- },
375
- beforeCopy: (data, coords) => {
376
- const hot = table.value?.hotInstance
377
- if (!hot) return
378
- coords.forEach((range) => {
379
- for (let row = range.startRow; row <= range.endRow; row++) {
380
- for (let col = range.startCol; col <= range.endCol; col++) {
381
- const td = hot.getCell(row, col)
382
- if (td) {
383
- const dataRow = row - coords[0].startRow
384
- const dataCol = col - coords[0].startCol
385
- data[dataRow][dataCol] = td.dataset.copyValue ?? td.textContent ?? ''
391
+ })
392
+
393
+ const hasClampedColumns = columns.some(
394
+ (col) => typeof col.wordWrap === 'object' && col.wordWrap?.maxLines,
395
+ )
396
+
397
+ const userAfterRenderer = props.settings.afterRenderer
398
+
399
+ return {
400
+ ...defaultTableSettings,
401
+ nestedHeaders: get(props.settings, 'nestedHeaders', []),
402
+ ...(!get(props.settings, 'nestedHeaders') && {
403
+ afterGetColHeader: (col, th) => customiseHeader(col, th, sparkTable),
404
+ }),
405
+ afterChange: (changes, source) => updateRow(changes, source, sparkTable),
406
+ afterRender: () => syncSortClasses(sparkTable),
407
+ beforeStretchingColumnWidth: (stretchedWidth, column) => {
408
+ const columnSettings = columns[column]
409
+ if (columnSettings && columnSettings.width !== undefined) {
410
+ return columnSettings.width
411
+ }
412
+ return stretchedWidth
413
+ },
414
+ beforeCopy: (data, coords) => {
415
+ const hot = table.value?.hotInstance
416
+ if (!hot) return
417
+ coords.forEach((range) => {
418
+ for (let row = range.startRow; row <= range.endRow; row++) {
419
+ for (let col = range.startCol; col <= range.endCol; col++) {
420
+ const td = hot.getCell(row, col)
421
+ if (td) {
422
+ const dataRow = row - coords[0].startRow
423
+ const dataCol = col - coords[0].startCol
424
+ data[dataRow][dataCol] = td.dataset.copyValue ?? td.textContent ?? ''
425
+ }
386
426
  }
387
427
  }
388
- }
389
- })
390
- },
391
- ...props.settings,
392
- })),
428
+ })
429
+ },
430
+ ...props.settings,
431
+ columns: transformedColumns,
432
+ ...(hasClampedColumns && {
433
+ afterRenderer: (td, row, col) => {
434
+ userAfterRenderer?.(td, row, col)
435
+ const colConfig = columns[col]
436
+ const wordWrap = colConfig?.wordWrap
437
+ if (!wordWrap || wordWrap === true || !wordWrap.maxLines) return
438
+
439
+ const wrapper = document.createElement('div')
440
+ wrapper.style.display = '-webkit-box'
441
+ wrapper.style.webkitBoxOrient = 'vertical'
442
+ wrapper.style.webkitLineClamp = wordWrap.maxLines
443
+ wrapper.style.overflow = 'hidden'
444
+
445
+ while (td.firstChild) wrapper.appendChild(td.firstChild)
446
+ td.appendChild(wrapper)
447
+ },
448
+ }),
449
+ }
450
+ }),
393
451
  })
394
452
 
395
453
  // Empty state detection - true when data has loaded but contains no records
@@ -40,6 +40,8 @@ import {
40
40
  faXmark,
41
41
  faSignOut,
42
42
  faEye,
43
+ faEyeSlash,
44
+ faLock,
43
45
  faUndo,
44
46
  faBolt,
45
47
  faCloudDownload,
@@ -109,6 +111,8 @@ export const Icons = {
109
111
  farXmark: faXmark,
110
112
  farSignOut: faSignOut,
111
113
  farEye: faEye,
114
+ farEyeSlash: faEyeSlash,
115
+ farLock: faLock,
112
116
  farUndo: faUndo,
113
117
  farBolt: faBolt,
114
118
  farCloudDownload: faCloudDownload,
@@ -0,0 +1,56 @@
1
+ function isTruthy(value) {
2
+ return value !== undefined && value !== false
3
+ }
4
+
5
+ function loadIcon(node, sectionKey, iconName) {
6
+ const iconHandler = node.props.iconHandler
7
+ if (!iconHandler) return
8
+
9
+ const rawProp = `_raw${sectionKey.charAt(0).toUpperCase()}${sectionKey.slice(1)}`
10
+ node.addProps([sectionKey, rawProp])
11
+
12
+ node.props[sectionKey] = iconName
13
+ const svg = iconHandler(iconName)
14
+ if (svg instanceof Promise) {
15
+ svg.then((s) => {
16
+ node.props[rawProp] = s
17
+ })
18
+ } else {
19
+ node.props[rawProp] = svg
20
+ }
21
+
22
+ node.on(`prop:${sectionKey}`, (event) => {
23
+ const newSvg = iconHandler(event.payload)
24
+ if (newSvg instanceof Promise) {
25
+ newSvg.then((s) => {
26
+ node.props[rawProp] = s
27
+ })
28
+ } else {
29
+ node.props[rawProp] = newSvg
30
+ }
31
+ })
32
+ }
33
+
34
+ export function createPasswordTogglePlugin() {
35
+ return function passwordTogglePlugin(node) {
36
+ if (node.props.type !== 'password') return
37
+
38
+ node.addProps(['toggleVisibility', 'showPrefixLock'])
39
+
40
+ if (isTruthy(node.props.toggleVisibility)) {
41
+ loadIcon(node, 'suffixIcon', 'farEyeSlash')
42
+ node.addProps(['onSuffixIconClick'])
43
+ node.props.onSuffixIconClick = (innerNode) => {
44
+ const el = document.getElementById(innerNode.props.id)
45
+ if (!el) return
46
+ const isHidden = el.type === 'password'
47
+ el.type = isHidden ? 'text' : 'password'
48
+ innerNode.props.suffixIcon = isHidden ? 'farEye' : 'farEyeSlash'
49
+ }
50
+ }
51
+
52
+ if (isTruthy(node.props.showPrefixLock)) {
53
+ loadIcon(node, 'prefixIcon', 'farLock')
54
+ }
55
+ }
56
+ }
@@ -17,3 +17,4 @@ export {
17
17
  export { createAxiosInstance, setupAxios, getAxiosInstance } from './axios.js'
18
18
  export { createBootstrapService } from './app-bootstrap.js'
19
19
  export { setupTooltip } from './tooltip.js'
20
+ export { createPasswordTogglePlugin } from './formkit-password.js'