@rokkit/core 1.0.0-next.15 → 1.0.0-next.151

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 (93) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +121 -1
  3. package/dist/calendar.d.ts +10 -0
  4. package/dist/colors/index.d.ts +47 -0
  5. package/dist/connector.d.ts +8 -0
  6. package/dist/constants.d.ts +80 -0
  7. package/dist/events.d.ts +12 -0
  8. package/dist/field-mapper.d.ts +60 -0
  9. package/dist/index.d.ts +13 -0
  10. package/dist/key-event-map.d.ts +18 -0
  11. package/dist/mapped-items.d.ts +14 -0
  12. package/dist/mapping.d.ts +14 -0
  13. package/dist/nested.d.ts +9 -0
  14. package/dist/string.d.ts +59 -0
  15. package/dist/theme.d.ts +96 -0
  16. package/dist/ticks.d.ts +10 -0
  17. package/dist/types.d.ts +300 -0
  18. package/dist/utils.d.ts +104 -0
  19. package/dist/vite/icon-collections.d.ts +32 -0
  20. package/dist/vite/index.d.ts +1 -0
  21. package/package.json +31 -39
  22. package/src/calendar.js +44 -0
  23. package/src/colors/extra.json +16 -0
  24. package/src/colors/index.ts +24 -0
  25. package/src/colors/syntax.json +42 -0
  26. package/src/colors/tailwind.json +275 -0
  27. package/src/connector.js +34 -0
  28. package/src/constants.js +163 -107
  29. package/src/events.js +32 -0
  30. package/src/field-mapper.js +147 -0
  31. package/src/index.js +19 -27
  32. package/src/key-event-map.js +35 -0
  33. package/src/mapped-items.js +22 -0
  34. package/src/mapping.js +21 -0
  35. package/src/nested.js +28 -0
  36. package/src/string.js +97 -0
  37. package/src/theme.ts +208 -0
  38. package/src/ticks.js +26 -0
  39. package/src/types.js +160 -0
  40. package/src/utils.js +272 -0
  41. package/src/vite/icon-collections.js +73 -0
  42. package/src/vite/index.js +1 -0
  43. package/src/Accordion.svelte +0 -80
  44. package/src/Alerts.svelte +0 -39
  45. package/src/DropDown.svelte +0 -82
  46. package/src/DropSearch.svelte +0 -67
  47. package/src/EditableTabs.svelte +0 -31
  48. package/src/Icon.svelte +0 -15
  49. package/src/List-Discard.svelte +0 -48
  50. package/src/List.svelte +0 -65
  51. package/src/ListActions.svelte +0 -35
  52. package/src/NavTabs.svelte +0 -0
  53. package/src/NestedList.svelte +0 -77
  54. package/src/Overlay.svelte +0 -4
  55. package/src/PageNavigator.svelte +0 -94
  56. package/src/ResponsiveGrid.svelte +0 -73
  57. package/src/Scrollable.svelte +0 -8
  58. package/src/Searchable.svelte +0 -19
  59. package/src/Sidebar.svelte +0 -5
  60. package/src/Slider.svelte +0 -17
  61. package/src/SpinList.svelte +0 -48
  62. package/src/SplitPane.svelte +0 -109
  63. package/src/SplitView.svelte +0 -44
  64. package/src/Splitter.svelte +0 -95
  65. package/src/TabItem.svelte +0 -27
  66. package/src/TabItems.svelte +0 -34
  67. package/src/Tabs.svelte +0 -49
  68. package/src/Tree.svelte +0 -45
  69. package/src/actions/dismissable.js +0 -24
  70. package/src/actions/fillable.js +0 -114
  71. package/src/actions/hierarchy.js +0 -180
  72. package/src/actions/index.js +0 -7
  73. package/src/actions/navigable.js +0 -43
  74. package/src/actions/navigator.js +0 -179
  75. package/src/actions/pannable.js +0 -50
  76. package/src/actions/swipeable.js +0 -56
  77. package/src/actions/themeable.js +0 -23
  78. package/src/items/Collapsible.svelte +0 -51
  79. package/src/items/Connector.svelte +0 -26
  80. package/src/items/Link.svelte +0 -18
  81. package/src/items/Node.svelte +0 -52
  82. package/src/items/Pill.svelte +0 -33
  83. package/src/items/Separator.svelte +0 -1
  84. package/src/items/Summary.svelte +0 -27
  85. package/src/items/Text.svelte +0 -21
  86. package/src/items/index.js +0 -8
  87. package/src/list.js +0 -14
  88. package/src/mocks/Custom.svelte +0 -7
  89. package/src/mocks/index.js +0 -10
  90. package/src/stores/alerts.js +0 -3
  91. package/src/stores/index.js +0 -6
  92. package/src/stores/persist.js +0 -63
  93. package/src/stores/theme.js +0 -34
@@ -0,0 +1,147 @@
1
+ import { isNil, has, omit } from 'ramda'
2
+ import { DEFAULT_FIELDS } from './constants.js'
3
+ import { isObject } from './utils.js'
4
+
5
+ export class FieldMapper {
6
+ #fields = { ...DEFAULT_FIELDS }
7
+ #childMapper = null
8
+
9
+ constructor(fields = DEFAULT_FIELDS) {
10
+ this.#updateFields(fields)
11
+ }
12
+
13
+ #updateFields(fields) {
14
+ Object.keys(fields).forEach((key) => {
15
+ this.#fields[key] = fields[key]
16
+ })
17
+ this.hasIcon = has(this.#fields.icon)
18
+ this.hasImage = has(this.#fields.image)
19
+ this.hasValue = has(this.#fields.value)
20
+ this.hasLabel = has(this.#fields.label)
21
+ this.hasComponent = has(this.#fields.component)
22
+ this.hasCurrency = has(this.#fields.currency)
23
+ this.withPrefix = (x) => [this.#fields.iconPrefix, x].join('-').replace(/^-+/g, '')
24
+ this.excludeFlags = omit([
25
+ this.#fields.isDeleted,
26
+ this.#fields.isHidden,
27
+ this.#fields.isSelected,
28
+ this.#fields.isFiltered,
29
+ this.#fields.isOpen
30
+ ])
31
+ }
32
+
33
+ getChildMapper() {
34
+ if (!this.#childMapper) {
35
+ this.#childMapper = new FieldMapper(this.fields.fields ?? this.fields)
36
+ }
37
+ return this.#childMapper
38
+ }
39
+ /**
40
+ * @private
41
+ */
42
+ prop(fieldName, value) {
43
+ if (isNil(value)) return null
44
+
45
+ if (typeof value === 'object') {
46
+ return value[this.fields[fieldName]]
47
+ }
48
+
49
+ return fieldName === 'label' ? value : null
50
+ }
51
+
52
+ /**
53
+ * Gets a mapped attribute from the original item
54
+ *
55
+ * @param {string} fieldName - Name of the field to get
56
+ * @returns {any|null} - The attribute value or null if not found
57
+ */
58
+ get(fieldName, value, defaultValue = null) {
59
+ return this.prop(fieldName, value) ?? defaultValue
60
+ }
61
+
62
+ get fields() {
63
+ return this.#fields
64
+ }
65
+
66
+ set fields(fields) {
67
+ this.#updateFields(fields)
68
+ }
69
+
70
+ getIcon(value) {
71
+ if (!this.hasIcon(value)) return null
72
+ const icon = value[this.fields.icon]
73
+ if (isObject(icon)) return this.withPrefix(icon[value[this.fields.state]])
74
+ return this.withPrefix(icon)
75
+ }
76
+
77
+ getValue(value) {
78
+ if (this.hasValue(value)) {
79
+ return value[this.fields.value]
80
+ }
81
+ return value
82
+ }
83
+
84
+ getFormattedText(value, formatter) {
85
+ const text = this.get('label', value)
86
+
87
+ if (isNil(text)) return ''
88
+
89
+ if (typeof formatter !== 'function') return text.toString()
90
+
91
+ if (this.hasCurrency(value)) {
92
+ return formatter(text, this.get('currency', value))
93
+ }
94
+ return formatter(text)
95
+ }
96
+
97
+ hasChildren(item) {
98
+ return (
99
+ !isNil(item) &&
100
+ has(this.fields.children, item) &&
101
+ Array.isArray(item[this.fields.children]) &&
102
+ item[this.fields.children].length > 0
103
+ )
104
+ }
105
+
106
+ isNested(items) {
107
+ return Array.isArray(items) && items.some((item) => this.hasChildren(item))
108
+ }
109
+
110
+ getChildren(item) {
111
+ return this.hasChildren(item) ? item[this.fields.children] : []
112
+ }
113
+
114
+ /**
115
+ * Finds children by an index path
116
+ *
117
+ * @param {Array<Object>} items
118
+ * @param {Array<number>} path
119
+ * @returns {Array<Object>}
120
+ */
121
+ getChildrenByPath(items, path = []) {
122
+ const result = path.reduce(
123
+ (children, index) => children?.[index]?.[this.fields.children],
124
+ items
125
+ )
126
+
127
+ if (result === undefined) throw new Error('Invalid path')
128
+ return result
129
+ }
130
+
131
+ /**
132
+ * Finds an item by an index path
133
+ *
134
+ * @param {Array<Object>} items
135
+ * @param {Array<number>} path
136
+ * @returns {Object|null}
137
+ */
138
+ getItemByPath(items, path = []) {
139
+ const result = path.reduce(
140
+ (item, index, i) => (i === 0 ? items?.[index] : item?.[this.fields.children]?.[index]),
141
+ /* skipcq: JS-W1042 default undefined is needed */ undefined
142
+ )
143
+
144
+ if (result === undefined) throw new Error('Invalid path')
145
+ return result
146
+ }
147
+ }
package/src/index.js CHANGED
@@ -1,27 +1,19 @@
1
- export * from './constants'
2
- export * from './items'
3
-
4
- export { default as Icon } from './Icon.svelte'
5
- // export { default as List } from './List-Discard.svelte'
6
- export { default as Tree } from './Tree.svelte'
7
- export { default as Accordion } from './Accordion.svelte'
8
- export { default as List } from './List.svelte'
9
- export { default as Searchable } from './Searchable.svelte'
10
- export { default as Scrollable } from './Scrollable.svelte'
11
- export { default as NestedList } from './NestedList.svelte'
12
- export { default as Tabs } from './Tabs.svelte'
13
-
14
- export { default as Alerts } from './Alerts.svelte'
15
- export { default as Slider } from './Slider.svelte'
16
- export { default as DropDown } from './DropDown.svelte'
17
- export { default as DropSearch } from './DropSearch.svelte'
18
- export { default as SpinList } from './SpinList.svelte'
19
-
20
- export { default as Sidebar } from './Sidebar.svelte'
21
- export { default as SplitPane } from './SplitPane.svelte'
22
- export { default as Splitter } from './Splitter.svelte'
23
- export { default as SplitView } from './SplitView.svelte'
24
- export { default as Overlay } from './Overlay.svelte'
25
-
26
- export { default as PageNavigator } from './PageNavigator.svelte'
27
- export { default as ResponsiveGrid } from './ResponsiveGrid.svelte'
1
+ // skipcq: JS-E1004 - Needed for exposing JS Doc types
2
+ export * from './types.js'
3
+ // skipcq: JS-E1004 - Needed for exposing all constants
4
+ export * from './constants.js'
5
+ // skipcq: JS-E1004 - Needed for exposing all functions
6
+ export * from './utils.js'
7
+ // skipcq: JS-E1004 - Needed for exposing all functions
8
+ export * from './nested.js'
9
+ // skipcq: JS-E1004 - Needed for exposing all functions
10
+ export * from './string.js'
11
+ // skipcq: JS-E1004 - Needed for exposing all functions
12
+ export * from './theme.js'
13
+ export { FieldMapper } from './field-mapper.js'
14
+ export { getItemAtIndex, getIndexForItem } from './mapped-items.js'
15
+ export { createEmitter } from './events.js'
16
+ export { getLineTypes } from './connector.js'
17
+ export { weekdays, getCalendarDays } from './calendar.js'
18
+ export { generateTicks } from './ticks.js'
19
+ export { getNestedFields } from './mapping.js'
@@ -0,0 +1,35 @@
1
+ import { find, toPairs } from 'ramda'
2
+
3
+ /**
4
+ * Class to manage key event mappings.
5
+ */
6
+ export class KeyEventMap {
7
+ constructor() {
8
+ this.mapping = {}
9
+ }
10
+
11
+ /**
12
+ * Add a new key mapping.
13
+ * @param {string} eventName - The event name.
14
+ * @param {Array<string>|RegExp} keys - The keys to match.
15
+ */
16
+ add(eventName, keys) {
17
+ if (!Array.isArray(keys) && !(keys instanceof RegExp)) {
18
+ throw new Error('Keys must be an array or a regular expression')
19
+ }
20
+ this.mapping[eventName] = keys
21
+ }
22
+
23
+ /**
24
+ * Get the event name for a given key.
25
+ * @param {string} key - The key to match.
26
+ * @returns {string|null} - The event name or null if no match is found.
27
+ */
28
+ getEventForKey(key) {
29
+ const matchEvent = ([_, keys]) =>
30
+ (Array.isArray(keys) && keys.includes(key)) || (keys instanceof RegExp && keys.test(key))
31
+
32
+ const event = find(matchEvent, toPairs(this.mapping))
33
+ return event ? event[0] : null
34
+ }
35
+ }
@@ -0,0 +1,22 @@
1
+ import { equals, isNil } from 'ramda'
2
+
3
+ /**
4
+ * Get an item at a specific index
5
+ * @param {Array<any>} items
6
+ * @param {any} index
7
+ * @returns
8
+ */
9
+ export function getItemAtIndex(items, index) {
10
+ if (isNil(index)) return null
11
+ return index >= 0 && index < items.length ? items[index] : null
12
+ }
13
+
14
+ /**
15
+ * Get the index for an item in an array
16
+ * @param {Array<any} items
17
+ * @param {any} item
18
+ * @returns
19
+ */
20
+ export function getIndexForItem(items, item) {
21
+ return items.findIndex((i) => equals(i, item))
22
+ }
package/src/mapping.js ADDED
@@ -0,0 +1,21 @@
1
+ import { DEFAULT_FIELDS } from './constants.js'
2
+
3
+ /**
4
+ * Gets the attribute from the node
5
+ * @param {*} node
6
+ * @param {string} attr
7
+ * @returns {*}
8
+ */
9
+ export function getAttribute(node, attr) {
10
+ return typeof node === 'object' && node !== null && attr !== null ? node[attr] : null
11
+ }
12
+
13
+ /**
14
+ * Fetches the fieldmapping for a child node
15
+ *
16
+ * @param {import('./types').FieldMapping} fields
17
+ * @returns {import('./types').FieldMapping}
18
+ */
19
+ export function getNestedFields(fields) {
20
+ return { ...DEFAULT_FIELDS, ...(fields.fields ?? fields) }
21
+ }
package/src/nested.js ADDED
@@ -0,0 +1,28 @@
1
+ import { omit } from 'ramda'
2
+ import { DEFAULT_FIELDS } from './constants.js'
3
+
4
+ /**
5
+ * Flattens a nested list of items
6
+ *
7
+ * @param {Array} items
8
+ * @param {import('./types).FieldMapping} fields
9
+ * @param {number} level
10
+ * @returns {Array}
11
+ */
12
+ export function flattenNestedList(items, fields = DEFAULT_FIELDS, level = 0) {
13
+ fields = { ...DEFAULT_FIELDS, ...fields }
14
+ let data = []
15
+ items.forEach((item) => {
16
+ const children = item[fields.children] ?? []
17
+ data = [
18
+ ...data,
19
+ {
20
+ ...omit([fields.children], item),
21
+ [fields.level]: level,
22
+ [fields.parent]: children.length > 0
23
+ },
24
+ ...flattenNestedList(children, fields, level + 1)
25
+ ]
26
+ })
27
+ return data
28
+ }
package/src/string.js ADDED
@@ -0,0 +1,97 @@
1
+ import { filter } from 'ramda'
2
+ /**
3
+ * Capitalizes the first letter of input string
4
+ *
5
+ * @param {String} str
6
+ * @returns {String}
7
+ */
8
+ export function toInitCapCase(text) {
9
+ return text.charAt(0).toUpperCase() + text.slice(1).toLowerCase()
10
+ }
11
+
12
+ /**
13
+ * Convert a hyphen separated string to PascalCase
14
+ *
15
+ * @param {String} text
16
+ * @returns
17
+ */
18
+ export function toPascalCase(text) {
19
+ return text
20
+ .split('-')
21
+ .map((part) => toInitCapCase(part))
22
+ .join('')
23
+ }
24
+
25
+ /**
26
+ * Convert a PascalCase string to snake case with separator as hyphen
27
+ *
28
+ * @param {string} text
29
+ * @returns {string}
30
+ */
31
+ export function toHyphenCase(text) {
32
+ return text
33
+ .replace(/\s+/, '-')
34
+ .replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)
35
+ .replace(/^-/, '')
36
+ }
37
+
38
+ /**
39
+ * Simple comparison for two strings
40
+ *
41
+ * @param {String} a
42
+ * @param {String} b
43
+ * @returns
44
+ */
45
+ export function compareStrings(a, b) {
46
+ return a > b ? 1 : a < b ? -1 : 0
47
+ }
48
+
49
+ /**
50
+ * Sort by splitting hyphen separated strings while keeping strings with same number of parts together
51
+ *
52
+ * @param {String} a hyphen separates string
53
+ * @param {String} b hyphen separates string
54
+ * @param {string} separator - separator to split the string
55
+ * @returns {Number} -1, 0, 1 based on comparison
56
+ */
57
+ export function sortByParts(a, b, separator = '-') {
58
+ const partsOfA = a.split(separator)
59
+ const partsOfB = b.split(separator)
60
+
61
+ let result = compareStrings(partsOfA[0], partsOfB[0])
62
+ if (result === 0) result = partsOfA.length - partsOfB.length
63
+ if (result === 0) result = compareStrings(a, b)
64
+ return result
65
+ }
66
+
67
+ /**
68
+ * Generates a unique id from current timestamp
69
+ *
70
+ * @returns {String} timestamp based unique id
71
+ */
72
+ export function uniqueId(prefix = '', separator = '-') {
73
+ const pair = prefix && prefix.length > 0 ? [prefix] : []
74
+ pair.push(Date.now().toString(36))
75
+ return pair.join(separator)
76
+ }
77
+
78
+ /**
79
+ * Removes undefined and null values from the input object.
80
+ *
81
+ * @param {Object} obj
82
+ * @returns {Object}
83
+ */
84
+ export function compact(obj) {
85
+ return filter((x) => x !== undefined && x !== null, obj)
86
+ }
87
+
88
+ /**
89
+ * Converts an input number into it's hexadecimal representation, with optional left padded zeroes based on the `size`
90
+ *
91
+ * @param {number} value
92
+ * @param {number} size
93
+ * @returns
94
+ */
95
+ export function toHexString(value, size = 2) {
96
+ return value.toString(16).padStart(size, '0')
97
+ }
package/src/theme.ts ADDED
@@ -0,0 +1,208 @@
1
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
2
+ // @ts-nocheck
3
+ import { DEFAULT_THEME_MAPPING, defaultColors, TONE_MAP } from './constants'
4
+ import { shades } from './colors/index'
5
+ import { colorToRgb } from './utils'
6
+
7
+ const modifiers = {
8
+ hsl: (value) => `hsl(${value} / <alpha-value>)`,
9
+ rgb: (value) => `rgb(${value} / <alpha-value>)`,
10
+ none: (value) => value
11
+ }
12
+
13
+ /**
14
+ * Generate shades for a color using css varuable
15
+ *
16
+ * @param {string} name
17
+ * @param {string} modifier
18
+ * @returns
19
+ */
20
+ export function shadesOf(name, modifier = 'none') {
21
+ const fn = modifier in modifiers ? modifiers[modifier] : modifiers.none
22
+
23
+ return shades.reduce(
24
+ (result, shade) => ({
25
+ ...result,
26
+ [shade]: fn(`var(--color-${name}-${shade})`)
27
+ }),
28
+ {
29
+ DEFAULT: fn(`var(--color-${name}-500)`)
30
+ }
31
+ )
32
+ }
33
+
34
+ /**
35
+ * Generates color rules for a specific theme variant, for both light and dark modes.
36
+ *
37
+ * @param {string} variant - The name of the variant to generate rules for.
38
+ * @param {Object} colors - The object containing color definitions.
39
+ * @param {Object} mapping - An object that maps variant names to color property names.
40
+ * @returns {import('./types').ShadeMappings} An array containing the color rules for both light and dark modes.
41
+ */
42
+ function generateColorRules(variant, colors, mapping) {
43
+ return ['DEFAULT', ...shades].flatMap((shade) => [
44
+ {
45
+ key: shade === 'DEFAULT' ? `--color-${variant}` : `--color-${variant}-${shade}`,
46
+ value: colorToRgb(colors[mapping[variant]][`${shade}`])
47
+ }
48
+ ])
49
+ }
50
+
51
+ /**
52
+ * Constructs and returns the light and dark theme variants based on provided color mapping and color definitions.
53
+ *
54
+ * @param {Object} [mapping=DEFAULT_THEME_MAPPING] - An object mapping variant names to color property names.
55
+ * @param {Object} [colors=defaultColors] - The object containing default color definitions.
56
+ * @returns {Array<Array>} An array containing two arrays, one for the light theme variant and another for the dark theme.
57
+ */
58
+ export function themeRules(mapping = DEFAULT_THEME_MAPPING, colors = defaultColors) {
59
+ mapping = { ...DEFAULT_THEME_MAPPING, ...mapping }
60
+ colors = { ...defaultColors, ...colors }
61
+ const variants = Object.keys(mapping)
62
+ const rules = variants
63
+ .flatMap((variant) => generateColorRules(variant, colors, mapping))
64
+ .reduce((acc, { key, value }) => ({ ...acc, [key]: value }), {})
65
+
66
+ return rules
67
+ }
68
+
69
+ const SEMANTIC_PREFIXES = [
70
+ 'bg',
71
+ 'border',
72
+ 'border-l',
73
+ 'border-r',
74
+ 'border-t',
75
+ 'border-b',
76
+ 'text',
77
+ 'ring',
78
+ 'outline',
79
+ 'from',
80
+ 'to',
81
+ 'divide',
82
+ 'stroke',
83
+ 'fill'
84
+ ]
85
+
86
+ /**
87
+ * @param {{ prefix: string, name: string, toneName: string, lightValue: number, darkValue: number }} opts
88
+ * @returns {Array}
89
+ */
90
+ function toneShortcuts({ prefix, name, toneName, lightValue, darkValue }) {
91
+ const variantPattern = new RegExp(`^(.+):${prefix}-${name}-${toneName}(\\/\\d+)?$`)
92
+ const opacityPattern = new RegExp(`^${prefix}-${name}-${toneName}(\\/\\d+)?$`)
93
+ const exactPattern = `${prefix}-${name}-${toneName}`
94
+ return [
95
+ [
96
+ variantPattern,
97
+ ([, variant, end]) =>
98
+ `${variant}:${prefix}-${name}-${lightValue}${end || ''} ${variant}:dark:${prefix}-${name}-${darkValue}${end || ''}`
99
+ ],
100
+ [
101
+ opacityPattern,
102
+ ([, end]) =>
103
+ `${prefix}-${name}-${lightValue}${end || ''} dark:${prefix}-${name}-${darkValue}${end || ''}`
104
+ ],
105
+ [exactPattern, `${prefix}-${name}-${lightValue} dark:${prefix}-${name}-${darkValue}`]
106
+ ]
107
+ }
108
+
109
+ /**
110
+ * Generates UnoCSS shortcut definitions for semantic tones with bg, border, text.
111
+ * @param {string} name - Color name (e.g., 'primary')
112
+ * @returns {Array} Array of shortcut definitions
113
+ */
114
+ export function semanticShortcuts(name) {
115
+ const shortcuts = []
116
+ for (const [toneName, lightValue] of Object.entries(TONE_MAP)) {
117
+ const darkValue = 1000 - lightValue
118
+ for (const prefix of SEMANTIC_PREFIXES) {
119
+ shortcuts.push(...toneShortcuts({ prefix, name, toneName, lightValue, darkValue }))
120
+ }
121
+ }
122
+ return shortcuts
123
+ }
124
+
125
+ /**
126
+ * Generates "on-color" text shortcuts for readable text on colored backgrounds.
127
+ *
128
+ * - `text-on-{name}` → high contrast text for use on z5+ backgrounds (always light text)
129
+ * - `text-on-{name}-muted` → slightly muted but still readable on z5+ backgrounds
130
+ *
131
+ * @param {string} name - Color name (e.g., 'primary', 'surface')
132
+ * @returns {Array} Array of shortcut definitions
133
+ */
134
+ export function contrastShortcuts(name) {
135
+ return [
136
+ [`text-on-${name}`, `text-${name}-50 dark:text-${name}-50`],
137
+ [`text-on-${name}-muted`, `text-${name}-100 dark:text-${name}-200`]
138
+ ]
139
+ }
140
+
141
+ /**
142
+ * Theme class for managing color palettes, mappings, and semantic shortcuts.
143
+ */
144
+ export class Theme {
145
+ #colors
146
+ #mapping
147
+
148
+ /**
149
+ *
150
+ * @param {import('./types.js').ColorTheme} param0
151
+ */
152
+ constructor({ colors = defaultColors, mapping = DEFAULT_THEME_MAPPING } = {}) {
153
+ this.#colors = { ...defaultColors, ...colors }
154
+ this.#mapping = { ...DEFAULT_THEME_MAPPING, ...mapping }
155
+ }
156
+
157
+ get colors() {
158
+ return this.#colors
159
+ }
160
+ set colors(colors) {
161
+ this.#colors = { ...colors }
162
+ }
163
+
164
+ get mapping() {
165
+ return this.#mapping
166
+ }
167
+ set mapping(mapping) {
168
+ this.#mapping = { ...mapping }
169
+ }
170
+
171
+ mapVariant(color, variant) {
172
+ return Object.keys(color).reduce(
173
+ (acc, key) => ({
174
+ ...acc,
175
+ [key]:
176
+ key === 'DEFAULT'
177
+ ? `rgba(var(--color-${variant}),<alpha-value>)`
178
+ : `rgba(var(--color-${variant}-${key}),<alpha-value>)`
179
+ }),
180
+ {}
181
+ )
182
+ }
183
+
184
+ getColorRules(mapping = null) {
185
+ const variants = Object.entries({ ...this.#mapping, ...mapping })
186
+ return variants.reduce(
187
+ (acc, [variant, key]) => ({
188
+ ...acc,
189
+ [variant]: this.mapVariant(this.#colors[key], variant)
190
+ }),
191
+ {}
192
+ )
193
+ }
194
+
195
+ getPalette(mapping = null) {
196
+ const useMapping = { ...this.#mapping, ...mapping }
197
+ const useColors = { ...defaultColors, ...this.#colors }
198
+ const variants = Object.keys(useMapping)
199
+ const rules = variants
200
+ .flatMap((variant) => generateColorRules(variant, useColors, useMapping))
201
+ .reduce((acc, { key, value }) => ({ ...acc, [key]: value }), {})
202
+ return rules
203
+ }
204
+
205
+ getShortcuts(name) {
206
+ return [...semanticShortcuts(name), ...contrastShortcuts(name)]
207
+ }
208
+ }
package/src/ticks.js ADDED
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Generates an array of tick marks for a range of values.
3
+ *
4
+ * @param {number} lowerBound - The lower bound of the range.
5
+ * @param {number} upperBound - The upper bound of the range.
6
+ * @param {number} [minorTickStep=upperBound-lowerBound] - The step size for minor ticks.
7
+ * @param {number} [majorTickStep=1] - The step size for major ticks.
8
+ * @returns {import('./types').TickMark[]>} An array of tick mark objects.
9
+ */
10
+ export function generateTicks(
11
+ lowerBound,
12
+ upperBound,
13
+ minorTickStep = upperBound - lowerBound,
14
+ majorTickStep = 1
15
+ ) {
16
+ const length = 1 + Math.ceil((upperBound - lowerBound) / minorTickStep)
17
+ return Array.from({ length }, (_, i) => {
18
+ const value = i === length - 1 ? upperBound : lowerBound + minorTickStep * i
19
+ const major = i === 0 || i === length - 1 || i % majorTickStep === 0
20
+ return {
21
+ value,
22
+ label: major ? value : '',
23
+ major
24
+ }
25
+ })
26
+ }