Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom root & shadow DOM support (#832) #1004

Open
wants to merge 1 commit into
base: canary
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/core/src/createStitches.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export const createStitches = (config) => {
theme,
themeMap,
utils,
root,
}

/** Internal stylesheet. */
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/utility/createMemo.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
const stringifyReplacer = (name, data) => (typeof data === 'function' ? { '()': Function.prototype.toString.call(data) } : data)
import { safeJsonStringify } from './safeJsonStringify.js'

const stringify = (value) => JSON.stringify(value, stringifyReplacer)
const stringifyReplacer = (name, data) => (typeof data === 'function' ? { '()': Function.prototype.toString.call(data) } : data)

/** @type {() => <T = any, A = any[], F = (T, ...A) => any>(value: T, apply: F, ...args: A) => ReturnType<F>} */
export const createMemo = () => {
const cache = Object.create(null)

return (value, apply, ...args) => {
const vjson = stringify(value)
const vjson = safeJsonStringify(value, stringifyReplacer)

return vjson in cache ? cache[vjson] : (cache[vjson] = apply(value, ...args))
}
Expand Down
63 changes: 63 additions & 0 deletions packages/core/src/utility/safeJsonStringify.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// https://github.com/debitoor/safe-json-stringify/blob/master/index.js
import { hasOwn } from './hasOwn.js'

function throwsMessage(err) {
return '[Throws: ' + (err ? err.message : '?') + ']'
}

function safeGetValueFromPropertyOnObject(obj, property) {
if (hasOwn(obj, property)) {
try {
return obj[property]
} catch (error) {
return throwsMessage(error)
}
}
return obj[property]
}

function ensureProperties(obj) {
const seen = [] // store references to objects we have seen before

function visit(obj) {
if (obj === null || typeof obj !== 'object') {
return obj
}

if (seen.indexOf(obj) !== -1) {
return '[Circular]'
}

seen.push(obj)

if (typeof obj.toJSON === 'function') {
try {
const result = visit(obj.toJSON())
seen.pop()
return result
} catch(err) {
return throwsMessage(err)
}
}

if (Array.isArray(obj)) {
const result = obj.map(visit)
seen.pop()
return result
}

const result = Object.keys(obj).reduce(function(acc, prop) {
// prevent faulty defined getter properties
acc[prop] = visit(safeGetValueFromPropertyOnObject(obj, prop))
return acc
}, {})
seen.pop()
return result
}

return visit(obj)
}

export const safeJsonStringify = (data, replacer, space) => {
return JSON.stringify(ensureProperties(data), replacer, space)
}
13 changes: 13 additions & 0 deletions packages/core/tests/issue-832.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { createStitches } from '../src/index.js'
import { createMemo } from '../src/utility/createMemo.js'

describe('Issue #832', () => {
test('Circular object bug reproduction', () => {
const circularObject = { }
circularObject.self = circularObject

expect(() => JSON.stringify(circularObject)).toThrow()
expect(() => createMemo(circularObject)).toNotThrow()
expect(() => createStitches(circularObject)).toNotThrow()
})
})
7 changes: 6 additions & 1 deletion packages/core/types/config.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ declare namespace ConfigType {
[K in keyof CSSUtil.CSSProperties]?: CSSUtil.CSSProperties[K] | V
}) : never
}

/** Root interface. */
export type Root<T = Document> = T extends DocumentOrShadowRoot ? T : DocumentOrShadowRoot
}

/** Default ThemeMap. */
Expand Down Expand Up @@ -197,14 +200,16 @@ export type CreateStitches = {
Media extends {} = {},
Theme extends {} = {},
ThemeMap extends {} = DefaultThemeMap,
Utils extends {} = {}
Utils extends {} = {},
Root extends DocumentOrShadowRoot = Document
>(
config?: {
prefix?: ConfigType.Prefix<Prefix>
media?: ConfigType.Media<Media>
theme?: ConfigType.Theme<Theme>
themeMap?: ConfigType.ThemeMap<ThemeMap>
utils?: ConfigType.Utils<Utils>
root?: ConfigType.Root<Root>
}
): Stitches<Prefix, Media, Theme, ThemeMap, Utils>
}
7 changes: 6 additions & 1 deletion packages/react/types/config.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ declare namespace ConfigType {
[K in keyof CSSUtil.CSSProperties]?: CSSUtil.CSSProperties[K] | V
}) : never
}

/** Root interface. */
export type Root<T = Document> = T extends DocumentOrShadowRoot ? T : DocumentOrShadowRoot
}

/** Default ThemeMap. */
Expand Down Expand Up @@ -197,14 +200,16 @@ export type CreateStitches = {
Media extends {} = {},
Theme extends {} = {},
ThemeMap extends {} = DefaultThemeMap,
Utils extends {} = {}
Utils extends {} = {},
Root extends DocumentOrShadowRoot = Document
>(
config?: {
prefix?: ConfigType.Prefix<Prefix>
media?: ConfigType.Media<Media>
theme?: ConfigType.Theme<Theme>
themeMap?: ConfigType.ThemeMap<ThemeMap>
utils?: ConfigType.Utils<Utils>
root?: ConfigType.Root<Root>
}
): Stitches<Prefix, Media, Theme, ThemeMap, Utils>
}