@nordcraft/core 1.0.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.
Files changed (119) hide show
  1. package/README.md +5 -0
  2. package/dist/api/LegacyToddleApi.d.ts +34 -0
  3. package/dist/api/LegacyToddleApi.js +178 -0
  4. package/dist/api/LegacyToddleApi.js.map +1 -0
  5. package/dist/api/ToddleApiV2.d.ts +77 -0
  6. package/dist/api/ToddleApiV2.js +271 -0
  7. package/dist/api/ToddleApiV2.js.map +1 -0
  8. package/dist/api/api.d.ts +49 -0
  9. package/dist/api/api.js +276 -0
  10. package/dist/api/api.js.map +1 -0
  11. package/dist/api/apiTypes.d.ts +125 -0
  12. package/dist/api/apiTypes.js +11 -0
  13. package/dist/api/apiTypes.js.map +1 -0
  14. package/dist/api/headers.d.ts +10 -0
  15. package/dist/api/headers.js +36 -0
  16. package/dist/api/headers.js.map +1 -0
  17. package/dist/api/template.d.ts +5 -0
  18. package/dist/api/template.js +7 -0
  19. package/dist/api/template.js.map +1 -0
  20. package/dist/component/ToddleComponent.d.ts +45 -0
  21. package/dist/component/ToddleComponent.js +373 -0
  22. package/dist/component/ToddleComponent.js.map +1 -0
  23. package/dist/component/actionUtils.d.ts +2 -0
  24. package/dist/component/actionUtils.js +56 -0
  25. package/dist/component/actionUtils.js.map +1 -0
  26. package/dist/component/component.types.d.ts +313 -0
  27. package/dist/component/component.types.js +9 -0
  28. package/dist/component/component.types.js.map +1 -0
  29. package/dist/component/isPageComponent.d.ts +2 -0
  30. package/dist/component/isPageComponent.js +3 -0
  31. package/dist/component/isPageComponent.js.map +1 -0
  32. package/dist/formula/ToddleFormula.d.ts +15 -0
  33. package/dist/formula/ToddleFormula.js +20 -0
  34. package/dist/formula/ToddleFormula.js.map +1 -0
  35. package/dist/formula/formula.d.ts +103 -0
  36. package/dist/formula/formula.js +186 -0
  37. package/dist/formula/formula.js.map +1 -0
  38. package/dist/formula/formulaTypes.d.ts +30 -0
  39. package/dist/formula/formulaTypes.js +2 -0
  40. package/dist/formula/formulaTypes.js.map +1 -0
  41. package/dist/formula/formulaUtils.d.ts +18 -0
  42. package/dist/formula/formulaUtils.js +240 -0
  43. package/dist/formula/formulaUtils.js.map +1 -0
  44. package/dist/styling/className.d.ts +2 -0
  45. package/dist/styling/className.js +52 -0
  46. package/dist/styling/className.js.map +1 -0
  47. package/dist/styling/style.css.d.ts +5 -0
  48. package/dist/styling/style.css.js +242 -0
  49. package/dist/styling/style.css.js.map +1 -0
  50. package/dist/styling/theme.const.d.ts +3 -0
  51. package/dist/styling/theme.const.js +381 -0
  52. package/dist/styling/theme.const.js.map +1 -0
  53. package/dist/styling/theme.d.ts +72 -0
  54. package/dist/styling/theme.js +200 -0
  55. package/dist/styling/theme.js.map +1 -0
  56. package/dist/styling/variantSelector.d.ts +123 -0
  57. package/dist/styling/variantSelector.js +25 -0
  58. package/dist/styling/variantSelector.js.map +1 -0
  59. package/dist/types.d.ts +97 -0
  60. package/dist/types.js +2 -0
  61. package/dist/types.js.map +1 -0
  62. package/dist/utils/collections.d.ts +21 -0
  63. package/dist/utils/collections.js +75 -0
  64. package/dist/utils/collections.js.map +1 -0
  65. package/dist/utils/customElements.d.ts +6 -0
  66. package/dist/utils/customElements.js +15 -0
  67. package/dist/utils/customElements.js.map +1 -0
  68. package/dist/utils/hash.d.ts +1 -0
  69. package/dist/utils/hash.js +17 -0
  70. package/dist/utils/hash.js.map +1 -0
  71. package/dist/utils/json.d.ts +5 -0
  72. package/dist/utils/json.js +16 -0
  73. package/dist/utils/json.js.map +1 -0
  74. package/dist/utils/sha1.d.ts +2 -0
  75. package/dist/utils/sha1.js +13 -0
  76. package/dist/utils/sha1.js.map +1 -0
  77. package/dist/utils/url.d.ts +5 -0
  78. package/dist/utils/url.js +27 -0
  79. package/dist/utils/url.js.map +1 -0
  80. package/dist/utils/util.d.ts +3 -0
  81. package/dist/utils/util.js +4 -0
  82. package/dist/utils/util.js.map +1 -0
  83. package/package.json +18 -0
  84. package/src/api/LegacyToddleApi.ts +205 -0
  85. package/src/api/ToddleApiV2.ts +331 -0
  86. package/src/api/api.test.ts +319 -0
  87. package/src/api/api.ts +414 -0
  88. package/src/api/apiTypes.ts +145 -0
  89. package/src/api/headers.ts +41 -0
  90. package/src/api/template.ts +10 -0
  91. package/src/component/ToddleComponent.actionReferences.test.ts +75 -0
  92. package/src/component/ToddleComponent.formulasInComponent.test.ts +234 -0
  93. package/src/component/ToddleComponent.ts +470 -0
  94. package/src/component/actionUtils.ts +61 -0
  95. package/src/component/component.types.ts +362 -0
  96. package/src/component/isPageComponent.ts +6 -0
  97. package/src/formula/ToddleFormula.ts +30 -0
  98. package/src/formula/formula.ts +355 -0
  99. package/src/formula/formulaTypes.ts +45 -0
  100. package/src/formula/formulaUtils.ts +287 -0
  101. package/src/styling/className.test.ts +19 -0
  102. package/src/styling/className.ts +73 -0
  103. package/src/styling/style.css.ts +309 -0
  104. package/src/styling/theme.const.ts +390 -0
  105. package/src/styling/theme.ts +339 -0
  106. package/src/styling/variantSelector.ts +168 -0
  107. package/src/types.ts +158 -0
  108. package/src/utils/collections.test.ts +57 -0
  109. package/src/utils/collections.ts +122 -0
  110. package/src/utils/customElements.test.ts +40 -0
  111. package/src/utils/customElements.ts +16 -0
  112. package/src/utils/hash.test.ts +32 -0
  113. package/src/utils/hash.ts +18 -0
  114. package/src/utils/json.ts +18 -0
  115. package/src/utils/sha1.test.ts +50 -0
  116. package/src/utils/sha1.ts +17 -0
  117. package/src/utils/url.test.ts +17 -0
  118. package/src/utils/url.ts +33 -0
  119. package/src/utils/util.ts +8 -0
@@ -0,0 +1,73 @@
1
+ // cSpell: ignore phash
2
+ const SEED = 5381
3
+
4
+ // When we have separate strings it's useful to run a progressive
5
+ // version of djb2 where we pretend that we're still looping over
6
+ // the same string
7
+ const phash = (h: number, x: string) => {
8
+ let i = x.length
9
+
10
+ while (i) {
11
+ h = (h * 33) ^ x.charCodeAt(--i)
12
+ }
13
+
14
+ return h
15
+ }
16
+
17
+ // This is a djb2 hashing function
18
+ const hash = (x: string) => {
19
+ return phash(SEED, x)
20
+ }
21
+
22
+ const AD_REPLACER_R = /(a)(d)/gi
23
+
24
+ /* This is the "capacity" of our alphabet i.e. 2x26 for all letters plus their capitalized
25
+ * counterparts */
26
+ const charsLength = 52
27
+
28
+ /* start at 75 for 'a' until 'z' (25) and then start at 65 for capitalized letters */
29
+ const getAlphabeticChar = (code: number) =>
30
+ String.fromCharCode(code + (code > 25 ? 39 : 97))
31
+
32
+ /* input a number, usually a hash and convert it to base-52 */
33
+ function generateAlphabeticName(code: number) {
34
+ let name = ''
35
+ let x
36
+
37
+ /* get a char and divide by alphabet-length */
38
+ for (x = Math.abs(code); x > charsLength; x = (x / charsLength) | 0) {
39
+ name = getAlphabeticChar(x % charsLength) + name
40
+ }
41
+
42
+ return (getAlphabeticChar(x % charsLength) + name).replace(
43
+ AD_REPLACER_R,
44
+ '$1-$2',
45
+ )
46
+ }
47
+
48
+ export const getClassName = (object: any) => {
49
+ return generateAlphabeticName(hash(JSON.stringify(object)))
50
+ }
51
+
52
+ export const toValidClassName = (
53
+ input: string,
54
+ escapeSpecialCharacters = false,
55
+ ) => {
56
+ // Replace invalid characters with hyphens
57
+ let className = input
58
+ // Remove leading and trailing whitespace
59
+ .trim()
60
+ // Replace whitespace with hyphens
61
+ .replace(/\s+/g, '-')
62
+
63
+ if (escapeSpecialCharacters) {
64
+ className = className.replace(/[^a-zA-Z0-9-_]/g, (match) => `\\${match}`)
65
+ }
66
+
67
+ // Ensure the class name doesn't start with a number or special character
68
+ if (/^[^a-zA-Z]/.test(className)) {
69
+ className = `_${className}`
70
+ }
71
+
72
+ return className
73
+ }
@@ -0,0 +1,309 @@
1
+ // cSpell:ignore thinn, ABCDEFGHIJKLMNOPQRSTYVWXYZ
2
+ import type { Component } from '../component/component.types'
3
+ import { omitKeys } from '../utils/collections'
4
+ import { isDefined } from '../utils/util'
5
+ import { getClassName, toValidClassName } from './className'
6
+ import type { OldTheme, Theme, ThemeOptions } from './theme'
7
+ import { getThemeCss } from './theme'
8
+ import type {
9
+ ComponentNodeModel,
10
+ ElementNodeModel,
11
+ StyleDeclarationBlock,
12
+ } from './variantSelector'
13
+ import { variantSelector } from './variantSelector'
14
+
15
+ const LEGACY_BREAKPOINTS = {
16
+ large: 1440,
17
+ small: 576,
18
+ medium: 960,
19
+ }
20
+
21
+ export function kebabCase(string: string) {
22
+ return string
23
+ .split('')
24
+ .map((char) => {
25
+ return 'ABCDEFGHIJKLMNOPQRSTYVWXYZ'.includes(char)
26
+ ? '-' + char.toLocaleLowerCase()
27
+ : char
28
+ })
29
+ .join('')
30
+ }
31
+
32
+ const SIZE_PROPERTIES = new Set([
33
+ 'width',
34
+ 'min-width',
35
+ 'max-width',
36
+ 'height',
37
+ 'min-height',
38
+ 'max-height',
39
+ 'margin',
40
+ 'margin-top',
41
+ 'margin-left',
42
+ 'margin-bottom',
43
+ 'margin-right',
44
+ 'padding',
45
+ 'padding-top',
46
+ 'padding-left',
47
+ 'padding-bottom',
48
+ 'padding-right',
49
+ 'gap',
50
+ 'gap-x',
51
+ 'gap-y',
52
+ 'border-radius',
53
+ 'border-bottom-left-radius',
54
+ 'border-bottom-right-radius',
55
+ 'border-top-left-radius',
56
+ 'border-top-right-radius',
57
+ 'border-width',
58
+ 'border-top-width',
59
+ 'border-left-width',
60
+ 'border-bottom-width',
61
+ 'border-right-width',
62
+ 'font-size',
63
+ 'top',
64
+ 'right',
65
+ 'bottom',
66
+ 'left',
67
+ 'outline-width',
68
+ ])
69
+
70
+ export const createStylesheet = (
71
+ root: Component,
72
+ components: Component[],
73
+ theme: Theme | OldTheme,
74
+ options: ThemeOptions,
75
+ // eslint-disable-next-line max-params
76
+ ) => {
77
+ const hashes = new Set<string>()
78
+
79
+ // Get fonts used on the page
80
+ const fonts = getAllFonts(components)
81
+
82
+ //Exclude fonts that are not used on this page.
83
+ let stylesheet = getThemeCss(
84
+ 'breakpoints' in theme
85
+ ? {
86
+ ...theme,
87
+ fontFamily: Object.fromEntries(
88
+ Object.entries(theme.fontFamily).filter(
89
+ ([key, value]) => value.default ?? fonts.has('--font-' + key),
90
+ ),
91
+ ),
92
+ }
93
+ : {
94
+ ...theme,
95
+ fonts: theme.fonts,
96
+ },
97
+ options,
98
+ )
99
+ const styleToCss = (style: StyleDeclarationBlock) => {
100
+ return Object.entries(style)
101
+ .map(([property, value]) => {
102
+ if (!isDefined(value)) {
103
+ // ignore undefined/null values
104
+ return
105
+ }
106
+ const propertyName = kebabCase(property)
107
+ const propertyValue =
108
+ String(Number(value)) === String(value) &&
109
+ SIZE_PROPERTIES.has(propertyName)
110
+ ? `${Number(value) * 4}px`
111
+ : value
112
+ return `${propertyName}:${propertyValue};`
113
+ })
114
+ .filter(Boolean)
115
+ .join('\n ')
116
+ }
117
+ const getNodeStyles = (
118
+ node: ElementNodeModel | ComponentNodeModel,
119
+ classHash: string,
120
+ ) => {
121
+ try {
122
+ const style = omitKeys(node.style ?? {}, [
123
+ 'variants',
124
+ 'breakpoints',
125
+ 'shadows',
126
+ ])
127
+ const styleVariants = node.variants ?? node.style?.variants
128
+ const renderVariant = (
129
+ selector: string,
130
+ style: StyleDeclarationBlock,
131
+ options?: { startingStyle?: boolean },
132
+ ) => {
133
+ const scrollbarStyles = Object.entries(style).filter(
134
+ ([key]) => key == 'scrollbar-width',
135
+ )
136
+ // If selectorCss is empty, we don't need to render the selector
137
+ let styles = styleToCss(style)
138
+ if (options?.startingStyle) {
139
+ styles = `@starting-style {
140
+ ${styles}
141
+ }`
142
+ }
143
+
144
+ return `
145
+ ${
146
+ styles.length > 0
147
+ ? `${selector} {
148
+ ${styles}
149
+ }`
150
+ : ''
151
+ }
152
+ ${
153
+ scrollbarStyles.length > 0
154
+ ? `
155
+ ${selector}::-webkit-scrollbar {
156
+ ${scrollbarStyles
157
+ .map(([_, value]) => {
158
+ switch (value) {
159
+ case 'none':
160
+ return 'width: 0;'
161
+ case 'thinn':
162
+ case 'thin':
163
+ return 'width: 4px;'
164
+ default:
165
+ return ''
166
+ }
167
+ })
168
+ .join('\n')}
169
+ }
170
+ `
171
+ : ''
172
+ }
173
+ `
174
+ }
175
+
176
+ return `
177
+ ${renderVariant('.' + classHash, style)}
178
+ ${(styleVariants ?? [])
179
+ .map((variant) => {
180
+ const renderedVariant = renderVariant(
181
+ `.${classHash}${variantSelector(variant)}`,
182
+ variant.style,
183
+ {
184
+ startingStyle: variant.startingStyle,
185
+ },
186
+ )
187
+
188
+ if (variant.mediaQuery) {
189
+ return `
190
+ @media (${Object.entries(variant.mediaQuery)
191
+ .map(([key, value]) => `${key}: ${value}`)
192
+ .filter(Boolean)
193
+ .join(') and (')}) {
194
+ ${renderedVariant}
195
+ }
196
+ `
197
+ }
198
+
199
+ if (variant.breakpoint) {
200
+ return `
201
+ @media (min-width: ${LEGACY_BREAKPOINTS[variant.breakpoint]}px) {
202
+ ${renderedVariant}
203
+ }
204
+ `
205
+ }
206
+
207
+ return renderedVariant
208
+ })
209
+ .join('\n')}
210
+ ${
211
+ node.animations
212
+ ? Object.entries(node.animations)
213
+ .map(([animationName, keyframes]) => {
214
+ return `
215
+ @keyframes ${animationName} {
216
+ ${Object.values(keyframes)
217
+ .sort((a, b) => a.position - b.position)
218
+ .map(({ key, position, value }) => {
219
+ return `
220
+ ${position * 100}% {
221
+ ${key}: ${value};
222
+ }
223
+ `
224
+ })
225
+ .join('\n')}
226
+ }
227
+ `
228
+ })
229
+ .join('\n')
230
+ : ''
231
+ }
232
+ `
233
+ } catch (e) {
234
+ // eslint-disable-next-line no-console
235
+ console.error(e)
236
+ return ''
237
+ }
238
+ }
239
+
240
+ // Make sure that CSS for dependencies are rendered first so that instance styles can override
241
+ const visitedComponents = new Set<string>()
242
+ function insertComponentStyles(
243
+ component: Component,
244
+ package_name?: string,
245
+ ): string | undefined {
246
+ if (visitedComponents.has(component.name)) {
247
+ return
248
+ }
249
+ visitedComponents.add(component.name)
250
+ if (!component.nodes) {
251
+ // eslint-disable-next-line no-console
252
+ console.warn('Unable to find nodes for component', component.name)
253
+ return
254
+ }
255
+ Object.entries(component.nodes).forEach(([id, node]) => {
256
+ if (node.type === 'component') {
257
+ const childComponent = components.find(
258
+ (c) =>
259
+ c.name ===
260
+ [node.package ?? package_name, node.name]
261
+ .filter((c) => c)
262
+ .join('/'),
263
+ )
264
+ if (childComponent) {
265
+ insertComponentStyles(childComponent, node.package ?? package_name)
266
+ stylesheet += getNodeStyles(
267
+ node as any,
268
+ toValidClassName(`${component.name}:${id}`, true),
269
+ )
270
+
271
+ return
272
+ }
273
+ }
274
+ if (node.type !== 'element') {
275
+ return
276
+ }
277
+ const classHash = getClassName([node.style, node.variants])
278
+ if (hashes.has(classHash)) {
279
+ return ''
280
+ }
281
+ hashes.add(classHash)
282
+ stylesheet += getNodeStyles(node as any, classHash)
283
+ })
284
+ }
285
+ insertComponentStyles(root)
286
+
287
+ return stylesheet
288
+ }
289
+
290
+ export const getAllFonts = (components: Component[]) => {
291
+ return new Set(
292
+ components
293
+ .flatMap((component) => {
294
+ return Object.values(component.nodes).flatMap((node) => {
295
+ if (node.type === 'element') {
296
+ return [
297
+ node.style.fontFamily,
298
+ node.style['font-family'],
299
+ ...(node.variants?.map(
300
+ (v) => v.style.fontFamily ?? v.style['font-family'],
301
+ ) ?? []),
302
+ ].filter(isDefined)
303
+ }
304
+ return []
305
+ })
306
+ })
307
+ .map((f) => f.replace('var(', '').replace(')', '').replace(/'/g, '')),
308
+ )
309
+ }