@rokkit/core 1.0.0-next.90 → 1.0.0-next.91

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": "@rokkit/core",
3
- "version": "1.0.0-next.90",
3
+ "version": "1.0.0-next.91",
4
4
  "description": "Core components, actions and stores for svelte apps.",
5
5
  "author": "Jerry Thomas <me@jerrythomas.name>",
6
6
  "license": "MIT",
@@ -22,8 +22,8 @@
22
22
  "typescript": "^5.3.3",
23
23
  "vite": "^5.1.4",
24
24
  "vitest": "~1.3.1",
25
- "shared-config": "1.0.0-next.90",
26
- "validators": "1.0.0-next.90"
25
+ "shared-config": "1.0.0-next.91",
26
+ "validators": "1.0.0-next.91"
27
27
  },
28
28
  "files": [
29
29
  "src/**/*.js",
package/src/constants.js CHANGED
@@ -6,8 +6,8 @@ export { defaultColors, syntaxColors, shades, defaultPalette } from './colors'
6
6
  export const defaultFields = {
7
7
  id: 'id',
8
8
  url: 'url',
9
- value: 'value',
10
9
  text: 'text',
10
+ value: 'value',
11
11
  children: 'children',
12
12
  icon: 'icon',
13
13
  iconPrefix: null,
@@ -21,7 +21,8 @@ export const defaultFields = {
21
21
  isOpen: '_open',
22
22
  isDeleted: '_deleted',
23
23
  level: 'level',
24
- parent: 'parent'
24
+ parent: 'parent',
25
+ currency: 'currency'
25
26
  }
26
27
 
27
28
  export const defaultIcons = [
@@ -41,6 +42,7 @@ export const defaultIcons = [
41
42
  'checkbox-unknown',
42
43
  'rating-filled',
43
44
  'rating-empty',
45
+ 'rating-half',
44
46
  'radio-off',
45
47
  'radio-on',
46
48
  'mode-dark',
@@ -57,7 +59,14 @@ export const defaultIcons = [
57
59
  'badge-fail',
58
60
  'badge-warn',
59
61
  'badge-pass',
60
- 'badge-unknown'
62
+ 'badge-unknown',
63
+ 'sort-none',
64
+ 'sort-ascending',
65
+ 'sort-descending',
66
+ 'validity-failed',
67
+ 'validity-passed',
68
+ 'validity-unknown',
69
+ 'validity-warning'
61
70
  ]
62
71
 
63
72
  export const defaultOptions = {
@@ -0,0 +1,47 @@
1
+ import { identity } from 'ramda'
2
+
3
+ /**
4
+ * Creates a formatter function that formats a value according to the specified type and optional locale settings.
5
+ * Supported types include 'default' (no formatting), 'integer', 'number', 'date', 'time', and 'currency'.
6
+ *
7
+ * The function is curried, which means it can be partially applied with some arguments and reused
8
+ * to format different values with the same settings.
9
+ *
10
+ * @param {string} type - The type of the formatter to use (e.g., 'integer', 'number', 'date', 'time', 'currency').
11
+ * @param {string} [language='en-US'] - Optional IETF language tag used for locale-specific formatting.
12
+ * @param {string} [currency='USD'] - Optional currency code which is relevant when the type is 'currency'.
13
+ * @param {number} [decimalPlaces=2] - Optional number of decimal places to show with number and currency formatting.
14
+ * @param {number|Date} value - The value to format, it should be of a type corresponding to the formatter type.
15
+ * @returns {*} - The formatted value as a string or the original value if formatting is not applicable.
16
+ */
17
+ export function createFormatter(type, language = 'en-US', decimalPlaces = 2, currency) {
18
+ const formatWithLocaleOptions = (options, val) => val.toLocaleString(language, options)
19
+ const formatCurrency = (val, currency = 'USD') =>
20
+ val.toLocaleString(language, {
21
+ style: 'currency',
22
+ currency,
23
+ minimumFractionDigits: decimalPlaces,
24
+ maximumFractionDigits: decimalPlaces
25
+ })
26
+
27
+ const formatters = {
28
+ integer: (val) => formatWithLocaleOptions({ maximumFractionDigits: 0 }, val), // Integer without decimals
29
+ number: (val) =>
30
+ formatWithLocaleOptions(
31
+ {
32
+ minimumFractionDigits: decimalPlaces,
33
+ maximumFractionDigits: decimalPlaces
34
+ },
35
+ val
36
+ ), // Number with fixed decimal places
37
+ date: (val) => val.toLocaleDateString(language), // Locale-specific date format
38
+ time: (val) => val.toLocaleTimeString(language), // Locale-specific time format
39
+ currency: currency ? (val) => formatCurrency(val, currency) : formatCurrency
40
+ // Currency with fixed decimal places and currency symbol
41
+ }
42
+ if (type in formatters) return formatters[type]
43
+ return identity
44
+ // return propOr(identity, type, formatters)(value) // Apply the formatter based on the type
45
+ }
46
+
47
+ // export
package/src/parser.js CHANGED
@@ -1,3 +1,23 @@
1
+ /**
2
+ * Constructs a regular expression pattern for matching search filter strings.
3
+ * The pattern captures "column", "operator", and "value" groups for filter expressions.
4
+ * Supported operators include common comparison and wildcard operators.
5
+ *
6
+ * Examples of valid expressions this regex can match:
7
+ * - "username:john_doe"
8
+ * - "age>30"
9
+ * - "status!=active"
10
+ * - "title~\"Product Manager\""
11
+ * - "completed!~*yes"
12
+ *
13
+ * The "column" group matches one or more word characters.
14
+ * The "operator" group matches one among the specified comparison or wildcard operators:
15
+ * :, >, <, >=, <=, =<, =>, =, !=, ~, ~*, !~, !~*.
16
+ * The "value" group matches either a double-quoted string or a single unquoted word
17
+ * that doesn't include whitespace or any of the operator characters.
18
+ *
19
+ * @returns {RegExp} - The regular expression pattern to identify search filter elements.
20
+ */
1
21
  export function getRegex() {
2
22
  let column = '[\\w]+'
3
23
  let operator = ':|>|<|>=|<=|=<|=>|=|!=|~|~\\*|!~|!~\\*'
@@ -8,6 +28,15 @@ export function getRegex() {
8
28
  return new RegExp(pattern, 'gm')
9
29
  }
10
30
 
31
+ /**
32
+ * Parses a search string and extracts filters based on a predefined regular expression pattern.
33
+ * Each filter captures column, operator, and value components. Any remaining text that
34
+ * does not fit the pattern is considered a general search term.
35
+ *
36
+ * @param {string} string - The search string to parse for filters.
37
+ * @returns {Object[]} - An array of filter objects each containing the column, operator, and value,
38
+ * plus an additional object for general search if there is remaining text.
39
+ */
11
40
  export function parseFilters(string) {
12
41
  const results = []
13
42
  const regex = getRegex()
@@ -38,15 +67,30 @@ export function parseFilters(string) {
38
67
  return results
39
68
  }
40
69
 
70
+ /**
71
+ * Replaces various shorthand operators with their standardized equivalent for filtering.
72
+ *
73
+ * @param {string} operator - The operator to be replaced.
74
+ * @returns {string} - The replaced operator string.
75
+ */
41
76
  function replaceOperators(operator) {
42
77
  return operator.replace(':', '~*').replace('=>', '>=').replace('=<', '<=')
43
78
  }
44
79
 
80
+ /**
81
+ * Processes the filter value provided, converting it into a format suitable for filtering.
82
+ * If the operator includes a tilde (~), the value is converted to a RegExp.
83
+ *
84
+ * @param {string|number} value - The value to be processed. It could be a string needing quote removal or a number that needs parsing.
85
+ * @param {string} operator - The operator that determines the type of processing to be applied to the value.
86
+ * @returns {string|number|RegExp} - The processed value, which may be a RegExp if the operator involves pattern matching.
87
+ */
45
88
  function processValue(value, operator) {
46
- // if (!value) return value
89
+ // If the value can be parsed as an integer, do so. Otherwise, strip quotes if present.
47
90
 
48
91
  value = !isNaN(parseInt(value)) ? parseInt(value) : removeQuotes(value)
49
-
92
+ // If the operator includes a tilde (~), treat the value as a regular expression,
93
+ // case-insensitive if '*' is present, sensitive otherwise.
50
94
  if (operator.includes('~')) {
51
95
  value = operator.includes('*') ? new RegExp(value, 'i') : new RegExp(value)
52
96
  }
@@ -54,6 +98,12 @@ function processValue(value, operator) {
54
98
  return value
55
99
  }
56
100
 
101
+ /**
102
+ * Removes double quotes from the start and end of a string.
103
+ *
104
+ * @param {string} str - The input string from which the surrounding quotes should be removed.
105
+ * @returns {string} - The unquoted string.
106
+ */
57
107
  function removeQuotes(str) {
58
108
  const quoteMatch = str.match(/^"([^"]+)"$/)
59
109
  return quoteMatch ? quoteMatch[1] : str
package/src/tabular.js ADDED
@@ -0,0 +1,64 @@
1
+ import { createFormatter } from './formatter'
2
+
3
+ export function tabular(data, columns, options) {
4
+ const { path, separator = '/', actions } = options ?? {}
5
+
6
+ let metadata = columns
7
+ if (!Array.isArray(columns) || columns.length === 0) metadata = deriveColumnMetadata(data)
8
+
9
+ return {
10
+ data,
11
+ columns: metadata,
12
+ filter: () => {},
13
+ sort: () => {}
14
+ }
15
+ }
16
+
17
+ /**
18
+ * Derives column metadata from the data to be used in a tabular component.
19
+ *
20
+ * @param {Array} data - The data to derive column metadata from.
21
+ * @returns {Array<import('./types').ColumnMetadata>} - The derived column metadata.
22
+ */
23
+ export function deriveColumnMetadata(dataArray) {
24
+ if (dataArray.length === 0) {
25
+ return []
26
+ }
27
+
28
+ const firstRow = dataArray[0]
29
+ const language = navigator.language || 'en-US' // Get the browser's language or default to 'en-US'
30
+
31
+ const columns = []
32
+
33
+ for (const key in firstRow) {
34
+ // if (Object.prototype.hasOwnProperty.call(firstRow, key)) {
35
+ const dataType = typeof firstRow[key]
36
+ const formatter = createFormatter(dataType, language)
37
+ const fields = { text: key }
38
+
39
+ if (key.endsWith('_currency')) {
40
+ const currencyColumn = key
41
+ const baseColumn = key.replace(/_currency$/, '')
42
+
43
+ // Find the existing column and update its currency attribute
44
+ const existingColumn = columns.find((column) => column.name === baseColumn)
45
+ existingColumn.dataType = 'currency'
46
+ existingColumn.formatter = createFormatter('currency', language, 2)
47
+ if (existingColumn) {
48
+ existingColumn.fields = {
49
+ ...existingColumn.fields,
50
+ currency: currencyColumn
51
+ }
52
+ }
53
+ } else {
54
+ columns.push({
55
+ name: key,
56
+ dataType: dataType,
57
+ fields,
58
+ formatter
59
+ })
60
+ }
61
+ }
62
+
63
+ return columns
64
+ }
package/src/types.js CHANGED
@@ -1,19 +1,39 @@
1
+ /**
2
+ * Options for the sort order of the column.
3
+ *
4
+ * @typedef {'ascending'|'descending'|'none'} SortOptions
5
+ */
6
+
7
+ /**
8
+ * Options for the horizontal alignment of the column content.
9
+ *
10
+ * @typedef {'left'|'middle'|'right'} HorizontalAlignOptions
11
+ */
12
+
13
+ /**
14
+ * Options for the action type of the column.
15
+ *
16
+ * @typedef {'select'|'edit'|'delete'} ActionTypes
17
+ */
18
+
1
19
  /**
2
20
  * Structure to map custom fields for rendering. This is used to identofy the attributes for various purposes.
3
21
  *
4
22
  * @typedef FieldMapping
5
23
  * @property {string} [id='id'] Unique id for the item
6
24
  * @property {string} [text='text'] the text to render
25
+ * @property {string} [value='value'] the value associated with the item
7
26
  * @property {string} [url='url'] a URL
8
27
  * @property {string} [icon='icon'] icon to render
9
28
  * @property {string} [image='image'] the image to render
10
29
  * @property {string} [children='children'] children of the item
11
- * @property {string} [summary='summary']
12
- * @property {string} [notes='notes']
13
- * @property {string} [props='props']
30
+ * @property {string} [summary='summary'] summary of the item
31
+ * @property {string} [notes='notes'] notes for the item
32
+ * @property {string} [props='props'] additional properties
14
33
  * @property {string} [isOpen='_open'] item is open or closed
15
34
  * @property {string} [level='level'] level of item
16
35
  * @property {string} [parent='parent'] item is a parent
36
+ * @property {string} [currency='currency] column specifying currency to be used for the current value
17
37
  * @property {string} [isDeleted='_deleted'] item is deleted
18
38
  * @property {FieldMapping} [fields] Field mapping to be used on children in the next level
19
39
  */
@@ -22,3 +42,38 @@
22
42
  * Component map to be used to render the item.
23
43
  * @typedef {Object<string, import('svelte').SvelteComponent>} ComponentMap
24
44
  */
45
+
46
+ /**
47
+ * Column metadata for the table.
48
+ *
49
+ * @typedef {Object} ColumnMetadata
50
+ *
51
+ * @property {string} name - The name of the column.
52
+ * @property {string} dataType - The data type of the column (e.g., "string", "number", "date").
53
+ * @property {FieldMapping} [fields] - Additional attributes for the column.
54
+ * @property {number} [digits=0] - The number of digits for numeric values (defaults to 0).
55
+ * @property {Function} formatter - A function to format the column value.
56
+ * @property {boolean} [virtual] - Indicates if the column is virtual (true/false).
57
+ * @property {boolean} [sortable] - Indicates if the column is sortable (true/false).
58
+ * @property {SortOptions} [sortOrder] - The sort order of the column.
59
+ * @property {HorizontalAlignOptions} [align] - The alignment of the column content.
60
+ * @property {ActionTypes} [action] - Action attribute for action columns.
61
+ */
62
+
63
+ /**
64
+ * Track the state of a row in the table.
65
+ *
66
+ * @typedef {Object} RowState
67
+ * @property {boolean} visible - Indicates whether the node is visible (true/false).
68
+ * @property {string} [label] - The label of the hierarchy node.
69
+ * @property {boolean} is_parent - Indicates if this node is a parent (true/false).
70
+ * @property {boolean} is_collapsed - Indicates whether the node is collapsed (true/false).
71
+ * @property {number[]} levels - Array of hierarchy indices, including parent indices.
72
+ */
73
+
74
+ /**
75
+ * Track the state of all rows in the table.
76
+ *
77
+ * @typedef {Object} RowStateMap
78
+ * @property {RowState[]} rows - Flat list of hierarchy nodes.
79
+ */