purse-styles 0.0.1
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/LICENSE.md +21 -0
- package/README.md +90 -0
- package/dist/clsx.d.ts +1 -0
- package/dist/cssVar.d.ts +2 -0
- package/dist/destructors.d.ts +2 -0
- package/dist/hashObject.d.ts +1 -0
- package/dist/hyphenateStyleName.d.ts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +411 -0
- package/dist/index.js.map +6 -0
- package/dist/purse.d.ts +42 -0
- package/dist/useRequiredContext.d.ts +2 -0
- package/package.json +44 -0
- package/src/clsx.ts +3 -0
- package/src/cssVar.ts +5 -0
- package/src/destructors.ts +7 -0
- package/src/hashObject.ts +25 -0
- package/src/hyphenateStyleName.ts +16 -0
- package/src/index.ts +11 -0
- package/src/purse.test.ts +97 -0
- package/src/purse.tsx +576 -0
- package/src/useRequiredContext.tsx +11 -0
package/src/purse.tsx
ADDED
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
import CSS from "csstype"
|
|
2
|
+
import { entries, isObject, mapValues } from "lodash-es"
|
|
3
|
+
import React, {
|
|
4
|
+
DependencyList,
|
|
5
|
+
createContext,
|
|
6
|
+
useInsertionEffect,
|
|
7
|
+
useLayoutEffect,
|
|
8
|
+
useMemo,
|
|
9
|
+
} from "react"
|
|
10
|
+
import { clsx } from "./clsx"
|
|
11
|
+
import { CSSVar } from "./cssVar"
|
|
12
|
+
import { Destructor, joinDestructors } from "./destructors"
|
|
13
|
+
import { hashObject } from "./hashObject"
|
|
14
|
+
import { hyphenateStyleName } from "./hyphenateStyleName"
|
|
15
|
+
import { useRequiredContext } from "./useRequiredContext"
|
|
16
|
+
|
|
17
|
+
const UNITLESS_NUMBER_PROPS = [
|
|
18
|
+
"animation-iteration-count",
|
|
19
|
+
"border-image-outset",
|
|
20
|
+
"border-image-slice",
|
|
21
|
+
"border-image-width",
|
|
22
|
+
"box-flex",
|
|
23
|
+
"box-flex-group",
|
|
24
|
+
"box-ordinal-group",
|
|
25
|
+
"column-count",
|
|
26
|
+
"columns",
|
|
27
|
+
"flex",
|
|
28
|
+
"flex-grow",
|
|
29
|
+
"flex-positive",
|
|
30
|
+
"flex-shrink",
|
|
31
|
+
"flex-negative",
|
|
32
|
+
"flex-order",
|
|
33
|
+
"grid-row",
|
|
34
|
+
"grid-row-end",
|
|
35
|
+
"grid-row-span",
|
|
36
|
+
"grid-row-start",
|
|
37
|
+
"grid-column",
|
|
38
|
+
"grid-column-end",
|
|
39
|
+
"grid-column-span",
|
|
40
|
+
"grid-column-start",
|
|
41
|
+
"font-weight",
|
|
42
|
+
"line-clamp",
|
|
43
|
+
"line-height",
|
|
44
|
+
"opacity",
|
|
45
|
+
"order",
|
|
46
|
+
"orphans",
|
|
47
|
+
"tabSize",
|
|
48
|
+
"widows",
|
|
49
|
+
"z-index",
|
|
50
|
+
"zoom",
|
|
51
|
+
|
|
52
|
+
// SVG-related properties
|
|
53
|
+
"fill-opacity",
|
|
54
|
+
"flood-opacity",
|
|
55
|
+
"stop-opacity",
|
|
56
|
+
"stroke-dasharray",
|
|
57
|
+
"stroke-dashoffset",
|
|
58
|
+
"stroke-miterlimit",
|
|
59
|
+
"stroke-opacity",
|
|
60
|
+
"stroke-width",
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
type BaseCSSProperties = CSS.Properties<number | string, string & {}>
|
|
64
|
+
type CSSVarDeclarations = Record<CSSVar, string>
|
|
65
|
+
|
|
66
|
+
type NestedRuleKey =
|
|
67
|
+
| `&${CSS.SimplePseudos}`
|
|
68
|
+
| `${CSS.AtRules}${string}`
|
|
69
|
+
| `.${string}`
|
|
70
|
+
| `&${string}`
|
|
71
|
+
|
|
72
|
+
export type CSSProperties = {
|
|
73
|
+
[Key in keyof BaseCSSProperties]?: BaseCSSProperties[Key]
|
|
74
|
+
} & {
|
|
75
|
+
[Key in NestedRuleKey]?: BaseCSSProperties
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const PurseContext = createContext<StyleApi | undefined>(undefined)
|
|
79
|
+
|
|
80
|
+
export type StyleApi = {
|
|
81
|
+
addStyleElement: (styleElement: StyleElement) => Destructor
|
|
82
|
+
addGlobalStyle: (styleRule: string) => Destructor
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const __style__ = Symbol("style-element")
|
|
86
|
+
|
|
87
|
+
type StyleRule = string
|
|
88
|
+
|
|
89
|
+
export type StyleElement = {
|
|
90
|
+
__style__: typeof __style__
|
|
91
|
+
composed: StyleElement[]
|
|
92
|
+
owned?: {
|
|
93
|
+
styleRules: StyleRule[]
|
|
94
|
+
className: string
|
|
95
|
+
}
|
|
96
|
+
className: string
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function isStyleElement(o: any): o is StyleElement {
|
|
100
|
+
return "__style__" in o && o["__style__"] === __style__
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const DEV = process.env.NODE_ENV === "development"
|
|
104
|
+
|
|
105
|
+
export type InMemoryStyleApi = {
|
|
106
|
+
styleRulesRef: { current: string[] }
|
|
107
|
+
addStyleElement: StyleApi["addStyleElement"]
|
|
108
|
+
addGlobalStyle: StyleApi["addGlobalStyle"]
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function createInMemoryStyleApi(): InMemoryStyleApi {
|
|
112
|
+
let styleRules: string[] = []
|
|
113
|
+
|
|
114
|
+
type InsertedStyleElement = {
|
|
115
|
+
refCount: number
|
|
116
|
+
destructor: Destructor
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const insertedStyleElementClassNames = new Map<string, InsertedStyleElement>()
|
|
120
|
+
|
|
121
|
+
function addStyleRule(rule: StyleRule): Destructor {
|
|
122
|
+
styleRules.push(rule)
|
|
123
|
+
|
|
124
|
+
return () => {
|
|
125
|
+
const ruleToRemove = rule
|
|
126
|
+
styleRules = styleRules.filter((rule) => rule !== ruleToRemove)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function removeStyleElement(styleElement: StyleElement) {
|
|
131
|
+
for (const composed of styleElement.composed) {
|
|
132
|
+
removeStyleElement(composed)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (styleElement.owned) {
|
|
136
|
+
const { className } = styleElement.owned
|
|
137
|
+
|
|
138
|
+
const maybeInsertedStyleElement =
|
|
139
|
+
insertedStyleElementClassNames.get(className)
|
|
140
|
+
if (!maybeInsertedStyleElement) return
|
|
141
|
+
|
|
142
|
+
const { refCount, destructor } = maybeInsertedStyleElement
|
|
143
|
+
const newRefCount = refCount - 1
|
|
144
|
+
|
|
145
|
+
if (newRefCount <= 0) {
|
|
146
|
+
insertedStyleElementClassNames.delete(className)
|
|
147
|
+
destructor()
|
|
148
|
+
} else {
|
|
149
|
+
insertedStyleElementClassNames.set(className, {
|
|
150
|
+
refCount: newRefCount,
|
|
151
|
+
destructor,
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function addStyleElement(styleElement: StyleElement): Destructor {
|
|
158
|
+
for (const composed of styleElement.composed) {
|
|
159
|
+
addStyleElement(composed)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (styleElement.owned) {
|
|
163
|
+
const { className, styleRules } = styleElement.owned
|
|
164
|
+
const maybeInsertedStyleElement =
|
|
165
|
+
insertedStyleElementClassNames.get(className)
|
|
166
|
+
|
|
167
|
+
if (maybeInsertedStyleElement) {
|
|
168
|
+
const { destructor, refCount } = maybeInsertedStyleElement
|
|
169
|
+
insertedStyleElementClassNames.set(className, {
|
|
170
|
+
destructor,
|
|
171
|
+
refCount: refCount + 1,
|
|
172
|
+
})
|
|
173
|
+
} else {
|
|
174
|
+
const destructors: Destructor[] = styleRules.map(addStyleRule)
|
|
175
|
+
const destructor = joinDestructors(destructors)
|
|
176
|
+
|
|
177
|
+
insertedStyleElementClassNames.set(className, {
|
|
178
|
+
destructor,
|
|
179
|
+
refCount: 1,
|
|
180
|
+
})
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return () => removeStyleElement(styleElement)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
addStyleElement,
|
|
189
|
+
addGlobalStyle: addStyleRule,
|
|
190
|
+
styleRulesRef: {
|
|
191
|
+
get current() {
|
|
192
|
+
return styleRules
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function PurseProvider(props: { children?: React.ReactNode }) {
|
|
199
|
+
const htmlStyleElement = useMemo(() => document.createElement("style"), [])
|
|
200
|
+
const atRuleHtmlStyleElement = useMemo(
|
|
201
|
+
() => document.createElement("style"),
|
|
202
|
+
[],
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
useInsertionEffect(() => {
|
|
206
|
+
document.head.appendChild(htmlStyleElement)
|
|
207
|
+
document.head.appendChild(atRuleHtmlStyleElement)
|
|
208
|
+
|
|
209
|
+
return () => {
|
|
210
|
+
document.head.removeChild(htmlStyleElement)
|
|
211
|
+
document.head.removeChild(atRuleHtmlStyleElement)
|
|
212
|
+
}
|
|
213
|
+
}, [htmlStyleElement, atRuleHtmlStyleElement])
|
|
214
|
+
|
|
215
|
+
const styleApi: StyleApi = useMemo(() => {
|
|
216
|
+
type InsertedStyleElement = {
|
|
217
|
+
refCount: number
|
|
218
|
+
destructor: Destructor
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const insertedStyleElementClassNames = new Map<
|
|
222
|
+
string,
|
|
223
|
+
InsertedStyleElement
|
|
224
|
+
>()
|
|
225
|
+
|
|
226
|
+
function addStyleRule(rule: StyleRule): Destructor {
|
|
227
|
+
const styleSheet = htmlStyleElement.sheet
|
|
228
|
+
if (!styleSheet)
|
|
229
|
+
throw new Error(`Could not get style sheet of style element`)
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
// TODO: do these in dev
|
|
233
|
+
// styleSheet.insertRule(styleRule, insertPosition)
|
|
234
|
+
|
|
235
|
+
const isAtRule = rule.startsWith("@")
|
|
236
|
+
const styleText = new Text(rule)
|
|
237
|
+
|
|
238
|
+
if (isAtRule) {
|
|
239
|
+
// atRuleStyleElement.sheet?.insertRule(rule)
|
|
240
|
+
|
|
241
|
+
// We add as Text here because otherwise, the styles don't show up in chrome dev tools
|
|
242
|
+
atRuleHtmlStyleElement.appendChild(styleText)
|
|
243
|
+
|
|
244
|
+
return () => atRuleHtmlStyleElement.removeChild(styleText)
|
|
245
|
+
} else {
|
|
246
|
+
// styleSheet?.insertRule(rule)
|
|
247
|
+
htmlStyleElement.appendChild(styleText)
|
|
248
|
+
|
|
249
|
+
return () => {
|
|
250
|
+
htmlStyleElement.removeChild(styleText)
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
} catch (error) {
|
|
254
|
+
if (DEV) {
|
|
255
|
+
throw new Error(`Could not add style rule ${rule}`)
|
|
256
|
+
} else {
|
|
257
|
+
console.warn(`Could not add style rule ${rule}`)
|
|
258
|
+
return () => {}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function removeStyleElement(styleElement: StyleElement) {
|
|
264
|
+
for (const composed of styleElement.composed) {
|
|
265
|
+
removeStyleElement(composed)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (styleElement.owned) {
|
|
269
|
+
const { className } = styleElement.owned
|
|
270
|
+
|
|
271
|
+
const maybeInsertedStyleElement =
|
|
272
|
+
insertedStyleElementClassNames.get(className)
|
|
273
|
+
if (!maybeInsertedStyleElement) return
|
|
274
|
+
|
|
275
|
+
const { refCount, destructor } = maybeInsertedStyleElement
|
|
276
|
+
const newRefCount = refCount - 1
|
|
277
|
+
|
|
278
|
+
if (newRefCount <= 0) {
|
|
279
|
+
insertedStyleElementClassNames.delete(className)
|
|
280
|
+
destructor()
|
|
281
|
+
} else {
|
|
282
|
+
insertedStyleElementClassNames.set(className, {
|
|
283
|
+
refCount: newRefCount,
|
|
284
|
+
destructor,
|
|
285
|
+
})
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function addStyleElement(styleElement: StyleElement): Destructor {
|
|
291
|
+
for (const composed of styleElement.composed) {
|
|
292
|
+
addStyleElement(composed)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (styleElement.owned) {
|
|
296
|
+
const { className, styleRules } = styleElement.owned
|
|
297
|
+
const maybeInsertedStyleElement =
|
|
298
|
+
insertedStyleElementClassNames.get(className)
|
|
299
|
+
|
|
300
|
+
if (maybeInsertedStyleElement) {
|
|
301
|
+
const { destructor, refCount } = maybeInsertedStyleElement
|
|
302
|
+
insertedStyleElementClassNames.set(className, {
|
|
303
|
+
destructor,
|
|
304
|
+
refCount: refCount + 1,
|
|
305
|
+
})
|
|
306
|
+
} else {
|
|
307
|
+
const destructors: Destructor[] = styleRules.map(addStyleRule)
|
|
308
|
+
const destructor = joinDestructors(destructors)
|
|
309
|
+
|
|
310
|
+
insertedStyleElementClassNames.set(className, {
|
|
311
|
+
destructor,
|
|
312
|
+
refCount: 1,
|
|
313
|
+
})
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return () => removeStyleElement(styleElement)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return { addStyleElement, addGlobalStyle: addStyleRule }
|
|
321
|
+
}, [])
|
|
322
|
+
|
|
323
|
+
return (
|
|
324
|
+
<PurseContext.Provider value={styleApi}>
|
|
325
|
+
{props.children}
|
|
326
|
+
</PurseContext.Provider>
|
|
327
|
+
)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function compileDeclarations(declarations: BaseCSSProperties) {
|
|
331
|
+
// return entries(prefix(declarations))
|
|
332
|
+
return entries(declarations)
|
|
333
|
+
.map(([camelCaseProperty, value]) => {
|
|
334
|
+
const kebabProperty = hyphenateStyleName(camelCaseProperty)
|
|
335
|
+
const needToAddPixelsUnit =
|
|
336
|
+
typeof value === "number" &&
|
|
337
|
+
!UNITLESS_NUMBER_PROPS.includes(kebabProperty)
|
|
338
|
+
|
|
339
|
+
if (needToAddPixelsUnit) {
|
|
340
|
+
return `${kebabProperty}:${value}px;`
|
|
341
|
+
} else {
|
|
342
|
+
return `${kebabProperty}:${value};`
|
|
343
|
+
}
|
|
344
|
+
})
|
|
345
|
+
.join("")
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function groupEntriesBy<K extends string | number | symbol, V>(
|
|
349
|
+
obj: Record<K, V>,
|
|
350
|
+
predicate: (key: K, value: V) => string,
|
|
351
|
+
): Record<string, Record<K, V>> {
|
|
352
|
+
const groupedEntries: Record<string, any> = {}
|
|
353
|
+
|
|
354
|
+
for (const key in obj) {
|
|
355
|
+
const value = obj[key]
|
|
356
|
+
const group = predicate(key, value)
|
|
357
|
+
|
|
358
|
+
if (group in groupedEntries) {
|
|
359
|
+
groupedEntries[group][key] = value
|
|
360
|
+
} else {
|
|
361
|
+
groupedEntries[group] = { [key]: value }
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return groupedEntries
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function isObjectEmpty(obj: {}): boolean {
|
|
369
|
+
return Object.keys(obj).length === 0
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
type CSSPropertiesGroup = {
|
|
373
|
+
"": BaseCSSProperties
|
|
374
|
+
} & {
|
|
375
|
+
[Key in NestedRuleKey]?: BaseCSSProperties
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function mergeCSSProperties(...groups: CSSProperties[]): CSSProperties {
|
|
379
|
+
const merged: CSSProperties = {}
|
|
380
|
+
|
|
381
|
+
for (const group of groups) {
|
|
382
|
+
for (const _propertyOrSelector in group) {
|
|
383
|
+
const propertyOrSelector = _propertyOrSelector as keyof typeof group
|
|
384
|
+
const isNestedRule = isObject(group[propertyOrSelector])
|
|
385
|
+
|
|
386
|
+
if (isNestedRule) {
|
|
387
|
+
// Type-casting to a key that only has nested rules
|
|
388
|
+
const selector = propertyOrSelector as NestedRuleKey
|
|
389
|
+
const existingStyles = merged[selector] || {}
|
|
390
|
+
|
|
391
|
+
merged[selector] = {
|
|
392
|
+
...existingStyles,
|
|
393
|
+
...group[selector],
|
|
394
|
+
}
|
|
395
|
+
} else {
|
|
396
|
+
const property = propertyOrSelector as keyof BaseCSSProperties
|
|
397
|
+
|
|
398
|
+
;(merged as any)[property] = group[property]
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return merged
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function groupCSSProperties(properties: CSSProperties): CSSPropertiesGroup {
|
|
407
|
+
const styleDeclarationsBySelector: Record<string, any> = { "": {} }
|
|
408
|
+
|
|
409
|
+
for (const _propertyOrSelector in properties) {
|
|
410
|
+
const propertyOrSelector = _propertyOrSelector as keyof typeof properties
|
|
411
|
+
const value = properties[propertyOrSelector]
|
|
412
|
+
if (value === undefined || value === null) continue
|
|
413
|
+
const isNestedRule = isObject(value)
|
|
414
|
+
|
|
415
|
+
if (isNestedRule) {
|
|
416
|
+
// Type-casting to a key that only has nested rules
|
|
417
|
+
const selector = propertyOrSelector as `&:${CSS.SimplePseudos}`
|
|
418
|
+
const existingStyles = styleDeclarationsBySelector[selector] || {}
|
|
419
|
+
|
|
420
|
+
styleDeclarationsBySelector[selector] = {
|
|
421
|
+
...existingStyles,
|
|
422
|
+
...properties[selector],
|
|
423
|
+
}
|
|
424
|
+
} else {
|
|
425
|
+
const property = propertyOrSelector as keyof BaseCSSProperties
|
|
426
|
+
const value = (properties as any)[property]
|
|
427
|
+
|
|
428
|
+
styleDeclarationsBySelector[""][property] = value
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
for (const selector in styleDeclarationsBySelector) {
|
|
433
|
+
const declarations = styleDeclarationsBySelector[selector]
|
|
434
|
+
if (declarations && isObjectEmpty(declarations)) {
|
|
435
|
+
delete styleDeclarationsBySelector[selector]
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return styleDeclarationsBySelector as CSSPropertiesGroup
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function compileCSS(properties: CSSProperties): StyleElement["owned"] {
|
|
443
|
+
if (isObjectEmpty(properties)) {
|
|
444
|
+
return undefined
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const groupedCSSProperties = groupCSSProperties(properties)
|
|
448
|
+
|
|
449
|
+
const compiledStyleDeclarationsByGroup = mapValues(
|
|
450
|
+
groupedCSSProperties,
|
|
451
|
+
compileDeclarations,
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
const className = hashObject(compiledStyleDeclarationsByGroup)
|
|
455
|
+
const styleRules: StyleRule[] = entries(compiledStyleDeclarationsByGroup).map(
|
|
456
|
+
([group, styles]) => {
|
|
457
|
+
if (group === "") {
|
|
458
|
+
// Base group
|
|
459
|
+
return `.${className}{${styles}}`
|
|
460
|
+
} else if (group.startsWith("@")) {
|
|
461
|
+
// At rules
|
|
462
|
+
return `${group}{.${className}{${styles}}}`
|
|
463
|
+
} else {
|
|
464
|
+
// Nested Rules
|
|
465
|
+
const selector = group.replace(/&/g, `.${className}`)
|
|
466
|
+
return `${selector}{${styles}}`
|
|
467
|
+
}
|
|
468
|
+
},
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
return { styleRules, className }
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
export function style(
|
|
475
|
+
...styleElementsOrCSS: (CSSProperties | StyleElement)[]
|
|
476
|
+
): StyleElement {
|
|
477
|
+
let composed: StyleElement[] = []
|
|
478
|
+
let cssPropertyGroups: CSSProperties[] = []
|
|
479
|
+
|
|
480
|
+
for (const styleElementOrCSS of styleElementsOrCSS) {
|
|
481
|
+
if (isStyleElement(styleElementOrCSS)) {
|
|
482
|
+
composed.push(styleElementOrCSS)
|
|
483
|
+
} else {
|
|
484
|
+
cssPropertyGroups.push(styleElementOrCSS)
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const cssProperties = mergeCSSProperties(...cssPropertyGroups)
|
|
489
|
+
const owned = compileCSS(cssProperties)
|
|
490
|
+
|
|
491
|
+
const composedClassNames = composed.map((composed) => composed.className)
|
|
492
|
+
const className = clsx(...composedClassNames, owned?.className)
|
|
493
|
+
|
|
494
|
+
return {
|
|
495
|
+
__style__,
|
|
496
|
+
composed,
|
|
497
|
+
className,
|
|
498
|
+
owned,
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function styleElementToRules({ owned, composed }: StyleElement): StyleRule[] {
|
|
503
|
+
const styleRules = composed.flatMap(styleElementToRules)
|
|
504
|
+
if (owned) styleRules.push(...owned.styleRules)
|
|
505
|
+
return styleRules
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
export function useInjectGlobalStyles(
|
|
509
|
+
selector: string,
|
|
510
|
+
cssProperties: BaseCSSProperties & CSSVarDeclarations,
|
|
511
|
+
deps: DependencyList,
|
|
512
|
+
) {
|
|
513
|
+
const styleApi = useRequiredContext(PurseContext)
|
|
514
|
+
|
|
515
|
+
useLayoutEffect(() => {
|
|
516
|
+
const styleRule = `${selector}{${compileDeclarations(cssProperties)}}`
|
|
517
|
+
const destructor = styleApi.addGlobalStyle(styleRule)
|
|
518
|
+
|
|
519
|
+
return destructor
|
|
520
|
+
}, deps)
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
export function useStyles(
|
|
524
|
+
...styleElementsOrCSS: (CSSProperties | StyleElement)[]
|
|
525
|
+
): string {
|
|
526
|
+
const styleApi = useRequiredContext(PurseContext)
|
|
527
|
+
|
|
528
|
+
// let composed: StyleElement[] = []
|
|
529
|
+
// let cssPropertyGroups: CSSProperties[] = []
|
|
530
|
+
|
|
531
|
+
// for (const styleElementOrCSS of styleElementsOrCSS) {
|
|
532
|
+
// if (isStyleElement(styleElementOrCSS)) {
|
|
533
|
+
// composed.push(styleElementOrCSS)
|
|
534
|
+
// } else {
|
|
535
|
+
// cssPropertyGroups.push(styleElementOrCSS)
|
|
536
|
+
// }
|
|
537
|
+
// }
|
|
538
|
+
|
|
539
|
+
// const cssProperties = mergeCSSProperties(...cssPropertyGroups)
|
|
540
|
+
|
|
541
|
+
// Will want to memo this somehow at some point but its a little tricky
|
|
542
|
+
const styleElement = style(...styleElementsOrCSS)
|
|
543
|
+
|
|
544
|
+
useLayoutEffect(() => {
|
|
545
|
+
if (styleElement.className === "bpsHRa jwfJGj") {
|
|
546
|
+
console.log(styleElement)
|
|
547
|
+
}
|
|
548
|
+
const destructor = styleApi.addStyleElement(styleElement)
|
|
549
|
+
// Dep is class name because its hashed
|
|
550
|
+
return destructor
|
|
551
|
+
}, [styleElement.className])
|
|
552
|
+
|
|
553
|
+
// const memoedCSS = useMemoShallowEqual(cssProperties)
|
|
554
|
+
|
|
555
|
+
// const ownedStyleElement = useMemo(() => {
|
|
556
|
+
// return isObjectEmpty(memoedCSS) ? undefined : style(memoedCSS)
|
|
557
|
+
// }, [memoedCSS])
|
|
558
|
+
|
|
559
|
+
// useLayoutEffect(() => {
|
|
560
|
+
// for (const styleElement of styleElementsOrCSS.filter(isStyleElement)) {
|
|
561
|
+
// styleApi.addStyleElement(styleElement)
|
|
562
|
+
// }
|
|
563
|
+
|
|
564
|
+
// if (ownedStyleElement) {
|
|
565
|
+
// styleApi.addStyleElement(ownedStyleElement)
|
|
566
|
+
// }
|
|
567
|
+
// }, [ownedStyleElement])
|
|
568
|
+
|
|
569
|
+
// if (ownedStyleElement) {
|
|
570
|
+
// return composedClassName + " " + ownedStyleElement.className
|
|
571
|
+
// } else {
|
|
572
|
+
// return composedClassName
|
|
573
|
+
// }
|
|
574
|
+
|
|
575
|
+
return styleElement.className
|
|
576
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React, { useContext } from "react"
|
|
2
|
+
|
|
3
|
+
export function useRequiredContext<T>(
|
|
4
|
+
context: React.Context<T | undefined>,
|
|
5
|
+
): T {
|
|
6
|
+
const value = useContext(context)
|
|
7
|
+
if (value === undefined) {
|
|
8
|
+
throw new Error(`Expected value of ${context} to be defined`)
|
|
9
|
+
}
|
|
10
|
+
return value
|
|
11
|
+
}
|