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.

at master 176 lines 4.2 kB view raw
1import { queueFiberTree } from './render.ts'; 2import { getCurrentFiber, setCurrentScope } from './current.ts'; 3import { pushSideEffect } from './effects.ts'; 4import { parseCSS, scopeCSS, generateCSS } from './css.ts'; 5import { hashString } from './hash.ts'; 6 7export const Hook = { 8 State: 'state', 9 Effect: 'effect', 10 Ref: 'ref', 11 Style: 'style', 12}; 13 14const { 15 State, 16 Effect, 17 Ref, 18 Style, 19} = Hook; 20 21export function useFiber() { 22 const currentFiber = getCurrentFiber(); 23 if (!currentFiber) throw new Error('Hooks can only be called from within a function component.'); 24 return currentFiber; 25} 26 27function pushHook(fiber, hookType) { 28 if (!fiber.hooks) fiber.hooks = []; 29 const {hooks, pointer} = fiber; 30 fiber.pointer += 3; 31 32 const existing = hooks[pointer]; 33 34 if (!existing) hooks[pointer] = hookType; 35 else if (existing !== hookType) throw new Error('Hooks must be called in the same order between renders.'); 36 37 return pointer; 38} 39 40export function useState(initialValue) { 41 const fiber = useFiber(); 42 const pointer = pushHook(fiber, State); 43 44 let value = fiber.hooks[pointer + 1]; 45 const existing = fiber.hooks[pointer + 2]; 46 47 if (!existing) { 48 fiber.hooks[pointer + 1] = value = initialValue; 49 fiber.hooks[pointer + 2] = true; 50 } 51 52 const setValue = (val) => { 53 if (typeof val === 'function') { 54 const currentVal = fiber.hooks[pointer + 1]; 55 fiber.hooks[pointer + 1] = val(currentVal); 56 } 57 else{ 58 fiber.hooks[pointer + 1] = val; 59 } 60 61 queueFiberTree(fiber); 62 } 63 64 return [value, setValue]; 65} 66 67export function useEffect(callback, dependencies) { 68 const fiber = useFiber(); 69 const pointer = pushHook(fiber, Effect); 70 71 const oldDeps = fiber.hooks[pointer + 2]; 72 if (!areDependenciesEqual(oldDeps, dependencies)) { 73 fiber.hooks[pointer + 2] = dependencies; 74 const oldCleanup = fiber.hooks[pointer + 1]; 75 76 pushSideEffect(() => { 77 if (oldCleanup !== undefined) oldCleanup(); 78 const newCleanup = callback(); 79 fiber.hooks[pointer + 1] = newCleanup; 80 }); 81 } 82} 83 84export function useRef(initialValue) { 85 const fiber = useFiber(); 86 const pointer = pushHook(fiber, Ref); 87 88 let ref = fiber.hooks[pointer + 1]; 89 if(!ref) { 90 fiber.hooks[pointer + 1] = ref = {current: initialValue}; 91 } 92 93 return ref; 94} 95 96export function useStyle(text) { 97 const fiber = useFiber(); 98 const pointer = pushHook(fiber, Style); 99 100 const existingText = fiber.hooks[pointer + 1]; 101 let scope = fiber.hooks[pointer + 2]; 102 103 if (existingText !== text) { 104 scope = insertScopedStyles(text); 105 fiber.hooks[pointer + 1] = text; 106 fiber.hooks[pointer + 2] = scope; 107 } 108 109 setCurrentScope(scope); 110 return scope; 111} 112 113const KNOWN_STYLES = new Map(); 114function insertScopedStyles(text) { 115 let scope = KNOWN_STYLES.get(text); 116 if (scope !== undefined) return scope; 117 118 const hash = hashString(text); 119 // TODO: abs prevents a negative number (s--123) but is not ideal 120 scope = 's-' + Math.abs(hash).toString(36); 121 KNOWN_STYLES.set(text, scope); 122 123 const ast = parseCSS(text); 124 scopeCSS(ast, scope); 125 const rules = generateCSS(ast); 126 127 injectStyles(rules); 128 129 return scope; 130} 131 132function injectStyles(rules) { 133 const sheet = getStylesheet(); 134 for (const rule of rules) { 135 try { 136 sheet.insertRule(rule); 137 } 138 catch(error) { 139 console.error('An error occurred while inserting the following scoped rule:\n\n' + rule); 140 throw error; 141 } 142 } 143} 144 145let STYLESHEET; 146function getStylesheet() { 147 if (!STYLESHEET) { 148 STYLESHEET = new CSSStyleSheet(); 149 document.adoptedStyleSheets.push(STYLESHEET); 150 } 151 return STYLESHEET; 152} 153 154export function cleanUpEffects(fiber) { 155 const hooks = fiber.hooks; 156 if (!hooks) return; 157 158 for (let i = 0; i < hooks.length; i += 3) { 159 if (hooks[i] === Effect) { 160 const cleanup = hooks[i + 1]; 161 cleanup(); 162 } 163 } 164} 165 166function areDependenciesEqual(oldDeps, newDeps) { 167 if (oldDeps === undefined) return false; 168 if (newDeps === oldDeps) return true; 169 170 const length = oldDeps.length; 171 if (length !== newDeps.length) return false; 172 for (let i = 0; i < length; i++) { 173 if (newDeps[i] !== oldDeps[i]) return false; 174 } 175 return true; 176}