a small incremental UI library for the web
javascript web ui
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Scope CSS with useStyle

garrison 24afa94a efe21623

+78 -25
+3 -3
demos/todo/todo.tsx
··· 18 18 background-color: red; 19 19 border: 1px solid green; 20 20 } 21 - 22 - .foo { color: 'red'; } 23 - .bar { color: 'green'; } 21 + .container { 22 + background-color: orange; 23 + } 24 24 `); 25 25 26 26 return (
+12 -11
js/css.ts
··· 1 1 import { assert } from './utils'; 2 2 3 3 export function generateCSS(stylesheet) { 4 - const output = generateStylesheet(stylesheet); 5 - return output; 4 + return generateRules(stylesheet); 6 5 } 7 6 8 - function generateStylesheet(stylesheet) { 9 - const state = { 10 - output: '', 11 - }; 12 - 7 + function generateRules(stylesheet) { 8 + const rules = []; 13 9 for (const ruleset of stylesheet.rulesets) { 14 - generateRuleset(ruleset, state); 10 + const rule = generateRuleset(ruleset); 11 + rules.push(rule); 15 12 } 16 13 17 - return state.output; 14 + return rules; 18 15 } 19 16 20 - function generateRuleset(ruleset, state) { 17 + function generateRuleset(ruleset) { 18 + const state = {output: ''}; 19 + 21 20 let firstSelector = true; 22 21 for (const complexSelector of ruleset.selectorList.selectors) { 23 22 if (firstSelector) firstSelector = false; ··· 32 31 } 33 32 34 33 const body = ruleset.body.text.trim().replace(/\n\s*/, '\n '); 35 - state.output += (' {\n ' + body + '\n}\n\n'); 34 + state.output += (' {\n ' + body + '\n}'); 35 + 36 + return state.output; 36 37 } 37 38 38 39 export function scopeCSS(stylesheet, scope) {
+10
js/current.ts
··· 7 7 export function setCurrentFiber(fiber) { 8 8 CURRENT_FIBER = fiber; 9 9 } 10 + 11 + let CURRENT_SCOPE = null; 12 + 13 + export function getCurrentScope() { 14 + return CURRENT_SCOPE; 15 + } 16 + 17 + export function setCurrentScope(scope) { 18 + CURRENT_SCOPE = scope; 19 + }
+43 -4
js/hooks.ts
··· 1 1 import { queueFiberTree } from './render.ts'; 2 - import { getCurrentFiber } from './current.ts'; 2 + import { getCurrentFiber, setCurrentScope } from './current.ts'; 3 3 import { pushSideEffect } from './effects.ts'; 4 4 import { parseCSS, scopeCSS, generateCSS } from './css.ts'; 5 5 import { hashString } from './hash.ts'; ··· 7 7 export const Hook = { 8 8 State: 'state', 9 9 Effect: 'effect', 10 + Style: 'style', 10 11 }; 11 12 12 13 const { 13 14 State, 14 15 Effect, 16 + Style, 15 17 } = Hook; 16 18 17 19 export function useFiber() { ··· 78 80 } 79 81 80 82 export function useStyle(text) { 83 + const fiber = useFiber(); 84 + const pointer = pushHook(fiber, Style); 85 + 86 + const existingText = fiber.hooks[pointer + 1]; 87 + let scope = fiber.hooks[pointer + 2]; 88 + 89 + if (existingText !== text) { 90 + scope = insertScopedStyles(text); 91 + setCurrentScope(scope); 92 + 93 + fiber.hooks[pointer + 1] = text; 94 + fiber.hooks[pointer + 2] = scope; 95 + } 96 + 97 + return scope; 98 + } 99 + 100 + const KNOWN_STYLES = new Set(); 101 + function insertScopedStyles(text) { 102 + if (KNOWN_STYLES.has(text)) return; 103 + KNOWN_STYLES.add(text); 104 + 81 105 const hash = hashString(text); 82 - const scope = 's-' + hash.toString(36); 106 + // TODO: abs prevents a negative number (s--123) but is not ideal 107 + const scope = 's-' + Math.abs(hash).toString(36); 83 108 84 109 const ast = parseCSS(text); 85 110 scopeCSS(ast, scope); 86 - const output = generateCSS(ast); 111 + const rules = generateCSS(ast); 87 112 88 - console.log(output); 113 + injectStyles(rules); 89 114 90 115 return scope; 116 + } 117 + 118 + function injectStyles(rules) { 119 + const sheet = getStylesheet(); 120 + for (const rule of rules) sheet.insertRule(rule); 121 + } 122 + 123 + let STYLESHEET; 124 + function getStylesheet() { 125 + if (!STYLESHEET) { 126 + STYLESHEET = new CSSStyleSheet(); 127 + document.adoptedStyleSheets.push(STYLESHEET); 128 + } 129 + return STYLESHEET; 91 130 } 92 131 93 132 export function cleanUpEffects(fiber) {
+8 -6
js/noir.ts
··· 1 1 import { FiberTag, makeFiber, reconcileChildren } from './fiber.ts'; 2 2 import { startRenderLoop, renderFiberTree } from './render.ts'; 3 - import { setCurrentFiber } from './current.ts'; 3 + import { setCurrentFiber, getCurrentScope } from './current.ts'; 4 4 5 5 const { 6 6 RootFiber, ··· 8 8 HostFiber, 9 9 } = FiberTag; 10 10 11 - export function createElement(type, props, ...children) { 11 + export function createElement(type, inputProps, ...children) { 12 + const scope = getCurrentScope(); 13 + 14 + const props = {...inputProps, children}; 15 + if (scope) props[scope] = true; 16 + 12 17 return { 13 18 type: type, 14 - props: { 15 - ...props, 16 - children, 17 - }, 19 + props: props, 18 20 }; 19 21 } 20 22
+2 -1
js/render.ts
··· 1 1 import { FiberTag, cloneFiber, reconcileChildren } from './fiber.ts'; 2 2 import { commitTree } from './commit.ts'; 3 - import { setCurrentFiber } from './current.ts'; 3 + import { setCurrentFiber, setCurrentScope } from './current.ts'; 4 4 import { commitSideEffects, resetSideEffectQueue } from './effects.ts'; 5 5 import { assert } from './utils.ts'; 6 6 ··· 118 118 fiber.pointer = 0; 119 119 const elements = fiber.type(fiber.props); 120 120 setCurrentFiber(null); 121 + setCurrentScope(null); 121 122 122 123 const firstNewChild = reconcileChildren(fiber, elements); 123 124 fiber.child = firstNewChild;