tosijs 1.0.8 → 1.0.10
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/README.md +22 -10
- package/dist/blueprint-loader.d.ts +2 -2
- package/dist/component.d.ts +5 -4
- package/dist/elements-types.d.ts +129 -0
- package/dist/elements.d.ts +1 -126
- package/dist/index.d.ts +3 -2
- package/dist/index.js +8 -8
- package/dist/index.js.map +19 -18
- package/dist/list-binding.d.ts +11 -19
- package/dist/main.js +8 -8
- package/dist/main.js.map +19 -18
- package/dist/make-component.d.ts +2 -1
- package/dist/metadata.d.ts +2 -3
- package/dist/module.js +8 -8
- package/dist/module.js.map +19 -18
- package/dist/version.d.ts +1 -1
- package/dist/xin-types.d.ts +27 -4
- package/package.json +1 -1
package/dist/module.js.map
CHANGED
|
@@ -1,33 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/settings.ts", "../src/deep-clone.ts", "../src/metadata.ts", "../src/path-listener.ts", "../src/make-error.ts", "../src/by-path.ts", "../src/
|
|
3
|
+
"sources": ["../src/settings.ts", "../src/deep-clone.ts", "../src/metadata.ts", "../src/path-listener.ts", "../src/make-error.ts", "../src/by-path.ts", "../src/dom.ts", "../src/throttle.ts", "../src/list-binding.ts", "../src/bindings.ts", "../src/string-case.ts", "../src/more-math.ts", "../src/get-css-var.ts", "../src/color.ts", "../src/css.ts", "../src/elements-types.ts", "../src/elements.ts", "../src/xin.ts", "../src/bind.ts", "../src/component.ts", "../src/hot-reload.ts", "../src/version.ts", "../src/xin-proxy.ts", "../src/make-component.ts", "../src/blueprint-loader.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"export const settings = {\n debug: false,\n perf: false,\n}\n",
|
|
6
6
|
"import { XinObject, XinArray, AnyFunction } from './xin-types'\n\ntype Scalar = string | boolean | number | AnyFunction\ntype Cloneable = Scalar | XinObject | XinArray\n\nexport function deepClone(obj: Cloneable): Cloneable | Cloneable[] {\n if (obj == null || typeof obj !== 'object') {\n return obj\n }\n if (obj instanceof Set) {\n return new Set(obj)\n } else if (Array.isArray(obj)) {\n return obj.map(deepClone)\n }\n const clone: XinObject = {}\n for (const key in obj) {\n const val = obj[key]\n if (obj != null && typeof obj === 'object') {\n clone[key] = deepClone(val) as XinObject\n } else {\n clone[key] = val\n }\n }\n return clone\n}\n",
|
|
7
|
-
"/*#\n# 1.3 metadata\n\n## `
|
|
7
|
+
"/*#\n# 1.3 metadata\n\n## `xinValue(x: any): any`\n\n`xinValue` is helpful when you want to strip the `xin` or `boxed` proxy off of a\nvalue. `xinValue` passes through normal values, so it's safe to use on anything.\n\n```\nimport { boxed } from 'xinjs'\n\nconst foo = { bar: 'hello', baz: 17 }\nboxed.foo = foo\n\nboxed.foo.bar === foo.bar // false, boxed.foo.bar is a String\nboxed.foo === foo // false, boxed.foo is a Proxy\nboxed.foo.baz === 17 // false, boxed.foo.baz is a Number\nxinValue(boxed.foo.bar) === 'hello' // true\nboxed.foo.xinValue === foo // true\nboxed.foo.baz.xinValue = 17 // true\nxinValue(boxed.foo) === xinValue(foo) // true\nfoo.xinValue // undefined! foo isn't a proxy\n```\n\n## `xinPath(x: any): string | undefined`\n\n`xinPath` will get you the path of a `xin` or `boxed` proxy. `xinPath` will be\nundefined for anything that's isn't a `xin` or `boxed` proxy, so it can also\nbe used to tell if a value is a (`xin` or `boxed`) proxy.\n*/\nimport {\n XinObject,\n XinProps,\n XinBinding,\n XinEventHandler,\n Unboxed,\n} from './xin-types'\nimport { deepClone } from './deep-clone'\n\nexport const BOUND_CLASS = '-xin-data'\nexport const BOUND_SELECTOR = `.${BOUND_CLASS}`\nexport const EVENT_CLASS = '-xin-event'\nexport const EVENT_SELECTOR = `.${EVENT_CLASS}`\n\nexport const XIN_PATH = 'xinPath'\nexport const XIN_VALUE = 'xinValue'\nexport const XIN_OBSERVE = 'xinObserve'\nexport const XIN_BIND = 'xinBind'\nexport const XIN_ON = 'xinOn'\n\nexport const LIST_BINDING_REF = Symbol('list-binding')\nexport const LIST_INSTANCE_REF = Symbol('list-instance')\n\nexport const xinPath = (x: any): string | undefined => {\n return (x && x[XIN_PATH]) || undefined\n}\n\nexport function xinValue<T>(x: T): Unboxed<T> {\n return (\n typeof x === 'object' && x !== null\n ? (x as unknown as XinProps)[XIN_VALUE] || x\n : x\n ) as Unboxed<T>\n}\n\nexport interface DataBinding<T extends Element = Element> {\n path: string\n binding: XinBinding<T>\n options?: XinObject\n}\n\nexport type DataBindings = DataBinding[]\n\nexport interface XinEventBindings {\n [eventType: string]: Set<XinEventHandler>\n}\n\nexport const elementToHandlers: WeakMap<Element, XinEventBindings> =\n new WeakMap()\nexport const elementToBindings: WeakMap<Element, DataBindings> = new WeakMap()\n\ninterface ElementMetadata {\n eventBindings?: XinEventBindings\n dataBindings?: DataBindings\n}\n\nexport const getElementBindings = (element: Element): ElementMetadata => {\n return {\n eventBindings: elementToHandlers.get(element),\n dataBindings: elementToBindings.get(element),\n }\n}\n\nexport const cloneWithBindings = (element: Node): Node => {\n const cloned = element.cloneNode()\n if (cloned instanceof Element) {\n const dataBindings = elementToBindings.get(element as Element)\n const eventHandlers = elementToHandlers.get(element as Element)\n if (dataBindings != null) {\n // @ts-expect-error-error\n elementToBindings.set(cloned, deepClone(dataBindings))\n }\n if (eventHandlers != null) {\n // @ts-expect-error-error\n elementToHandlers.set(cloned, deepClone(eventHandlers))\n }\n }\n for (const node of Array.from(\n element instanceof HTMLTemplateElement\n ? element.content.childNodes\n : element.childNodes\n )) {\n if (node instanceof Element || node instanceof DocumentFragment) {\n cloned.appendChild(cloneWithBindings(node))\n } else {\n cloned.appendChild(node.cloneNode())\n }\n }\n return cloned\n}\n",
|
|
8
8
|
"/*#\n# 1.2 path-listener\n\n`path-listener` implements the `tosijs` observer model. Although these events\nare exported from `tosijs` they shouldn't need to be used very often. Mostly\nthey're used to manage state.\n\n## `touch(path: string)`\n\nThis is used to inform `xin` that a value at a path has changed. Remember that\nxin simply wraps an object, and if you change the object directly, `xin` won't\nnecessarily know about it.\n\nThe two most common uses for `touch()` are:\n\n1. You want to make lots of changes to a large data structure, possibly\n over a period of time (e.g. update hundreds of thousands of values\n in a table that involve service calls or heavy computation) and don't\n want to thrash the UI so you just change the object directly.\n2. You want to change the content of an object but need a something that\n is bound to the \"outer\" object to be refreshed.\n\n## `observe()` and `unobserve()`\n\n const listener = observe(\n path: string | RegExp | (path: string) => boolean,\n (changedPath: string) => {\n ...\n }\n )\n\n // and later, when you're done\n unobserve(listener);\n\n`observe(…)` lets you call a function whenever a specified path changes. You'll\nbe passed the path that changed and you can do whatever you like. It returns\na reference to the listener to allow you to dispose of it later.\n\n`unobserve(listener)` removes the listener.\n\n> This is how binding works. When you bind a path to an interface element, an\n> observer is created that knows when to update the interface element. (If the\n> binding is \"two-way\" (i.e. provides a `fromDOM` callback) then an `input` or\n> `change` event that hits that element will update the value at the bound\n> path.\n\n## `async updates()`\n\nYou can `await updates()` or use `updates().then(…)` to execute code\nafter any changes have been rendered to the DOM. Typically, you shouldn't\nhave to mess with this, but sometimes—for example—you might need to know\nhow large a rendered UI element is to adjust something else.\n\nIt's also used a lot in unit tests. After you perform some logic, does\nit appear correctly in the UI?\n*/\n\nimport {\n PathTestFunction,\n ObserverCallbackFunction,\n AnyFunction,\n} from './xin-types'\nimport { xinPath } from './metadata'\nimport { settings } from './settings'\n\nexport const observerShouldBeRemoved = Symbol('observer should be removed')\nexport const listeners: Listener[] = [] // { path_string_or_test, callback }\nconst touchedPaths: string[] = []\nlet updateTriggered: number | boolean = false\nlet updatePromise: Promise<undefined>\nlet resolveUpdate: AnyFunction\n\nexport class Listener {\n description: string\n test: PathTestFunction\n callback: ObserverCallbackFunction\n\n constructor(\n test: string | RegExp | PathTestFunction,\n callback: string | ObserverCallbackFunction\n ) {\n const callbackDescription =\n typeof callback === 'string'\n ? `\"${callback}\"`\n : `function ${callback.name}`\n let testDescription\n if (typeof test === 'string') {\n this.test = (t) =>\n typeof t === 'string' &&\n t !== '' &&\n (test.startsWith(t) || t.startsWith(test))\n testDescription = `test = \"${test}\"`\n } else if (test instanceof RegExp) {\n this.test = test.test.bind(test)\n testDescription = `test = \"${test.toString()}\"`\n } else if (test instanceof Function) {\n this.test = test\n testDescription = `test = function ${test.name}`\n } else {\n throw new Error(\n 'expect listener test to be a string, RegExp, or test function'\n )\n }\n this.description = `${testDescription}, ${callbackDescription}`\n if (typeof callback === 'function') {\n this.callback = callback\n } else {\n throw new Error('expect callback to be a path or function')\n }\n listeners.push(this)\n }\n}\n\nexport const updates = async (): Promise<void> => {\n if (updatePromise === undefined) {\n return\n }\n await updatePromise\n}\n\nconst update = (): void => {\n if (settings.perf) {\n console.time('xin async update')\n }\n const paths = Array.from(touchedPaths)\n\n for (const path of paths) {\n listeners\n .filter((listener) => {\n let heard\n try {\n heard = listener.test(path)\n } catch (e) {\n throw new Error(\n `Listener ${listener.description} threw \"${\n e as string\n }\" at \"${path}\"`\n )\n }\n if (heard === observerShouldBeRemoved) {\n unobserve(listener)\n return false\n }\n return heard as boolean\n })\n .forEach((listener) => {\n let outcome\n try {\n outcome = listener.callback(path)\n } catch (e) {\n console.error(\n `Listener ${listener.description} threw \"${\n e as string\n }\" handling \"${path}\"`\n )\n }\n if (outcome === observerShouldBeRemoved) {\n unobserve(listener)\n }\n })\n }\n\n touchedPaths.splice(0)\n updateTriggered = false\n if (typeof resolveUpdate === 'function') {\n resolveUpdate()\n }\n if (settings.perf) {\n console.timeEnd('xin async update')\n }\n}\n\nexport const touch = (touchable: any): void => {\n const path = typeof touchable === 'string' ? touchable : xinPath(touchable)\n\n if (path === undefined) {\n console.error('touch was called on an invalid target', touchable)\n throw new Error('touch was called on an invalid target')\n }\n\n if (updateTriggered === false) {\n updatePromise = new Promise((resolve) => {\n resolveUpdate = resolve\n })\n updateTriggered = setTimeout(update) as unknown as number\n }\n\n if (\n touchedPaths.find((touchedPath) => path.startsWith(touchedPath)) == null\n ) {\n touchedPaths.push(path)\n }\n}\n\nexport const observe = (\n test: string | RegExp | PathTestFunction,\n callback: ObserverCallbackFunction\n): Listener => {\n return new Listener(test, callback)\n}\n\nexport const unobserve = (listener: Listener): void => {\n const index = listeners.indexOf(listener)\n if (index > -1) {\n listeners.splice(index, 1)\n } else {\n throw new Error('unobserve failed, listener not found')\n }\n}\n",
|
|
9
9
|
"const stringify = (x: any): string => {\n try {\n return JSON.stringify(x)\n } catch (_) {\n return '{has circular references}'\n }\n}\n\nexport const makeError = (...messages: any[]): Error =>\n new Error(messages.map(stringify).join(' '))\n",
|
|
10
10
|
"// unique tokens passed to set by path to delete or create properties\n\nimport { XinObject, XinArray, XinScalar } from './xin-types'\nimport { makeError } from './make-error'\n\nconst now36 = (): string =>\n new Date(parseInt('1000000000', 36) + Date.now())\n .valueOf()\n .toString(36)\n .slice(1)\nlet _seq = 0\nconst seq = (): string =>\n (parseInt('10000', 36) + ++_seq).toString(36).slice(-5)\nconst id = (): string => now36() + seq()\n\nconst _delete_ = Symbol('delete')\nconst _newObject_ = Symbol('new-object')\nconst _auto_ = Symbol('automatic-index')\n\ntype Part = string | string[]\ntype PartArray = Part[]\n\nfunction pathParts(path: string | PartArray): PartArray {\n if (path === '') {\n return []\n }\n\n if (Array.isArray(path)) {\n return path\n } else {\n const parts: PartArray = []\n while (path.length > 0) {\n let index = path.search(/\\[[^\\]]+\\]/)\n if (index === -1) {\n parts.push(path.split('.'))\n break\n } else {\n const part = path.slice(0, index)\n path = path.slice(index)\n if (part !== '') {\n parts.push(part.split('.'))\n }\n index = path.indexOf(']') + 1\n parts.push(path.slice(1, index - 1))\n // handle paths dereferencing array element like foo[0].id\n if (path.slice(index, index + 1) === '.') {\n index += 1\n }\n path = path.slice(index)\n }\n }\n return parts\n }\n}\n\nconst idPathMaps = new WeakMap()\n\ninterface IdPathMap {\n [key: string]: number\n}\n\nfunction buildIdPathValueMap(array: XinObject[], idPath: string): IdPathMap {\n if (idPathMaps.get(array) === undefined) {\n idPathMaps.set(array, {})\n }\n if (idPathMaps.get(array)[idPath] === undefined) {\n idPathMaps.get(array)[idPath] = {}\n }\n const map = idPathMaps.get(array)[idPath]\n\n if (idPath === '_auto_') {\n array.forEach((item, idx) => {\n if (item[_auto_] === undefined) item[_auto_] = id()\n map[(item[_auto_] as string) + ''] = idx\n })\n } else {\n array.forEach((item, idx) => {\n map[(getByPath(item, idPath) as string) + ''] = idx\n })\n }\n return map\n}\n\nfunction getIdPathMap(array: XinObject[], idPath: string): IdPathMap {\n if (\n idPathMaps.get(array) === undefined ||\n idPathMaps.get(array)[idPath] === undefined\n ) {\n return buildIdPathValueMap(array, idPath)\n } else {\n return idPathMaps.get(array)[idPath]\n }\n}\n\nfunction keyToIndex(array: XinObject[], idPath: string, idValue: any): number {\n idValue = (idValue as string) + ''\n let idx = getIdPathMap(array, idPath)[idValue]\n if (\n idx === undefined ||\n (getByPath(array[idx], idPath) as string) + '' !== idValue\n ) {\n idx = buildIdPathValueMap(array, idPath)[idValue]\n }\n return idx\n}\n\nfunction byKey(obj: XinObject, key: string, valueToInsert?: any): any {\n if (obj[key] === undefined && valueToInsert !== undefined) {\n obj[key] = valueToInsert\n }\n return obj[key]\n}\n\nfunction byIdPath(\n array: any[],\n idPath: string,\n idValue: string,\n valueToInsert?: any\n): any {\n let idx = idPath !== '' ? keyToIndex(array, idPath, idValue) : idValue\n if (valueToInsert === _delete_) {\n array.splice(idx as number, 1)\n idPathMaps.delete(array)\n return Symbol('deleted')\n } else if (valueToInsert === _newObject_) {\n if (idPath === '' && array[idx as number] === undefined) {\n array[idx as number] = {}\n }\n } else if (valueToInsert !== undefined) {\n if (idx !== undefined) {\n array[idx as number] = valueToInsert\n } else if (\n idPath !== '' &&\n (getByPath(valueToInsert, idPath) as string) + '' === idValue + ''\n ) {\n array.push(valueToInsert)\n idx = array.length - 1\n } else {\n throw new Error(`byIdPath insert failed at [${idPath}=${idValue}]`)\n }\n }\n return array[idx as number]\n}\n\nfunction expectArray(obj: any): void {\n if (!Array.isArray(obj)) {\n throw makeError('setByPath failed: expected array, found', obj)\n }\n}\n\nfunction expectObject(obj: any): void {\n if (obj == null || !(obj instanceof Object)) {\n throw makeError('setByPath failed: expected Object, found', obj)\n }\n}\n\nfunction getByPath(obj: XinObject | XinArray, path: string): any {\n const parts = pathParts(path)\n let found: XinObject | XinArray | XinScalar = obj\n let i, iMax, j, jMax\n for (i = 0, iMax = parts.length; found !== undefined && i < iMax; i++) {\n const part = parts[i]\n if (Array.isArray(part)) {\n for (j = 0, jMax = part.length; found !== undefined && j < jMax; j++) {\n const key = part[j]\n found = (found as XinObject)[key]\n }\n } else {\n if ((found as XinArray).length === 0) {\n found = (found as XinArray)[Number(part.slice(1))]\n if (part[0] !== '=') {\n return undefined\n }\n } else if (part.includes('=')) {\n const [idPath, ...tail] = part.split('=')\n found = byIdPath(found as any[], idPath, tail.join('='))\n } else {\n j = parseInt(part, 10)\n found = (found as XinArray)[j]\n }\n }\n }\n return found\n}\n\nfunction setByPath(\n orig: XinObject | XinArray,\n path: string,\n val: any\n): boolean {\n let obj: XinObject | XinArray | XinScalar = orig\n if (path === '')\n throw new Error('setByPath cannot be used to set the root object')\n const parts = pathParts(path)\n\n while (obj != null && parts.length > 0) {\n const part = parts.shift()\n if (typeof part === 'string') {\n const equalsOffset = part.indexOf('=')\n if (equalsOffset > -1) {\n if (equalsOffset === 0) {\n expectObject(obj)\n } else {\n expectArray(obj)\n }\n const idPath = part.slice(0, equalsOffset)\n const idValue = part.slice(equalsOffset + 1)\n obj = byIdPath(\n obj as any[],\n idPath,\n idValue,\n parts.length > 0 ? _newObject_ : val\n )\n if (parts.length === 0) {\n return true\n }\n } else {\n expectArray(obj)\n const idx = parseInt(part, 10)\n if (parts.length > 0) {\n obj = (obj as XinArray)[idx]\n } else {\n if (val !== _delete_) {\n if ((obj as XinArray)[idx] === val) {\n return false\n }\n ;(obj as XinArray)[idx] = val\n } else {\n ;(obj as XinArray).splice(idx, 1)\n }\n return true\n }\n }\n } else if (Array.isArray(part) && part.length > 0) {\n expectObject(obj)\n while (part.length > 0) {\n const key = part.shift() as string\n if (part.length > 0 || parts.length > 0) {\n // if we're at the end of part.length then we need to insert an array\n obj = byKey(obj as XinObject, key, part.length > 0 ? {} : [])\n } else {\n if (val !== _delete_) {\n if ((obj as XinObject)[key] === val) {\n return false\n }\n ;(obj as XinObject)[key] = val\n } else {\n if (!Object.prototype.hasOwnProperty.call(obj, key)) {\n return false\n }\n delete (obj as XinObject)[key]\n }\n return true\n }\n }\n } else {\n throw new Error(`setByPath failed, bad path ${path}`)\n }\n }\n\n throw new Error(`setByPath(${orig}, ${path}, ${val}) failed`)\n}\n\nfunction deleteByPath(orig: XinObject, path: string): void {\n if (getByPath(orig, path) !== null) {\n setByPath(orig, path, _delete_)\n }\n}\n\nexport { getByPath, setByPath, deleteByPath, pathParts }\n",
|
|
11
|
-
"/*#\n# 1. tosi\n\n`tosi()` assigns an object passed to it to a global state object,\nand returns an observer proxy (`BoxedProxy`) wrapped around the global state object.\n\nBoxedProxy wraps any object you pull out of it in an observer\nptoxy. It boxes booleans, numbers, and strings in objects and wraps\nthose objects in an observer proxy.\n\nIn rough terms:\n\n```\nconst state = {}\nconst boxed = new Proxy(state, ...)\ntosi = (obj<T>): BoxedProxy<T> => {\n Object.assign(state, obj)\n return state\n}\n```\n\nThis allows the following pattern, which gives Typescript a lot of useful\ninformation for free, allowing autocomplete, etc. with a minimumn of\nboilerplate.\n\n```\nimport { tosi, elements, bind } from 'tosijs'\n\nconst { prefs } = tosi({\n prefs: {\n theme: 'system',\n highcontrast: false\n }\n})\n\n// this example continues…\n```\n\nSo `{ prefs: ... }` is assigned to the state object, and now `prefs`\nholds the same stuff except it's wrapped in a `BoxedProxy`.\n\nThe `BoxedProxy` behaves just like the original object, except\nthat it:\n\n- knows where it came from, so `prefs.xinPath === 'prefs'`\n- will automatically trigger updates if its properties are changed through it\n- can return the underlying value:\n `prefs.xinValue === prefs.valueOf() === the prefs property of the object passed to `tosi()`\n- it will wrap its non-object properties in objects and wrap those objects\n in a BoxedProxy, so `prefs.theme.xinPath === 'prefs.theme'`\n\n```\nprefs.theme instaceof String // true\nprefs.theme.valueOf() === 'system' // true\nprefs.theme.xinValue === 'system' // true\nprefs.theme.xinPath === 'prefs.theme' // true\n```\n\nThe `BoxedProxy` observes changes made through it and updates bound elements\naccordingly:\n\n```\nbind(document.body, prefs.theme, {\n toDOM(element, value) {\n element.classList.toggle('dark-mode', value === 'dark')\n }\n}\n\nconst { select, option } = elements\n\ndocument.body.append(\n select(\n { bindValue: prefs.theme },\n option('system'),\n option('dark'),\n option('light')\n )\n)\n```\n\nSetting up the binding to `document.body` will set the `class`\nappropriately, and modifying `prefs.theme` will update the\nbound element automatically.\n\n```\nproxy.theme.xinSet('dark')\n```\n\n> In javascript you can juset write `proxy.theme = 'dark'`.\n\nThis, in a nutshell, explains exactly what `tosijs` is designed to do.\n\n`tosi` tracks state and allows you to bind data to your user interface\ndirectly and with almost no code, with clean separation between business\nlogic and presentation.\n\nThe [`elements` proxy](/?elements.ts) lets you build HTML elements with\ndata and event bindings more compactly and efficiently than you can using\nJSX/TSX, and it deals in regular `HTMLElement`—no virtual DOM, tranpilation\nmagic, spooky action at a distance, or any similar nonsense.\n\nIf you need to do complex bindings, the `bind` method lets you directly\nlink data to the DOM and automatically sets up observers for you.\n\n`Component` lets you create reusable web-components.\n\n`css` lets you write CSS economically and makes it easy to leverage the power\nof CSS variables, while `Color` allows you to do color math quickly and\neasily until similar functionality is added to CSS.\n\n> In Finnish, *tosi* means true or really.\n>\n> As conceived, `tosi()` is an observer `Proxy` wrapped around your application's\n> state. It's the **single source of truth for application state**.\n>\n> Note that the interactive examples on the xinjs.net website only support Javascript.\n> If you want to play with `xinjs` using Typescript, try the [sandbox example](https://codesandbox.io/s/xintro-mh4rbj?file=/src/index.ts)\n\n## xin\n\n`xin` is a path-based implementation of the **observer** or **pub/sub**\npattern designed to be very simple and straightforward to use, leverage\nTypescript type-checking and autocompletion, and let you get more done with\nless code and no weird build magic (such as special decorators or \"execution zones\").\n\n## In a nutshell\n\n`xin` is a single object wrapped with an **observer** proxy.\n\n- when you assign an object (or array) to `xin` as a property, you're\n just assigning a property to the object. When you pull it out, you\n get a **proxy** of the underlying value, but the original value is\n still there, untouched.\n ```\n const foo = { bar: 'baz' }\n xin.foo = foo\n xin.foo.bar === foo.bar\n xin.foo.bar === 'baz'\n xin.foo !== foo // xin.foo is a proxy\n xin.foo.xinValue === foo // foo is still there!\n ```\n- if you change a property of something already in `xin` then this\n change will be `observed` and anything *listening* for changes to\n the value at that **path** will be notified.\n- xinjs's `bind` method leverages the proxy to keep the UI synced\n with application state.\n\nIn the following example there's a `<div>` and an `<input>`. The\ntextContent of the former and the value of the latter are bound to\nthe **path** `xinExample.string`.\n\n`xin` is exposed as a global in the console, so you can go into\nconsole and look at `xin.xinExample` and (for example) directly\nchange it via the console.\n\nYou can also turn on Chrome's rendering tools to see how\nefficiently the DOM is updated. And also note that typing into\nthe input field doesn't lose any state (so your text selection\nand insertion point are stable.\n\n```js\nimport { xin, elements } from 'tosijs'\n\nxin.xinExample = {\n string: 'hello, xin'\n}\n\nconst { label, input, div, span } = elements\n\npreview.append(\n div(\n {\n style: {\n display: 'flex',\n flexDirection: 'column',\n gap: 10,\n padding: 10\n }\n },\n div({bindText: 'xinExample.string'}),\n label(\n span('Edit this'),\n input({ bindValue: 'xinExample.string'})\n )\n )\n)\n```\n\n- a **data-path** typically resembles the way you'd reference a value inside\n a javascript object…\n- `xin` also supports **id-paths** which allow you to create stable references\n to elements in arrays using a (hopefully unique) identifier. E.g. instead\n of referring to an item in an array as `xin.foo.array[3]`, assuming it had\n an `id` of `abcd1234` you could write `xin.foo.array[id=abcd1234]`. This makes\n handling large arrays much more efficient.\n- when you pull an object-value out of `xin` it comes wrapped in the xin\n observer proxy, so it continues to support id-paths and so on.\n\n### A Calculator\n\n```js\nimport { xin, elements, touch } from 'tosijs'\n\n// here's a vanilla javascript calculator\nconst calculator = {\n x: 4,\n y: 3,\n op: '+',\n result: 0,\n evaluate() {\n this.result = eval(`${this.x} ${this.op} ${this.y}`)\n }\n}\n\ncalculator.evaluate()\n\nxin.calculatorExample = calculator\n\n// now we'll give it a user interface…\nconst { input, select, option, div, span } = elements\n\npreview.append(\n div(\n {\n onChange() {\n calculator.evaluate()\n touch('calculatorExample.result')\n }\n },\n input({bindValue: 'calculatorExample.x', placeholder: 'x'}),\n select(\n {\n bindValue: 'calculatorExample.op'\n },\n option('+'),\n option('-'),\n option({value: '*'}, '×'),\n option({value: '/'}, '÷'),\n ),\n input({bindValue: 'calculatorExample.y', placeholder: 'y'}),\n span('='),\n span({bindText: 'calculatorExample.result' })\n )\n)\n```\n\nImportant points:\n\n- `xin` points at a single object. It's a [Singleton](https://en.wikipedia.org/wiki/Singleton_pattern).\n- `boxed` points to the **same** object\n- `xin` and `boxed` are observers. They watch the object they point to and\n track changes made by accessing the underlying data through them.\n- because `calculator.evaluate()` changes `calculator.result`\n directly, `touch()` is needed to tell `xin` that the change occurred.\n See [path-listener](/?path-listener.ts) for more documentation on `touch()`.\n- `xin` is more than just an object!\n - `xin['foo.bar']` gets you the same thing `xin.foo.bar` gets you.\n - `xin.foo.bar = 17` tells `xin` that `foo.bar` changed, which triggers DOM updates.\n\n> If you're reading this on xinjs.net then this the demo app you're looking\n> works a bit like this and `xin` (and `boxed`) are exposed as globals so\n> you can play with them in the **debug console**.\n>\n> Try going into the console and typing `xin.app.title` to see what you get,\n> and then try `xin.app.title = 'foobar' and see what happens to the heading.\n>\n> Also try `xin.prefs.theme` and try `app.prefs.theme = 'dark'` etc.\n\nOnce an object is assigned to `xin`, changing it within `xin` is simple.\nTry this in the console:\n\n```\nxin.calculatorExample.x = 17\n```\n\nThis will update the `x` field in the calculator, but not the result.\nThe result is updated when a `change` event is triggered.\n\nIf you wanted the calculator to update based on *any* change to its\ninternal state, you could instead write:\n\n```\nobserve('calculatorExample', () => {\n calculator.evaluate()\n touch('calculatorExample.result')\n})\n```\n\nNow the `onChange` handler isn't necessary at all. `observe`\nis documented in [path-listener](/?path-listener.ts).\n\n```js\nimport { observe, xin, elements } from 'tosijs'\n\nconst { h3, div } = elements\n\nconst history = div('This shows changes made to the preceding example')\n\npreview.append(\n h3('Changes to the calculatorExample'),\n history\n)\n\nobserve(/calculatorExample\\./, path => {\n const value = xin[path]\n history.insertBefore(div(`${path} = ${value}`), history.firstChild)\n})\n```\n\nNow, if you sneakily make changes behind `xin`'s back, e.g. by modifying the values\ndirectly, e.g.\n\n```\nconst emails = await getEmails()\nxin.emails = emails\n\n// notes that xin.emails is really JUST emails\nemails.push(...)\nemails.splice(...)\nemails[17].from = '...'\n```\n\nThen `xin` won't know and observers won't fire. So you can simply `touch` the path\nimpacted:\n\n```\nimport { touch } from 'xinjs'\ntouch('emails')\n```\n\nIn the calculator example, the vanilla `calculator` code calls `evaluate` behind\n`xin`'s back and uses `touch('calculatorExample.result')` to let `xin` know that\n`calculatorExample.result` has changed. This causes `xin` to update the\nDOM.\n\n## How it works\n\n`xin` is a `Proxy` wrapped around a bare object: effectively a map of strings to values.\n\nWhen you access the properties of an object assigned to `xin` it wraps the values in\nsimilar proxies, and tracks the **path** that got you there:\n\n```\nxin.foo = {\n bar: 'baz',\n luhrman: {\n job: 'director'\n }\n}\n```\n\nNow if you pull objects back out of `xin`:\n\n```\nlet foo = xin.foo\nlet luhrman = foo.luhrman\n```\n\n`foo` is a `Proxy` wrapped around the original *untouched* object, and it knows it came from 'foo'.\nSimilarly `luhrman` is a `Proxy` that knows it came from 'foo.luhrman'.\n\nIf you **change** a value in a wrapped object, e.g.\n\n```\nfoo.bar = 'bob'\nluhrman.job = 'writer'\n```\n\nThen it will trigger any observers looking for relevant changes. And each change will fire the observer\nand tell it the `path` that was changed. E.g. an observer watching `lurman` will be fired if `lurman`\nor one of `lurman`'s properties is changed.\n\n## The `boxed` proxy\n\n`boxed` is a sister to `xin` that wraps \"scalar\" values (`boolean`, `number`, `string`) in\nobjects. E.g. if you write something like:\n\n```\nxin.test = { answer: 42 }\nboxed.box = { pie: 'apple' }\n```\n\nThen:\n\n```\nxin.test.answer === 42\nxin.box.pie === 'apple'\n// box wraps \"scalars\" in objects\nboxed.test.answer.valueOf() === 42\nboxed.box.pie.valueOf() === 'apple'\n// anything that comes out of boxed has a path!\nxinPath(boxed.test.answer) === 'test.answer'\nxinPath(boxed.box.pie) === 'box.pie'\n```\n\nAside from always \"boxing\" scalar values, `boxed` works just like `xin`.\n\nIn the console, you can also access `boxed` and look at what happens if you\naccess `boxed.xinExample.string`. Note that this changes the value you get,\nthe underlying value is still what it was. If you set it to a new `string`\nvalue that's what will be stored. `xin` doesn't monkey with the values you\nassign.\n\n### Why?!\n\nAs far as Typescript is concerned, `xinProxy` just passes back what you put into it,\nwhich means that you can now write bindings with type-checking and autocomplete and\nnever use string literals. So something like this *just works*:\n\n```\nconst div = elements.div({bindText: boxed.box.pie})\n```\n\n…because `boxed.box.pie` has a `xinPath` which is what is actually used for binding,\nwhereas `xin.box.pie` is just a scalar value. Without `boxed` you could write\n`bindText: 'box.pie'` but you don't get lint support or autocomplete. (Also, in\nsome cases, you might even mangle the names of an object during minification and\n`boxed` will know the mangled name).\n\n### If you need the thing itself or the path to the thing…\n\n`proxy`s returned by `xin` are typically indistinguishable from the original object, but\nin a pinch `xinPath()` will give you the path (`string`) of a `XinProxy` while `xinValue`\nwill give its \"bare\" value. `xinPath()` can also be used to test if something is actually\na proxy, as it will return `undefined` for regular objects.\n\nE.g.\n\n```\nxinPath(luhrman) === 'foo.luhrman' // true\nconst bareLurhman = xinValue(luhrman) // not wrapped\n```\n\nYou may want the thing itself to, for example, perform a large number of changes to an\nobject without firing observers. You can let `xin` know you've made changes behind its back using\n`touch`, e.g.\n\n```\ndoTerribleThings(xinValue(luhrman))\n// eslint-disable-next-line\ntouch(luhrman)\n```\n\nThis is **useful** because `boxed.foo.bar` always knows where it came from, while\n`xin.foo.bar` only knows where it came from if it's an object value.\n\nThis means you can write:\n\n```js\nimport { boxed, elements } from 'tosijs'\n\nboxed.boxedExample = {\n string: 'hello, boxed'\n}\n\nconst { boxedExample } = boxed\n\nconst { label, input, div, span } = elements\n\npreview.append(\n div(\n {\n style: {\n display: 'flex',\n flexDirection: 'column',\n gap: 10,\n padding: 10\n }\n },\n div({bindText: boxedExample.string}),\n label(\n span('Edit this'),\n input({ bindValue: boxedExample.string})\n )\n )\n)\n```\n\nAnd the difference here is you can bind direct to the reference itself rather\nthan a string. This leverages autocomplete, linting, and so on in a way that\nusing string paths doesn't.\n\nIt does have a downside! `boxedExample.string !== 'hello, boxed'` and in fact\n`boxedExample.string !== boxedExample.string` because they're two different\n`String` objects. This is critical for comparisons such as `===` and `!==`.\nAlways use `boxed.foo.bar.xinValue`, `xinValue(boxed.foo.bar)` or `boxed.foo.bar.valueOf()`\nwhen performing comparisons like this.\n\n## Helper properties and functions\n\n`XinProxy` and `BoxedProxy` provide some helper properties and functions.\n\n- `xinValue` gets you the underlying value\n- `xinPath` gets you the string path\n- `xinBind(element: Element, binding: [XinBinding](/?bindings.ts), options?: {[key: string]: any})` will\n bind the `xinPath` the element with the specified binding.\n ```\n boxed.foo.color.bind(element, {\n toDOM(element, color){\n element.style.backgroundColor = color\n }\n })\n ```\n- `xinOn(element: HTMLElement, eventType: keyof HTMLElementEventMap)` will\n trigger the event handler when the specified element receives\n an event of the specified type.\n- `xinObserve(callback: ObserverCallbackFunction): UnobserveFunction` will\n trigger the provided callback when the value changes, and can be cancelled\n using the returned function.\n\n### To Do List Example\n\nEach of the features described thus far, along with the features of the\n`elementCreator` functions provided by the [elements](/?elements.ts) proxy\nare designed to eliminate boilerplate, simplify your code, and reduce\nthe chance of making costly errors.\n\nThis example puts all of this together.\n\n```js\nimport { elements, tosi } from 'tosijs'\n\nconst { todos } = tosi({\n todos: {\n list: [],\n newItem: ''\n }\n})\n\nconst { h3, div, label, input, button, template } = elements\n\nconst addItem = () => {\n todos.list.push({\n description: todos.newItem\n })\n todos.newItem = ''\n}\n\npreview.append(\n h3('To do'),\n div(\n {\n bindList: {\n value: todos.list\n }\n },\n template(\n div({ bindText: '^.description' })\n )\n ),\n div(\n input({\n placeholder: 'task',\n bindValue: todos.newItem,\n onKeyup(event) {\n if(event.key === 'Enter' && todos.newItem != '') {\n addItem()\n }\n }\n }),\n button('Add', {\n bindEnabled: todos.newItem,\n onClick: addItem\n })\n )\n)\n```\n*/\n\nimport {\n XinProxyObject,\n XinProxyTarget,\n XinObject,\n XinProxy,\n BoxedProxy,\n XinArray,\n XinValue,\n XinBinding,\n PathTestFunction,\n ObserverCallbackFunction,\n XinEventHandler,\n} from './xin-types'\nimport { settings } from './settings'\nimport {\n Listener,\n touch,\n observe as _observe,\n unobserve,\n updates,\n} from './path-listener'\nimport { getByPath, setByPath } from './by-path'\nimport { bind, on } from './bind'\nimport {\n xinValue,\n XIN_VALUE,\n XIN_SET,\n XIN_PATH,\n XIN_OBSERVE,\n XIN_BIND,\n XIN_ON,\n} from './metadata'\n\ninterface ProxyConstructor {\n revocable: <T extends object, P extends object>(\n target: T,\n handler: ProxyHandler<P>\n ) => { proxy: P; revoke: () => void }\n new <T extends object>(target: T, handler: ProxyHandler<T>): T\n new <T extends object, P extends object>(\n target: T,\n handler: ProxyHandler<P>\n ): P\n}\ndeclare let Proxy: ProxyConstructor\n\n// list of Array functions that change the array\nconst ARRAY_MUTATIONS = [\n 'sort',\n 'splice',\n 'copyWithin',\n 'fill',\n 'pop',\n 'push',\n 'reverse',\n 'shift',\n 'unshift',\n]\n\nconst registry: XinObject = {}\nconst debugPaths = true\n\n// in essence this very liberally matches foo ( .bar | [17] | [id=123] ) *\nconst validPath =\n /^\\.?([^.[\\](),])+(\\.[^.[\\](),]+|\\[\\d+\\]|\\[[^=[\\](),]*=[^[\\]()]+\\])*$/\n\nconst isValidPath = (path: string): boolean => validPath.test(path)\n\nconst extendPath = (path = '', prop = ''): string => {\n if (path === '') {\n return prop\n } else {\n if (prop.match(/^\\d+$/) !== null || prop.includes('=')) {\n return `${path}[${prop}]`\n } else {\n return `${path}.${prop}`\n }\n }\n}\n\nconst boxes: { [key: string]: (x: any) => any } = {\n string(s: string) {\n return new String(s)\n },\n boolean(b: boolean) {\n return new Boolean(b)\n },\n bigint(b: bigint) {\n return b\n },\n symbol(s: symbol) {\n return s\n },\n number(n: number) {\n return new Number(n)\n },\n}\n\nfunction box<T>(x: T, path: string): T {\n const t = typeof x\n if (x === undefined || t === 'object' || t === 'function') {\n return x\n } else {\n return new Proxy<XinProxyTarget, XinObject>(\n boxes[typeof x](x),\n regHandler(path, true)\n ) as T\n }\n}\n\nconst regHandler = (\n path: string,\n boxScalars: boolean\n): ProxyHandler<XinObject> => ({\n get(target: XinObject | XinArray, _prop: string | symbol): XinValue {\n switch (_prop) {\n case XIN_PATH:\n return path\n case XIN_VALUE:\n return target.valueOf ? target.valueOf() : target\n case XIN_SET:\n return (newValue: any) => (xin[path] = newValue)\n case XIN_OBSERVE:\n return (callback: ObserverCallbackFunction) => {\n const listener = _observe(path, callback)\n return () => unobserve(listener)\n }\n case XIN_ON:\n return (\n element: HTMLElement,\n eventType: keyof HTMLElementEventMap\n ): VoidFunction =>\n on(element, eventType, xinValue(target) as XinEventHandler)\n case XIN_BIND:\n return (element: Element, binding: XinBinding, options?: XinObject) => {\n bind(element, path, binding, options)\n }\n }\n if (typeof _prop === 'symbol') {\n return (target as XinObject)[_prop]\n }\n let prop = _prop\n const compoundProp =\n prop.match(/^([^.[]+)\\.(.+)$/) ?? // basePath.subPath (omit '.')\n prop.match(/^([^\\]]+)(\\[.+)/) ?? // basePath[subPath\n prop.match(/^(\\[[^\\]]+\\])\\.(.+)$/) ?? // [basePath].subPath (omit '.')\n prop.match(/^(\\[[^\\]]+\\])\\[(.+)$/) // [basePath][subPath\n if (compoundProp !== null) {\n const [, basePath, subPath] = compoundProp\n const currentPath = extendPath(path, basePath)\n const value = getByPath(target, basePath)\n return value !== null && typeof value === 'object'\n ? new Proxy<XinObject, XinProxyObject>(\n value,\n regHandler(currentPath, boxScalars)\n )[subPath]\n : value\n }\n if (prop.startsWith('[') && prop.endsWith(']')) {\n prop = prop.substring(1, prop.length - 1)\n }\n if (\n (!Array.isArray(target) && target[prop] !== undefined) ||\n (Array.isArray(target) && prop.includes('='))\n ) {\n let value: XinValue\n if (prop.includes('=')) {\n const [idPath, needle] = prop.split('=')\n value = (target as XinObject[]).find(\n (candidate: XinObject) =>\n `${getByPath(candidate, idPath) as string}` === needle\n )\n } else {\n // we're working around Typescript's incorrect understanding of arrays\n value = (target as XinArray)[prop as unknown as number]\n }\n if (value instanceof Object) {\n const currentPath = extendPath(path, prop)\n return new Proxy<XinObject, XinProxyObject>(\n value instanceof Function ? value.bind(target) : value,\n regHandler(currentPath, boxScalars)\n ) as XinValue\n } else {\n return boxScalars ? box(value, extendPath(path, prop)) : value\n }\n } else if (Array.isArray(target)) {\n const value = target[prop as unknown as number]\n return typeof value === 'function'\n ? (...items: any[]) => {\n const result = value.apply(target, items)\n if (ARRAY_MUTATIONS.includes(prop)) {\n touch(path)\n }\n return result\n }\n : typeof value === 'object'\n ? new Proxy<XinProxyTarget, XinObject>(\n value,\n regHandler(extendPath(path, prop), boxScalars)\n )\n : boxScalars\n ? box(value, extendPath(path, prop))\n : value\n } else {\n return boxScalars\n ? box(target[prop], extendPath(path, prop))\n : target[prop]\n }\n },\n set(_, prop: string, value: any) {\n value = xinValue(value)\n const fullPath = prop !== XIN_VALUE ? extendPath(path, prop) : path\n if (debugPaths && !isValidPath(fullPath)) {\n throw new Error(`setting invalid path ${fullPath}`)\n }\n const existing = xinValue(xin[fullPath])\n if (existing !== value && setByPath(registry, fullPath, value)) {\n touch(fullPath)\n }\n return true\n },\n})\n\nconst observe = (\n test: string | RegExp | PathTestFunction,\n callback: string | ObserverCallbackFunction\n): Listener => {\n const func = typeof callback === 'function' ? callback : xin[callback]\n\n if (typeof func !== 'function') {\n throw new Error(\n `observe expects a function or path to a function, ${\n callback as string\n } is neither`\n )\n }\n\n return _observe(test, func as ObserverCallbackFunction)\n}\n\nconst xin = new Proxy<XinObject, XinProxy<XinObject>>(\n registry,\n regHandler('', false)\n)\n\nconst boxed = new Proxy<XinObject, BoxedProxy<XinObject>>(\n registry,\n regHandler('', true)\n)\n\n// settings and isValidPath are only used for internal testing\nexport { xin, boxed, updates, touch, observe, unobserve, settings, isValidPath }\n",
|
|
12
11
|
"import { cloneWithBindings } from './metadata'\nimport { ContentType, ValueElement } from './xin-types'\n\nexport const dispatch = (target: Element, type: string): void => {\n const event = new Event(type)\n target.dispatchEvent(event)\n}\n\nconst valueType = (element: Element): string => {\n if (element instanceof HTMLInputElement) {\n return element.type\n } else if (\n element instanceof HTMLSelectElement &&\n element.hasAttribute('multiple')\n ) {\n return 'multi-select'\n } else {\n return 'other'\n }\n}\n\nexport const setValue = (element: Element, newValue: any): void => {\n switch (valueType(element)) {\n case 'radio':\n ;(element as HTMLInputElement).checked =\n (element as HTMLInputElement).value === newValue\n break\n case 'checkbox':\n ;(element as HTMLInputElement).checked = !!newValue\n break\n case 'date':\n ;(element as HTMLInputElement).valueAsDate = new Date(newValue)\n break\n case 'multi-select':\n for (const option of Array.from(\n (element as HTMLSelectElement).querySelectorAll('option')\n ) as HTMLOptionElement[]) {\n option.selected = newValue[option.value]\n }\n break\n default:\n ;(element as HTMLInputElement).value = newValue\n }\n}\n\ninterface PickMap {\n [key: string]: boolean\n}\nexport const getValue = (element: ValueElement): any => {\n switch (valueType(element)) {\n case 'radio': {\n const radio = element.parentElement?.querySelector(\n `[name=\"${element.name}\"]:checked`\n ) as HTMLInputElement\n return radio != null ? radio.value : null\n }\n case 'checkbox':\n return (element as HTMLInputElement).checked\n case 'date':\n return (element as HTMLInputElement).valueAsDate?.toISOString()\n case 'multi-select':\n return Array.from(element.querySelectorAll('option')).reduce(\n (map: PickMap, option: HTMLOptionElement): PickMap => {\n map[option.value] = option.selected\n return map\n },\n {}\n )\n default:\n return element.value\n }\n}\n\nconst { ResizeObserver } = globalThis\nexport const resizeObserver =\n ResizeObserver != null\n ? new ResizeObserver((entries) => {\n for (const entry of entries) {\n const element = entry.target\n dispatch(element, 'resize')\n }\n })\n : {\n observe() {},\n unobserve() {},\n }\n\nexport const appendContentToElement = (\n elt: Element | ShadowRoot | null | undefined,\n content: ContentType | null | undefined,\n cloneElements = true\n): void => {\n if (elt != null && content != null) {\n if (typeof content === 'string') {\n elt.textContent = content\n } else if (Array.isArray(content)) {\n content.forEach((node) => {\n elt.append(\n node instanceof Node && cloneElements ? cloneWithBindings(node) : node\n )\n })\n } else if (content instanceof Node) {\n elt.append(cloneElements ? cloneWithBindings(content) : content)\n } else {\n throw new Error('expect text content or document node')\n }\n }\n}\n",
|
|
13
12
|
"/*#\n# A.2 throttle & debounce\n\nUsage:\n\n```\nconst debouncedFunc = debounce(func, 250)\nconst throttledFunc = debounce(func, 250)\n```\n\n`throttle(voidFunc, interval)` and `debounce(voidFunc, interval)` are utility functions for\nproducing functions that filter out unnecessary repeated calls to a function, typically\nin response to rapid user input, e.g. from keystrokes or pointer movement.\n\n```js\nimport { throttle, debounce, on } from 'tosijs'\n\nfunction follow( element ) {\n return ( event ) => {\n element.style.top = event.offsetY + 'px'\n element.style.left = event.offsetX + 'px'\n }\n}\non(preview, 'mousemove', follow(preview.querySelector('#unfiltered')))\non(preview, 'mousemove', throttle(follow(preview.querySelector('#throttle'))))\non(preview, 'mousemove', debounce(follow(preview.querySelector('#debounce'))))\n```\n```html\n<h3>Throttle & Debounce in Action</h3>\n<p>Move your mouse around in here…</p>\n<p style=\"color: blue\">follow function — triggers immediately</p>\n<p style=\"color: red\">throttled follow function — triggers every 250ms</p>\n<p style=\"color: green\">debounced follow function — stop moving for 250ms to trigger it</p>\n<div id=\"unfiltered\" class=\"follower\" style=\"height: 20px; width: 20px; border-color: blue\"></div>\n<div id=\"throttle\" class=\"follower\" style=\"height: 40px; width: 40px; border-color: red\"></div>\n<div id=\"debounce\" class=\"follower\" style=\"height: 60px; width: 60px; border-color: green\"></div>\n```\n```css\n.preview * {\n pointer-events: none;\n}\n.preview .follower {\n top: 100px;\n left: 400px;\n position: absolute;\n border-width: 4px;\n border-style: solid;\n background: transparent;\n transform: translateX(-50%) translateY(-50%);\n}\n```\n\nThe usual purpose of these functions is to prevent over-calling of a function based on\nrapidly changing data, such as keyboard event or scroll event handling.\n\n`debounce`ed functions will only actually be called `interval` ms after the last time the\nwrapper is called.\n\nE.g. if the user types into a search field, you can call a `debounce`ed\nfunction to do the query, and it won't fire until the user stops typing for `interval` ms.\n\n`throttle`ed functions will only called at most every `interval` ms.\n\nE.g. if the user types into a search field, you can call a `throttle`ed function\nevery `interval` ms, including one last time after the last time the wrapper is called.\n\n> In particular, both throttle and debounce are guaranteed to execute the\n> wrapped function after the last call to the wrapper.\n\nNote that parameters will be passed to the wrapped function, and that *the last call always goes through*.\nHowever, parameters passed to skipped calls will *never* reach the wrapped function.\n*/\n\ntype VoidFunc = (...args: any[]) => void\n\nexport const debounce = (origFn: VoidFunc, minInterval = 250): VoidFunc => {\n let debounceId: number\n return (...args: any[]) => {\n if (debounceId !== undefined) clearTimeout(debounceId)\n debounceId = setTimeout(() => {\n origFn(...args)\n }, minInterval) as unknown as number\n }\n}\n\nexport const throttle = (origFn: VoidFunc, minInterval = 250): VoidFunc => {\n let debounceId: number\n let previousCall = Date.now() - minInterval\n let inFlight = false\n return (...args: any[]) => {\n clearTimeout(debounceId)\n debounceId = setTimeout(() => {\n origFn(...args)\n previousCall = Date.now()\n }, minInterval) as unknown as number\n if (!inFlight && Date.now() - previousCall >= minInterval) {\n inFlight = true\n try {\n origFn(...args)\n previousCall = Date.now()\n } finally {\n inFlight = false\n }\n }\n }\n}\n",
|
|
14
|
-
"/*#\n# 2.1 binding arrays\n\nThe most likely source of complexity and performance issues in applications is\ndisplaying large lists or grids of objects. `xinjs` provides robust support\nfor handling this efficiently.\n\n## `bindList` and `bindings.list`\n\nThe basic structure of a **list-binding** is:\n\n div( // container element\n {\n bindList: {\n value: boxed.path.to.array // OR 'path.to.array'\n idPath: 'id' // (optional) path to unique id of array items\n }\n },\n template( // template for the repeated item\n div( // repeated item should have a single root element\n ... // whatever you want\n span({\n bindText: '^.foo.bar' // binding to a given array member's `foo.bar`\n // '^' refers to the array item itself\n })\n )\n )\n )\n\n```js\nimport { elements, boxedProxy } from 'tosijs'\nconst { listBindingExample } = boxedProxy({\n listBindingExample: {\n array: ['this', 'is', 'an', 'example']\n }\n})\n\nconst { h3, ul, li, template } = elements\n\npreview.append(\n h3('binding an array of strings'),\n ul(\n {\n bindList: {\n value: listBindingExample.array\n },\n },\n template(\n li({ bindText: '^' })\n )\n )\n)\n```\n\n### id-paths\n\n**id-paths** are a wrinkle in `xin`'s paths specifically there to make list-bindign more efficient.\nThis is because in many cases you will encounter large arrays of objects, each with a unique id somewhere, e.g. it might be `id` or `uid`\nor even buried deeper…\n\n xin.message = [\n {\n id: '1234abcd',\n title: 'hello',\n body: 'hello there!'\n },\n …\n ]\n\nInstead of referring to the first item in `messages` as `messages[0]` it can be referred to\nas `messages[id=1234abcd]`, and this will retrieve the item regardless of its position in messages.\n\nSpecifying an `idPath` in a list-binding will allow the list to be more efficiently updated.\nIt's the equivalent of a `key` in React, the difference being that its optional and\nspecifically intended to leverage pre-existing keys where available.\n\n## Virtualized Lists\n\nThe real power of `bindList` comes from its support for virtualizing lists.\n\n bindList: {\n value: emojiListExample.array,\n idPath: 'name',\n virtual: {\n height: 30,\n rowChunkSize: 3,\n },\n }\n\nSimply add a `virtual` property to the list-binding specifying a *minimum* `height` (and, optionally,\n`height`) and the list will be `virtualized` (meaning that only visible elements will be rendered,\nmissing elements being replaced by a single padding element above and below the list).\n\nYou can (optionally) specify `rowChunkSize` to virtualize the list in chunks of rows to allow\nconsistent `:nth-child()` styling.\n\nNow you can trivially bind an array of a million objects to the DOM and have it scroll at\n120fps.\n\n```js\nimport { elements, boxedProxy, tosi } from 'tosijs'\nconst request = await fetch(\n 'https://raw.githubusercontent.com/tonioloewald/emoji-metadata/master/emoji-metadata.json'\n)\nconst { emojiListExample } = tosi({\n emojiListExample: {\n array: await request.json()\n }\n})\n\nconst { div, span, template } = elements\n\npreview.append(\n div(\n {\n class: 'emoji-table',\n bindList: {\n value: emojiListExample.array,\n idPath: 'name',\n virtual: {\n height: 30,\n rowChunkSize: 3\n },\n }\n },\n template(\n div(\n {\n class: 'emoji-row',\n tabindex: 0,\n },\n span({ bindText: '^.chars', class: 'graphic' }),\n span({ bindText: '^.name', class: 'no-overflow' }),\n span({ bindText: '^.category', class: 'no-overflow' }),\n span({ bindText: '^.subcategory', class: 'no-overflow' })\n )\n )\n )\n)\n```\n```css\n.emoji-table {\n height: 100%;\n overflow: auto;\n}\n.emoji-row {\n display: grid;\n grid-template-columns: 50px 300px 200px 200px;\n align-items: center;\n height: 30px;\n overflow-x: hidden;\n}\n.emoji-row:nth-child(3n) {\n background: #f002;\n}\n.emoji-row:nth-child(3n+2) {\n background: #00f2;\n}\n\n.emoji-row > .graphic {\n font-size: 20px;\n justify-self: center;\n}\n\n.emoji-row > * {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n```\n\n### Virtualized Grids\n\nYou can virtualize a grid by styling the padding elements (with class `.virtual-list-padding`)\nto have the correct column span. (You can also just specify a fixed `width` for your list.)\n\n```js\nimport { elements, tosi } from 'tosijs'\nconst { div, template } = elements\nconst list = []\nfor (let i = 0; i < 2000; i++) {\n list.push({id: i})\n}\n\nconst { bigBindTest } = tosi({\n bigBindTest: { list }\n})\n\npreview.append(\n div(\n {\n class: 'virtual-grid-example',\n bindList: {\n value: bigBindTest.list,\n idPath: 'id',\n virtual: {\n height: 40,\n visibleColumns: 7,\n rowChunkSize: 2,\n }\n },\n },\n template(\n div({\n class: 'cell',\n bindText: '^.id'\n })\n )\n )\n)\n```\n```css\n.virtual-grid-example {\n height: 100%;\n width: 100%;\n overflow-y: auto;\n display: grid;\n grid-template-columns: 14% 14% 14% 14% 14% 14% 14%;\n}\n\n.virtual-grid-example .virtual-list-padding {\n grid-column: 1 / 8;\n}\n\n.virtual-grid-example .cell {\n height: 40px;\n line-height: 40px;\n text-align: center;\n}\n\n.virtual-grid-example .cell:nth-child(14n+2),\n.virtual-grid-example .cell:nth-child(14n+3),\n.virtual-grid-example .cell:nth-child(14n+4),\n.virtual-grid-example .cell:nth-child(14n+5),\n.virtual-grid-example .cell:nth-child(14n+6),\n.virtual-grid-example .cell:nth-child(14n+7),\n.virtual-grid-example .cell:nth-child(14n+8) {\n background: #0001;\n}\n```\n\n## Filtered Lists\n\nIt's also extremely common to want to filter a rendered list, and `xinjs`\nprovides both simple and powerful methods for doing this.\n\n## `hiddenProp` and `visibleProp`\n\n`hiddenProp` and `visibleProp` allow you to use a property to hide or show array\nelements (and they can be `symbol` values if you want to avoid \"polluting\"\nyour data, e.g. for round-tripping to a database.)\n\n## `filter` and `needle`\n\n bindList: {\n value: filterListExample.array,\n idPath: 'name',\n virtual: {\n height: 30,\n },\n filter: (emojis, needle) => {\n needle = needle.trim().toLocaleLowerCase()\n if (!needle) {\n return emojis\n }\n return emojis.filter(emoji => `${emoji.name} ${emoji.category} ${emoji.subcategory}`.toLocaleLowerCase().includes(needle))\n },\n needle: filterListExample.needle\n }\n\nIf `bindList`'s options provide a `filter` function and a `needle` (proxy or path) then\nthe list will be filtered using the function via throttled updates.\n\n`filter` is passed the whole array, and `needle` can be anything so, `filter` can\nsort the array or even synthesize it entirely.\n\nIn this example the `needle` is an object containing both a `needle` string and `sort`\nvalue, and the `filter` function filters the list if the string is non-empty, and\nsorts the list if `sort` is not \"default\". Also note that an `input` event handler\nis used to `touch` the object and trigger updates.\n\n```js\n// note that this example is styled by the earlier example\n\nimport { elements, boxedProxy } from 'tosijs'\nconst request = await fetch(\n 'https://raw.githubusercontent.com/tonioloewald/emoji-metadata/master/emoji-metadata.json'\n)\nconst { filterListExample } = boxedProxy({\n filterListExample: {\n config: {\n needle: '',\n sort: 'default',\n },\n array: await request.json()\n }\n})\n\nconst { b, div, span, template, label, input, select, option } = elements\n\npreview.append(\n div(\n {\n style: {\n display: 'flex',\n padding: 10,\n gap: 10,\n height: 60,\n alignItems: 'center'\n },\n onInput() {\n // need to trigger change if any prop of config changes\n touch(filterListExample.config)\n },\n },\n b('filtered list'),\n span({style: 'flex: 1'}),\n label(\n span('sort by'),\n select(\n {\n bindValue: filterListExample.config.sort\n },\n option('default'),\n option('name'),\n option('category')\n ),\n ),\n input({\n type: 'search',\n placeholder: 'filter emoji',\n bindValue: filterListExample.config.needle\n })\n ),\n div(\n {\n class: 'emoji-table',\n style: 'height: calc(100% - 60px)',\n bindList: {\n value: filterListExample.array,\n idPath: 'name',\n virtual: {\n height: 30,\n rowChunkSize: 3,\n },\n filter: (emojis, config) => {\n let { needle, sort } = config\n needle = needle.trim().toLocaleLowerCase()\n if (needle) {\n emojis = emojis.filter(emoji => `${emoji.name} ${emoji.category} ${emoji.subcategory}`.toLocaleLowerCase().includes(needle))\n }\n return config.sort === 'default' ? emojis : emojis.sort((a, b) => a[config.sort] > b[config.sort] ? 1 : -1)\n },\n needle: filterListExample.config\n }\n },\n template(\n div(\n {\n class: 'emoji-row',\n tabindex: 0,\n },\n span({ bindText: '^.chars', class: 'graphic' }),\n span({ bindText: '^.name', class: 'no-overflow' }),\n span({ bindText: '^.category', class: 'no-overflow' }),\n span({ bindText: '^.subcategory', class: 'no-overflow' })\n )\n )\n )\n)\n```\n*/\nimport { settings } from './settings'\nimport { resizeObserver } from './dom'\nimport { throttle } from './throttle'\nimport { xin } from './xin'\nimport {\n cloneWithBindings,\n elementToItem,\n elementToBindings,\n BOUND_SELECTOR,\n DataBinding,\n xinValue,\n xinPath,\n} from './metadata'\nimport { XinObject, XinTouchableType } from './xin-types'\nimport { Listener } from './path-listener'\n\nexport const listBindingRef = Symbol('list-binding')\nconst SLICE_INTERVAL_MS = 16 // 60fps\nconst FILTER_INTERVAL_MS = 100 // 10fps\n\ntype ListFilter = (array: any[], needle: any) => any[]\n\ninterface ListBindingOptions {\n idPath?: string\n virtual?: {\n height: number\n width?: number\n visibleColumns?: number\n rowChunkSize?: number\n }\n hiddenProp?: symbol | string\n visibleProp?: symbol | string\n filter?: ListFilter\n needle?: XinTouchableType\n}\n\ninterface VirtualListSlice {\n items: any[]\n firstItem: number\n lastItem: number\n topBuffer: number\n bottomBuffer: number\n}\n\nfunction updateRelativeBindings(element: Element, path: string): void {\n const boundElements = Array.from(element.querySelectorAll(BOUND_SELECTOR))\n if (element.matches(BOUND_SELECTOR)) {\n boundElements.unshift(element)\n }\n for (const boundElement of boundElements) {\n const bindings = elementToBindings.get(boundElement) as DataBinding[]\n for (const binding of bindings) {\n if (binding.path.startsWith('^')) {\n binding.path = `${path}${binding.path.substring(1)}`\n }\n if (binding.binding.toDOM != null) {\n binding.binding.toDOM(boundElement as Element, xin[binding.path])\n }\n }\n }\n}\n\nexport class ListBinding {\n boundElement: Element\n listTop: HTMLElement\n listBottom: HTMLElement\n template: Element\n options: ListBindingOptions\n itemToElement: WeakMap<XinObject, Element>\n private _array: any[] = []\n private readonly _update?: VoidFunction\n private _previousSlice?: VirtualListSlice\n static filterBoundObservers = new WeakMap<Element, Listener>()\n\n constructor(\n boundElement: Element,\n value: any[],\n options: ListBindingOptions = {}\n ) {\n this.boundElement = boundElement\n this.itemToElement = new WeakMap()\n if (boundElement.children.length !== 1) {\n throw new Error(\n 'ListBinding expects an element with exactly one child element'\n )\n }\n if (boundElement.children[0] instanceof HTMLTemplateElement) {\n const template = boundElement.children[0]\n if (template.content.children.length !== 1) {\n throw new Error(\n 'ListBinding expects a template with exactly one child element'\n )\n }\n this.template = cloneWithBindings(\n template.content.children[0]\n ) as HTMLElement\n } else {\n this.template = boundElement.children[0] as HTMLElement\n this.template.remove()\n }\n this.options = options\n this.listTop = document.createElement('div')\n this.listBottom = document.createElement('div')\n this.listTop.classList.add('virtual-list-padding')\n this.listBottom.classList.add('virtual-list-padding')\n this.boundElement.append(this.listTop)\n this.boundElement.append(this.listBottom)\n if (options.virtual != null) {\n resizeObserver.observe(this.boundElement)\n this._update = throttle(() => {\n this.update(this._array, true)\n }, SLICE_INTERVAL_MS)\n this.boundElement.addEventListener('scroll', this._update)\n this.boundElement.addEventListener('resize', this._update)\n }\n }\n\n private visibleSlice(): VirtualListSlice {\n const { virtual, hiddenProp, visibleProp } = this.options\n let visibleArray = this._array\n if (hiddenProp !== undefined) {\n visibleArray = visibleArray.filter((item) => item[hiddenProp] !== true)\n }\n if (visibleProp !== undefined) {\n visibleArray = visibleArray.filter((item) => item[visibleProp] === true)\n }\n if (this.options.filter && this.needle !== undefined) {\n visibleArray = this.options.filter(visibleArray, this.needle)\n }\n let firstItem = 0\n let lastItem = visibleArray.length - 1\n let topBuffer = 0\n let bottomBuffer = 0\n\n if (virtual != null && this.boundElement instanceof HTMLElement) {\n const width = this.boundElement.offsetWidth\n const height = this.boundElement.offsetHeight\n\n if (virtual.visibleColumns == null) {\n virtual.visibleColumns =\n virtual.width != null\n ? Math.max(1, Math.floor(width / virtual.width))\n : 1\n }\n const visibleRows =\n Math.ceil(height / virtual.height) + (virtual.rowChunkSize || 1)\n const totalRows = Math.ceil(visibleArray.length / virtual.visibleColumns)\n const visibleItems = virtual.visibleColumns * visibleRows\n let topRow = Math.floor(this.boundElement.scrollTop / virtual.height)\n if (topRow > totalRows - visibleRows + 1) {\n topRow = Math.max(0, totalRows - visibleRows + 1)\n }\n if (virtual.rowChunkSize) {\n topRow -= topRow % virtual.rowChunkSize\n }\n\n firstItem = topRow * virtual.visibleColumns\n lastItem = firstItem + visibleItems - 1\n\n topBuffer = topRow * virtual.height\n bottomBuffer = Math.max(\n (totalRows - visibleRows) * virtual.height - topBuffer,\n 0\n )\n }\n\n return {\n items: visibleArray,\n firstItem,\n lastItem,\n topBuffer,\n bottomBuffer,\n }\n }\n\n private needle?: any\n filter = throttle((needle: any) => {\n if (this.needle !== needle) {\n this.needle = needle\n this.update(this._array)\n }\n }, FILTER_INTERVAL_MS)\n\n update(array?: any[], isSlice?: boolean) {\n if (array == null) {\n array = []\n }\n this._array = array\n\n const { hiddenProp, visibleProp } = this.options\n const arrayPath: string = xinPath(array) as string\n\n const slice = this.visibleSlice()\n this.boundElement.classList.toggle(\n '-xin-empty-list',\n slice.items.length === 0\n )\n const previousSlice = this._previousSlice\n const { firstItem, lastItem, topBuffer, bottomBuffer } = slice\n if (\n hiddenProp === undefined &&\n visibleProp === undefined &&\n isSlice === true &&\n previousSlice != null &&\n firstItem === previousSlice.firstItem &&\n lastItem === previousSlice.lastItem\n ) {\n return\n }\n this._previousSlice = slice\n\n let removed = 0\n let moved = 0\n let created = 0\n\n for (const element of Array.from(this.boundElement.children)) {\n if (element === this.listTop || element === this.listBottom) {\n continue\n }\n const proxy = elementToItem.get(element as HTMLElement)\n if (proxy == null) {\n element.remove()\n } else {\n const idx = slice.items.indexOf(proxy)\n if (idx < firstItem || idx > lastItem) {\n element.remove()\n this.itemToElement.delete(proxy)\n elementToItem.delete(element as HTMLElement)\n removed++\n }\n }\n }\n\n this.listTop.style.height = String(topBuffer) + 'px'\n this.listBottom.style.height = String(bottomBuffer) + 'px'\n\n // build a complete new set of elements in the right order\n const elements: Element[] = []\n const { idPath } = this.options\n for (let i = firstItem; i <= lastItem; i++) {\n const item = slice.items[i]\n if (item === undefined) {\n continue\n }\n let element = this.itemToElement.get(xinValue(item))\n if (element == null) {\n created++\n element = cloneWithBindings(this.template) as HTMLElement\n if (typeof item === 'object') {\n this.itemToElement.set(xinValue(item), element)\n elementToItem.set(element, xinValue(item))\n }\n this.boundElement.insertBefore(element, this.listBottom)\n if (idPath != null) {\n const idValue = item[idPath] as string\n const itemPath = `${arrayPath}[${idPath}=${idValue}]`\n updateRelativeBindings(element, itemPath)\n } else {\n const itemPath = `${arrayPath}[${i}]`\n updateRelativeBindings(element, itemPath)\n }\n }\n elements.push(element)\n }\n\n // make sure all the elements are in the DOM and in the correct location\n let insertionPoint: Element | null = null\n for (const element of elements) {\n if (element.previousElementSibling !== insertionPoint) {\n moved++\n if (insertionPoint?.nextElementSibling != null) {\n this.boundElement.insertBefore(\n element,\n insertionPoint.nextElementSibling\n )\n } else {\n this.boundElement.insertBefore(element, this.listBottom)\n }\n }\n insertionPoint = element\n }\n\n if (settings.perf) {\n console.log(arrayPath, 'updated', { removed, created, moved })\n }\n }\n}\n\ninterface ListBoundElement extends Element {\n [listBindingRef]?: ListBinding\n}\n\nexport const getListBinding = (\n boundElement: ListBoundElement,\n value: any[],\n options?: ListBindingOptions\n): ListBinding => {\n let listBinding = boundElement[listBindingRef]\n if (listBinding === undefined) {\n listBinding = new ListBinding(boundElement, value, options)\n boundElement[listBindingRef] = listBinding\n }\n return listBinding\n}\n",
|
|
15
|
-
"/*#\n# 2. bind\n\n`bind()` lets you synchronize data / application state to the user-interface reliably,\nefficiently, and with a minimum of code.\n\n## An Aside on Reactive Programming vs. the Observer Model\n\nA good deal of front-end code deals with keeping the application's\nstate synchronized with the user-interface. One approach to this problem\nis [Reactive Programming](https://en.wikipedia.org/wiki/Reactive_programming)\nas exemplified by [React](https://reactjs.org) and its many imitators.\n\n`xinjs` works very well with React via the [useXin](https://github.com/tonioloewald/react-xinjs) React \"hook\".\nBut `xinjs` is not designed for \"reactive programming\" and in fact \"hooks\" aren't\n\"reactive\" at all, so much as an example of the \"observer\" or \"pub/sub\" pattern.\n\n`xinjs` is a \"path-observer\" in that it's an implementation of the\n[Observer Pattern](https://en.wikipedia.org/wiki/Observer_pattern)\nwhere **path strings** serve as a level of *indirection* to the things observed.\nThis allows data to be \"observed\" before it exists, which in particular *decouples* the setup\nof the user interface from the initialization of data and allows user interfaces\nbuilt with `xinjs` to be *deeply asynchronous*.\n\n## `bind()`\n\n```\nbind<T = Element>(\n element: T,\n what: XinTouchableType,\n binding: XinBinding,\n options: XinObject\n): T\n```\n\n`bind()` binds a `path` to an element, syncing the value at the path to and/or from the DOM.\n\n```js\nimport { bind, boxedProxy } from 'tosijs'\n\nconst { simpleBindExample } = boxedProxy({\n simpleBindExample: {\n showThing: true\n }\n})\n\nbind(\n preview.querySelector('b'),\n 'simpleBindExample.showThing',\n {\n toDOM(element, value) {\n element.style.visibility = value ? 'visible' : 'hidden'\n }\n }\n)\n\nbind(\n preview.querySelector('input[type=checkbox]'),\n // the boxedProxy can be used instead of a string path\n simpleBindExample.showThing,\n // we could just use bindings.value here\n {\n toDOM(element, value) {\n element.checked = value\n },\n fromDOM(element) {\n return element.checked\n }\n }\n)\n```\n```html\n<b>The thing</b><br>\n<label>\n <input type=\"checkbox\">\n Show the thing\n</label>\n```\n\nThe `bind` function is a simple way of tying an `HTMLElement`'s properties to\nstate via `path` using [bindings](/?bindings.ts)\n\n```\nimport {bind, bindings, xin, elements, updates} from 'xinjs'\nconst {div, input} = elements\n\nconst divElt = div()\nbind(divElt, 'app.title', bindings.text)\ndocument.body.append(divElt)\n\nconst inputElt = input()\nbind(inputElt, 'app.title', bindings.value)\n\nxin.app = {title: 'hello world'}\nawait updates()\n```\n\nWhat's happening is essentially the same as:\n\n```\ndivElt.textContent = xin.app.title\nobserve('app.title', () => divElt.textContent = xin.app.title)\n\ninputElt.value = xin.app.title\nobserve('app.title', () => inputElt.value = xin.app.title)\ninputElt.addEventListener('change', () => { xin.app.title = inputElt.value })\n```\n\nExcept:\n\n1. this code is harder to write\n2. it will fail if xin.app hasn't been initialized (which it hasn't been!)\n3. inputElt will also trigger *debounced* updates on `input` events\n\nAfter this. `div.textContent` and `inputElt.value` are 'hello world'.\nIf the user edits the value of `inputElt` then `xin.app.title` will\nbe updated, and `app.title` will be listed as a changed path, and\nan update will be fired via `setTimout`. When that update fires,\nanything observer of the paths `app.text` and `app` will be fired.\n\nA `binding` looks like this:\n\n```\ninterface XinBinding {\n toDOM?: (element: HTMLElement, value: any, options?: XinObject) => void\n fromDOM?: (element: HTMLElement) => any\n}\n```\n\nSimply put the `toDOM` method updates the DOM based on changes in state\nwhile `fromDOM` updates state based on data in the DOM. Most bindings\nwill have a `toDOM` method but no `fromDOM` method since `bindings.value`\n(which has both) covers most of the use-cases for `fromDOM`.\n\nIt's easy to write your own `bindings` if those in `bindings` don't meet your\nneed, e.g. here's a custom binding that toggles the visibility of an element\nbased on whether the bound value is neither \"falsy\" nor an empty `Array`.\n\n```\nconst visibility = {\n toDOM(element, value) {\n if (element.dataset.origDisplay === undefined && element.style.display !== 'none') {\n element.dataset.origDisplay = element.style.display\n }\n element.style.display = (value != null && value.length > 0) ? element.dataset.origDisplay : 'none'\n }\n}\nbind(listElement, 'app.bigList', visibility)\n```\n\n## `on()`\n\n```\non(element: Element, eventType: string, handler: XinEventHandler): VoidFunction\n\nexport type XinEventHandler<T extends Event = Event, E extends Element = Element> =\n | ((evt: T & {target: E}) => void)\n | ((evt: T & {target: E}) => Promise<void>)\n | string\n```\n\n```js\nimport { elements, on, boxedProxy } from 'tosijs'\nimport { postNotification } from 'tosijs-ui'\n\nconst makeHandler = (message) => () => {\n postNotification({ message, duration: 2 })\n}\n\nconst { onExample } = boxedProxy({\n onExample: {\n clickHandler: makeHandler('Hello from onExample proxy')\n }\n})\n\nconst { button, div, h2 } = elements\n\nconst hasListener = button('has listener')\nhasListener.addEventListener('click', makeHandler('Hello from addEventListener'))\n\npreview.append(\n div(\n {\n style: {\n display: 'flex',\n flexDirection: 'column',\n padding: 10,\n gap: 10\n }\n },\n h2('Event Handler Examples'),\n hasListener,\n button('just a callback', {onClick: makeHandler('just a callback')}),\n button('via proxy', {onClick: onExample.clickHandler}),\n )\n)\n```\n\n`on()` binds event-handlers to DOM elements.\n\nMore than syntax sugar for `addEventListener`, `on()` allows you to bind event\nhandlers inside `xin` by path (e.g. allowing event-handling code to be loaded\nasynchronously or lazily, or simply allowing event-handlers to be switched dynamically\nwithout rebinding) and it uses event-bubbling to minimize the actual number of\nevent handlers that need to be registered.\n\n`on()` returns a function for removing the event handler.\n\nIn essence, only one event handler of a given type is ever added to the\nDOM by `on()` (at `document.body` level), and then when that event is detected,\nthat handler goes from the original target through to the DOM and fires off\nevent-handlers, passing them an event proxy (so that `stopPropagation()` still\nworks).\n\n## `touchElement()`\n\n```\ntouchElement(element: Element, changedPath?: string)\n```\n\nThis is a low-level function for *immediately* updating a bound element. If you specifically\nwant to force a render of an element (versus anything bound to a path), simply call\n`touchElement(element)`. Specifying a `changedPath` will only trigger bindings bound\nto paths staring with the provided path.\n*/\n\nimport { xin, touch, observe } from './xin'\nimport {\n getListItem,\n elementToBindings,\n elementToHandlers,\n DataBindings,\n BOUND_CLASS,\n BOUND_SELECTOR,\n EVENT_CLASS,\n EVENT_SELECTOR,\n XinEventBindings,\n XIN_PATH,\n XIN_VALUE,\n} from './metadata'\nimport {\n XinObject,\n XinProps,\n XinEventHandler,\n XinTouchableType,\n XinBinding,\n XinBindingSpec,\n EventType,\n} from './xin-types'\nimport { ListBinding, listBindingRef } from './list-binding'\n\nconst { document, MutationObserver } = globalThis\n\nexport const touchElement = (element: Element, changedPath?: string): void => {\n const dataBindings = elementToBindings.get(element)\n if (dataBindings == null) {\n return\n }\n for (const dataBinding of dataBindings) {\n const { binding, options } = dataBinding\n let { path } = dataBinding\n const { toDOM } = binding\n if (toDOM != null) {\n if (path.startsWith('^')) {\n const dataSource = getListItem(element)\n if (dataSource != null && (dataSource as XinProps)[XIN_PATH] != null) {\n path = dataBinding.path = `${\n (dataSource as XinProps)[XIN_PATH]\n }${path.substring(1)}`\n } else {\n console.error(\n `Cannot resolve relative binding ${path}`,\n element,\n 'is not part of a list'\n )\n throw new Error(`Cannot resolve relative binding ${path}`)\n }\n }\n if (changedPath == null || path.startsWith(changedPath)) {\n toDOM(element, xin[path], options)\n }\n }\n }\n}\n\n// this is just to allow bind to be testable in node\nif (MutationObserver != null) {\n const observer = new MutationObserver((mutationsList) => {\n mutationsList.forEach((mutation) => {\n Array.from(mutation.addedNodes).forEach((node) => {\n if (node instanceof Element) {\n Array.from(node.querySelectorAll(BOUND_SELECTOR)).forEach((element) =>\n touchElement(element as Element)\n )\n }\n })\n })\n })\n observer.observe(document.body, { subtree: true, childList: true })\n}\n\nobserve(\n () => true,\n (changedPath: string) => {\n const boundElements = Array.from(document.querySelectorAll(BOUND_SELECTOR))\n\n for (const element of boundElements) {\n touchElement(element as HTMLElement, changedPath)\n }\n }\n)\n\nconst handleChange = (event: Event): void => {\n // @ts-expect-error-error\n let target = event.target.closest(BOUND_SELECTOR)\n while (target != null) {\n const dataBindings = elementToBindings.get(target) as DataBindings\n for (const dataBinding of dataBindings) {\n const { binding, path } = dataBinding\n const { fromDOM } = binding\n if (fromDOM != null) {\n let value\n try {\n value = fromDOM(target, dataBinding.options)\n } catch (e) {\n console.error('Cannot get value from', target, 'via', dataBinding)\n throw new Error('Cannot obtain value fromDOM')\n }\n if (value != null) {\n const existing = xin[path]\n if (existing == null) {\n xin[path] = value\n } else {\n const existingActual =\n existing[XIN_PATH] != null\n ? (existing as XinProps)[XIN_VALUE]\n : existing\n const valueActual =\n value[XIN_PATH] != null ? value[XIN_VALUE] : value\n if (existingActual !== valueActual) {\n xin[path] = valueActual\n }\n }\n }\n }\n }\n target = target.parentElement.closest(BOUND_SELECTOR)\n }\n}\n\nif (globalThis.document != null) {\n document.body.addEventListener('change', handleChange, true)\n document.body.addEventListener('input', handleChange, true)\n}\n\ninterface BindingOptions {\n [key: string]: any\n}\n\nexport function bind<T extends Element = Element>(\n element: T,\n what: XinTouchableType | XinBindingSpec,\n binding: XinBinding<T>,\n options?: BindingOptions\n): T {\n if (element instanceof DocumentFragment) {\n throw new Error('bind cannot bind to a DocumentFragment')\n }\n let path: string\n if (\n typeof what === 'object' &&\n (what as XinProps)[XIN_PATH] === undefined &&\n options === undefined\n ) {\n const { value } = what as XinBindingSpec\n path = typeof value === 'string' ? value : value[XIN_PATH]\n options = what as XinObject\n delete options.value\n } else {\n path = typeof what === 'string' ? what : (what as XinProps)[XIN_PATH]\n }\n if (path == null) {\n throw new Error('bind requires a path or object with xin Proxy')\n }\n const { toDOM } = binding\n\n element.classList?.add(BOUND_CLASS)\n let dataBindings = elementToBindings.get(element)\n if (dataBindings == null) {\n dataBindings = []\n elementToBindings.set(element, dataBindings)\n }\n dataBindings.push({\n path,\n binding: binding as XinBinding<Element>,\n options,\n })\n\n if (toDOM != null && !path.startsWith('^')) {\n // not calling toDOM directly here allows virtual list bindings to work\n touch(path)\n }\n\n if (options?.filter && options?.needle) {\n bind(element, options.needle, {\n toDOM(element, value) {\n console.log({ needle: value })\n ;(element as { [listBindingRef]?: ListBinding })[\n listBindingRef\n ]?.filter(value)\n },\n })\n }\n\n return element\n}\n\nconst handledEventTypes: Set<string> = new Set()\n\nconst handleBoundEvent = (event: Event): void => {\n // @ts-expect-error-error\n let target = event?.target.closest(EVENT_SELECTOR)\n let propagationStopped = false\n\n const wrappedEvent = new Proxy(event, {\n get(target, prop) {\n if (prop === 'stopPropagation') {\n return () => {\n event.stopPropagation()\n propagationStopped = true\n }\n } else {\n const value = (target as any)[prop]\n return typeof value === 'function' ? value.bind(target) : value\n }\n },\n })\n const nohandlers = new Set<XinEventHandler>()\n while (!propagationStopped && target != null) {\n const eventBindings = elementToHandlers.get(target) as XinEventBindings\n const handlers = eventBindings[event.type] || nohandlers\n for (const handler of handlers) {\n if (typeof handler === 'function') {\n handler(wrappedEvent as Event & { target: Element })\n } else {\n const func = xin[handler]\n if (typeof func === 'function') {\n func(wrappedEvent)\n } else {\n throw new Error(`no event handler found at path ${handler}`)\n }\n }\n if (propagationStopped) {\n continue\n }\n }\n target =\n target.parentElement != null\n ? target.parentElement.closest(EVENT_SELECTOR)\n : null\n }\n}\n\ntype RemoveListener = VoidFunction\n\nexport function on<E extends HTMLElement, K extends EventType>(\n element: E,\n eventType: K,\n eventHandler: XinEventHandler<HTMLElementEventMap[K], E>\n): RemoveListener {\n let eventBindings = elementToHandlers.get(element)\n element.classList.add(EVENT_CLASS)\n if (eventBindings == null) {\n eventBindings = {}\n elementToHandlers.set(element, eventBindings)\n }\n if (!eventBindings[eventType]) {\n eventBindings[eventType] = new Set<XinEventHandler>()\n }\n eventBindings[eventType].add(eventHandler as XinEventHandler)\n if (!handledEventTypes.has(eventType)) {\n handledEventTypes.add(eventType)\n document.body.addEventListener(eventType, handleBoundEvent, true)\n }\n return () => {\n eventBindings[eventType].delete(eventHandler as XinEventHandler)\n }\n}\n",
|
|
16
|
-
"/*#\n# 2.2 bindings\n\n`bindings` is simply a collection of common bindings.\n\nYou can create your own bindings easily enough (and add them to `bindings` if so desired).\n\nA `binding` looks like this:\n\n```\ninterface XinBinding {\n toDOM?: (element: HTMLElement, value: any, options?: XinObject) => void\n fromDOM?: (element: HTMLElement) => any\n}\n```\n\nThe `fromDOM` function is only needed for bindings to elements that trigger `change` or `input`\nevents, typically `<input>`, `<textarea>`, and `<select>` elements, and of course your\nown [Custom Elements](/?components.ts).\n\n## value\n\nThe `value` binding syncs state from `xin` to the bound element's `value` property. In\ngeneral this should only be used for binding simple things, like `<input>` and `<textarea>`\nelements.\n\n## text\n\nThe `text` binding copies state from `xin` to the bound element's `textContent` property.\n\n## enabled & disabled\n\nThe `enabled` and `disabled` bindings allow you to make a widget's enabled status\nbe determined by the truthiness of something in `xin`, e.g.\n\n```\nimport { xinProxy, elements } from 'xinjs'\n\nconst myDoc = xinProxy({\n myDoc: {\n content: ''\n unsavedChanges: false\n }\n}, 1)\n\n// this button will only be enabled if there is something in `myList.array`\ndocument.body.append(\n elements.textarea({\n bindValue: myDoc.content,\n onInput() {\n myDoc.unsavedChanges = true\n }\n }),\n elements.button(\n 'Save Changes',\n {\n bindEnabled: myDoc.unsavedChanges,\n onClick() {\n // save the doc\n myDoc.unsavedChanges = false\n }\n }\n )\n)\n```\n\n## list\n\nThe `list` binding makes a copy of a `template` element inside the bound element\nfor every item in the bound `Array`.\n\nIt uses the existing **single** child element it finds inside the bound element\nas its `template`. If the child is a `<template>` (which is a good idea) then it\nexpects that `template` to have a *single child element*.\n\nE.g. if you have a simple unordered list:\n\n <ul>\n <li></li>\n </ul>\n\nYou can bind an array to the `<ul>` and it will make a copy of the `<li>` inside\nfor each item in the source array.\n\nThe `list` binding accepts as options:\n- `idPath: string`\n- `initInstance: (element, item: any) => void`\n- `updateInstance: (element, item: any) => void`\n- `virtual: {width?: number, height: number}`\n- `hiddenProp: symbol | string`\n- `visibleProp: symbol | string`\n\n`initInstance` is called once for each element created, and is passed\nthat element and the array value that it represents.\n\nMeanwhile, `updateInstance` is called once on creation and then any time the\narray value is updated.\n\n### Virtual List Binding\n\nIf you want to bind large arrays with minimal performance impact, you can make a list\nbinding `virtual` by passing the `height` (and optionally `width`) of an item.\nOnly visible elements will be rendered. Just make sure the values passed represent\nthe *minimum* dimensions of the individual rendered items if they can vary in size.\n\n### Filtered Lists and Detail Views\n\nYou can **filter** the elements you wish to display in a bound list by using the\n`hiddenProp` (to hide elements of the list) and/or `visibleProp` (to show elements\nof the list).\n\nYou can pass a `path` or a `symbol` as either the `hiddenProp` or `visibleProp`.\n\nTypically, you can use `hiddenProp` to power filters and `visibleProp` to power\ndetail views. The beauty of using symbols is that it won't impact the serialized\nvalues of the array and different views of the array can use different selection\nand filtering criteria.\n\n> **Note** for a given list-binding, if you specify `hiddenProp` (but not `visibleProp`),\n> then all items in the array will be shown *unless* `item[hiddenProp] === true`.\n>\n> Conversely, if you specify `visibleProp` (but not `hiddenProp`), then all items\n> in the array will be ignored *unless* `item[visibleProp] === true`.\n>\n> If, for some reason, you specify both then an item will only be visible if\n> it `item[visibleProp] === true` and `item[hiddenProp] !== true`.\n\n### Binding custom-elements using idPath\n\nIf you list-bind a custom-element with `bindValue` implemented and providing an\n`idPath` then the list-binding will bind the array items to the value of the\ncustom-element.\n\n### xin-empty-list class\n\nThe `list` binding will automatically add the class `-xin-empty-list` to a\ncontainer bound to an empty array, making it easier to conditionally render\ninstructions or explanations when a list is empty.\n*/\nimport { XinObject, XinBinding, ValueElement } from './xin-types'\nimport { getListBinding } from './list-binding'\nimport { getValue, setValue } from './dom'\n\nexport const bindings: { [key: string | symbol]: XinBinding<Element> } = {\n value: {\n toDOM: setValue,\n\n fromDOM(element: Element) {\n return getValue(element as ValueElement)\n },\n },\n\n text: {\n toDOM(element: Element, value: any) {\n element.textContent = value\n },\n },\n\n enabled: {\n toDOM(element: Element, value: any) {\n ;(element as HTMLInputElement).disabled = !value\n },\n },\n\n disabled: {\n toDOM(element: Element, value: any) {\n ;(element as HTMLButtonElement).disabled = Boolean(value)\n },\n },\n\n list: {\n toDOM(element: Element, value: any[], options?: XinObject) {\n const listBinding = getListBinding(element, value, options)\n listBinding.update(value)\n },\n },\n}\n",
|
|
17
|
-
"/*#\n# A.1 more-math\n\nSome simple functions egregiously missing from the Javascript `Math`\nobject. They are exported from `xinjs` as the `MoreMath` object.\n\n## Functions\n\n`clamp(min, v, max)` will return `v` if it's between `min` and `max`\nand the `min` or `max` otherwise.\n\nIf min > max, the function will return NaN.\n\n```\nclamp(0, 0.5, 1) // produces 0.5\nclamp(0, -0.5, 1) // produces 0\nclamp(-50, 75, 50) // produces 50\n```\n\n`lerp(a, b, t, clamped = true)` will interpolate linearly between `a` and `b` using\nparameter `t`. `t` will be clamped to the interval `[0, 1]`, so\n`lerp` will be clamped *between* a and b unless you pass `false` as the\noptional fourth parameter (allowing `lerp()` to extrapolate).\n\n```\nlerp(0, 10, 0.5) // produces 5\nlerp(0, 10, 2) // produces 10\nlerp(0, 10, 2, false) // produces 20\nlerp(5, -5, 0.75) // produces -2.5\n```\n\n## Constants\n\n`RADIANS_TO_DEGREES` and `DEGREES_TO_RADIANS` are values to multiply\nan angle by to convert between degrees and radians.\n*/\n\nexport const RADIANS_TO_DEGREES = 180 / Math.PI\nexport const DEGREES_TO_RADIANS = Math.PI / 180\n\nexport function clamp(min: number, v: number, max: number): number {\n return max < min ? NaN : v < min ? min : v > max ? max : v\n}\n\nexport function lerp(a: number, b: number, t: number, clamped = true): number {\n if (clamped) t = clamp(0, t, 1)\n return t * (b - a) + a\n}\n\nexport const MoreMath = {\n RADIANS_TO_DEGREES,\n DEGREES_TO_RADIANS,\n clamp,\n lerp,\n}\n",
|
|
18
|
-
"export function getCssVar(\n variableName: string,\n atElement = document.body\n): string {\n const computedStyle = getComputedStyle(atElement)\n if (variableName.endsWith(')') && variableName.startsWith('var(')) {\n variableName = variableName.slice(4, -1)\n }\n return computedStyle.getPropertyValue(variableName).trim()\n}\n",
|
|
19
|
-
"/*#\n# 5.1 color\n\n`xinjs` includes a lightweight, powerful `Color` class for manipulating colors.\nI hope at some point CSS will provide sufficiently capable native color calculations\nso that this will no longer be needed. Some of these methods have begun to appear,\nand are approaching wide implementation.\n\n## Color\n\nThe most straightforward methods for creating a `Color` instance are to use the\n`Color()` constructor to create an `rgb` or `rgba` representation, or using the\n`Color.fromCss()` to create a `Color` from any CSS (s)rgb representation,\ne.g.\n\n```\nnew Color(255, 255, 0) // yellow\nnew Color(0, 128, 0, 0.5) // translucent dark green\nColor.fromCss('#000') // black\nColor.fromCss('hsl(90deg 100% 50%)) // orange\nColor.fromCss('color(srgb 1 0 0.5)) // purple\n```\n\nNote that `Color.fromCss()` is not compatible with non-srgb color spaces. The new CSS\ncolor functions produce color specifications of the form `color(<space> ....)` and\n`Color.fromCSS()` will handle `color(srgb ...)` correctly (this is so it can parse the\noutput of `color-mix(in hsl ...)` but not other [color spaces](https://developer.mozilla.org/en-US/blog/css-color-module-level-4/#whats_new_in_css_colors_module_level_4).\n\n## Manipulating Colors\n\n```js\nimport { elements, Color } from 'tosijs'\n\nconst { label, span, div, input, button } = elements\n\nconst swatches = div({ class: 'swatches' })\nfunction makeSwatch(text) {\n const color = Color.fromCss(colorInput.value)\n const adjustedColor = eval('color.' + text)\n swatches.style.setProperty('--original', color)\n swatches.append(\n div(\n text,\n {\n class: 'swatch',\n title: `${adjustedColor.html} ${adjustedColor.hsla}`,\n style: {\n _adjusted: adjustedColor,\n _text: adjustedColor.contrasting()\n }\n }\n )\n )\n}\n\nconst colorInput = input({\n type: 'color',\n value: '#000',\n onInput: update\n})\nconst red = Color.fromCss('#f00')\nconst gray = Color.fromCss('#888')\nconst teal = Color.fromCss('teal')\nconst aliceblue = Color.fromCss('aliceblue')\n\nfunction update() {\n swatches.textContent = ''\n makeSwatch('brighten(-0.5)')\n makeSwatch('brighten(0.5)')\n makeSwatch('saturate(0.25)')\n makeSwatch('saturate(0.5)')\n makeSwatch('desaturate(0.5)')\n makeSwatch('desaturate(0.75)')\n makeSwatch('contrasting()')\n makeSwatch('contrasting(0.05)')\n makeSwatch('contrasting(0.25)')\n makeSwatch('contrasting(0.45)')\n makeSwatch('inverseLuminance')\n makeSwatch('mono')\n makeSwatch('rotate(-330)')\n makeSwatch('rotate(60)')\n makeSwatch('rotate(-270)')\n makeSwatch('rotate(120)')\n makeSwatch('rotate(-210)')\n makeSwatch('rotate(180)')\n makeSwatch('rotate(-150)')\n makeSwatch('rotate(240)')\n makeSwatch('rotate(-90)')\n makeSwatch('rotate(300)')\n makeSwatch('rotate(-30)')\n makeSwatch('opacity(0.1)')\n makeSwatch('opacity(0.5)')\n makeSwatch('opacity(0.75)')\n makeSwatch('rotate(-90).opacity(0.75)')\n makeSwatch('brighten(0.5).desaturate(0.5)')\n makeSwatch('blend(Color.black, 0.5)')\n makeSwatch('mix(Color.white, 0.4)')\n makeSwatch('blend(gray, 0.4)')\n makeSwatch('mix(red, 0.25)')\n makeSwatch('mix(red, 0.5)')\n makeSwatch('mix(red, 0.75)')\n makeSwatch('mix(teal, 0.25)')\n makeSwatch('mix(teal, 0.5)')\n makeSwatch('mix(teal, 0.75)')\n makeSwatch('colorMix(aliceblue, 0.25)')\n makeSwatch('colorMix(aliceblue, 0.5)')\n makeSwatch('colorMix(aliceblue, 0.75)')\n}\n\nfunction randomColor() {\n colorInput.value = Color.fromHsl(Math.random() * 360, Math.random(), Math.random() * 0.5 + 0.25)\n update()\n}\n\nrandomColor()\n\npreview.append(\n label(\n span('base color'),\n colorInput\n ),\n button(\n 'Random(ish) Color',\n {\n onClick: randomColor\n }\n ),\n swatches\n)\n```\n```css\n.preview .swatches {\n display: flex;\n gap: 4px;\n padding: 4px;\n flex-wrap: wrap;\n font-size: 80%;\n}\n.preview .swatch {\n display: inline-block;\n padding: 2px 6px;\n color: var(--text);\n background: var(--adjusted);\n border: 2px solid var(--original);\n}\n```\n\nEach of these methods creates a new color instance based on the existing color(s).\n\nIn each case `amount` is from 0 to 1, and `degrees` is an angle in degrees.\n\n- `brighten(amount: number)`\n- `darken(amount: number)`\n- `saturate(amount: number)`\n- `desaturate(amount: number)`\n- `rotate(angle: number)`\n- `opacity(amount: number)` — this just creates a color with that opacity (it doesn't adjust it)\n- `mix(otherColor: Color, amount)` — produces a mix of the two colors in HSL-space\n- `colorMix(otherColor: Color, amount)` — uses `color-mix(in hsl...)` to blend the colors\n- `blend(otherColor: Color, amount)` — produces a blend of the two colors in RGB-space (usually icky)\n- `contrasting(amount = 1)` — produces a **contrasting color** by blending the color with black (if its\n `brightness` is > 0.5) or white by `amount`. The new color will always have opacity 1.\n `contrasting()` produce nearly identical results to `contrast-color()`.\n\n> **Note** the captions in the example above are colored using `contrasting()` and thus\n> should always be readable. In general, a base color will produce the worst results when\n> its `brightness` is around 0.5, much as is the case with the new and experimental CSS\n> [contrast-color()](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/contrast-color)\n> function.\n>\n> **Also note** that highly translucent colors might produce disappointing `.contrasting()`\n> results since it's the blended color you need to worry about.\n\nWhere-ever possible, unless otherwise indicated, all of these operations are performed in HSL-space.\nHSL space is not great! For example, `desaturate` essentially blends you with medium gray (`#888`)\nrather than a BT.601 `brightness` value where \"yellow\" is really bright and \"blue\" is really dark.\n\nIf you want to desaturate colors more nicely, you can try blending them with their own `mono`.\n\n## Static Methods\n\nThese are alternatives to the standard `Color(r, g, b, a = 1)` constructor.\n\n`Color.fromVar(cssVariableName: string, element = document.body): Color` evaluates\nthe color at the specified element and then returns a `Color` instance with that\nvalue. It will accept both bare variable names (`--foo-bar`) and wrapped (`var(--foo-bar)`).\n\n`Color.fromCss(cssColor: string): Color` produces a `Color` instance from any\ncss color definition the browser can handle.\n\n`Color.fromHsl(h: number, s: number, l: number, a = 1)` produces a `Color`\ninstance from HSL/HSLA values. The HSL values are cached internally and\nused for internal calculations to reduce precision problems that occur\nwhen converting HSL to RGB and back. It's nowhere near as sophisticated as\nthe models used by (say) Adobe or Apple, but it's less bad than doing all\ncomputations in rgb.\n\n## Static Properties\n\n- `black`, `white` — handy constants\n\n## Properties\n\n- `r`, `g`, `b` are numbers from 0 to 255.\n- `a` is a number from 0 to 1\n\n## Properties (read-only)\n\n- `html` — the color in HTML `#rrggbb[aa]` format\n- `inverse` — the photonegative of the color (light is dark, orange is blue)\n- `opaque` - the color, but guaranteed opaque\n- `inverseLuminance` — inverts luminance but keeps hue, great for \"dark mode\"\n- `rgb` and `rgba` — the color in `rgb(...)` and `rgba(...)` formats.\n- `hsl` and `hsla` — the color in `hsl(...)` and `hsla(...)` formats.\n- `RGBA` and `ARGB` — return the values as arrays of numbers from 0 to 1 for use with\n [WebGL](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API) (for example).\n- `brightness` — this is the brightness of the color based on [BT.601](https://www.itu.int/rec/R-REC-BT.601)\n- `mono` — this produces a `Color` instance that a greyscale version (based on `brightness`)\n\n## Utilities\n\n- `swatch()` emits the color into the console with a swatch and returns the color for chaining.\n- `toString()` emits the `html` property\n*/\n\nimport { lerp, clamp } from './more-math'\nimport { getCssVar } from './get-css-var'\nimport { CSSSystemColor } from './css-system-color'\n\n// http://www.itu.int/rec/R-REC-BT.601\nconst bt601 = (r: number, g: number, b: number): number => {\n return (0.299 * r + 0.587 * g + 0.114 * b) / 255\n}\n\nconst hex2 = (n: number): string =>\n ('00' + Math.round(Number(n)).toString(16)).slice(-2)\n\nclass HslColor {\n h: number\n s: number\n l: number\n\n constructor(r: number, g: number, b: number) {\n r /= 255\n g /= 255\n b /= 255\n const l = Math.max(r, g, b)\n const s = l - Math.min(r, g, b)\n const h =\n s !== 0\n ? l === r\n ? (g - b) / s\n : l === g\n ? 2 + (b - r) / s\n : 4 + (r - g) / s\n : 0\n\n this.h = 60 * h < 0 ? 60 * h + 360 : 60 * h\n this.s = s !== 0 ? (l <= 0.5 ? s / (2 * l - s) : s / (2 - (2 * l - s))) : 0\n this.l = (2 * l - s) / 2\n }\n}\n\nconst span =\n globalThis.document !== undefined\n ? globalThis.document.createElement('span')\n : undefined\nexport class Color {\n r: number\n g: number\n b: number\n a: number\n\n static fromVar(varName: string, element = document.body): Color {\n return Color.fromCss(getCssVar(varName, element))\n }\n\n static fromCss(spec: CSSSystemColor | string): Color {\n let converted = spec\n if (span instanceof HTMLSpanElement) {\n span.style.color = 'black'\n span.style.color = spec\n document.body.appendChild(span)\n converted = getComputedStyle(span).color\n span.remove()\n }\n const [r, g, b, a] = (converted.match(/[\\d.]+/g) as string[]) || [\n '0',\n '0',\n '0',\n '0',\n ]\n const scale = converted.startsWith('color(srgb') ? 255 : 1\n return new Color(\n Number(r) * scale,\n Number(g) * scale,\n Number(b) * scale,\n a == null ? 1 : Number(a)\n )\n }\n\n static fromHsl(h: number, s: number, l: number, a = 1): Color {\n let r: number, g: number, b: number\n\n if (s === 0) {\n r = g = b = l // achromatic\n } else {\n const hue2rgb = (p: number, q: number, t: number): number => {\n if (t < 0) t += 1\n if (t > 1) t -= 1\n if (t < 1 / 6) return p + (q - p) * 6 * t\n if (t < 1 / 2) return q\n if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6\n return p\n }\n\n const q = l < 0.5 ? l * (1 + s) : l + s - l * s\n const p = 2 * l - q\n const hNormalized = (((h % 360) + 360) % 360) / 360\n r = hue2rgb(p, q, hNormalized + 1 / 3)\n g = hue2rgb(p, q, hNormalized)\n b = hue2rgb(p, q, hNormalized - 1 / 3)\n }\n\n const color = new Color(r * 255, g * 255, b * 255, a)\n // Cache the HSL value to avoid re-calculating it later\n color.hslCached = { h: ((h % 360) + 360) % 360, s, l }\n return color\n }\n\n static black = new Color(0, 0, 0)\n static white = new Color(255, 255, 255)\n\n constructor(r: number, g: number, b: number, a = 1) {\n this.r = clamp(0, r, 255)\n this.g = clamp(0, g, 255)\n this.b = clamp(0, b, 255)\n this.a = clamp(0, a, 1)\n }\n\n get inverse(): Color {\n return new Color(255 - this.r, 255 - this.g, 255 - this.b, this.a)\n }\n\n get inverseLuminance(): Color {\n const { h, s, l } = this._hsl\n return Color.fromHsl(h, s, 1 - l, this.a)\n }\n\n get opaque(): Color {\n return this.a === 1 ? this : new Color(this.r, this.g, this.b, 1)\n }\n\n contrasting(amount = 1): Color {\n return this.opaque.blend(\n this.brightness > 0.5 ? Color.black : Color.white,\n amount\n )\n }\n\n get rgb(): string {\n const { r, g, b } = this\n return `rgb(${r.toFixed(0)},${g.toFixed(0)},${b.toFixed(0)})`\n }\n\n get rgba(): string {\n const { r, g, b, a } = this\n return `rgba(${r.toFixed(0)},${g.toFixed(0)},${b.toFixed(0)},${a.toFixed(\n 2\n )})`\n }\n\n get RGBA(): number[] {\n return [this.r / 255, this.g / 255, this.b / 255, this.a]\n }\n\n get ARGB(): number[] {\n return [this.a, this.r / 255, this.g / 255, this.b / 255]\n }\n\n private hslCached?: HslColor\n\n get _hsl(): HslColor {\n if (this.hslCached == null) {\n this.hslCached = new HslColor(this.r, this.g, this.b)\n }\n return this.hslCached\n }\n\n get hsl(): string {\n const { h, s, l } = this._hsl\n return `hsl(${h.toFixed(0)}deg ${(s * 100).toFixed(0)}% ${(l * 100).toFixed(\n 0\n )}%)`\n }\n\n get hsla(): string {\n const { h, s, l } = this._hsl\n return `hsl(${h.toFixed(0)}deg ${(s * 100).toFixed(0)}% ${(l * 100).toFixed(\n 0\n )}% / ${(this.a * 100).toFixed(0)}%)`\n }\n\n get mono(): Color {\n const v = this.brightness * 255\n return new Color(v, v, v)\n }\n\n get brightness(): number {\n return bt601(this.r, this.g, this.b)\n }\n\n get html(): string {\n return this.toString()\n }\n\n toString(): string {\n return this.a === 1\n ? '#' + hex2(this.r) + hex2(this.g) + hex2(this.b)\n : '#' +\n hex2(this.r) +\n hex2(this.g) +\n hex2(this.b) +\n hex2(Math.floor(255 * this.a))\n }\n\n brighten(amount: number): Color {\n const { h, s, l } = this._hsl\n const lClamped = clamp(0, l + amount * (1 - l), 1)\n return Color.fromHsl(h, s, lClamped, this.a)\n }\n\n darken(amount: number): Color {\n const { h, s, l } = this._hsl\n const lClamped = clamp(0, l * (1 - amount), 1)\n return Color.fromHsl(h, s, lClamped, this.a)\n }\n\n saturate(amount: number): Color {\n const { h, s, l } = this._hsl\n const sClamped = clamp(0, s + amount * (1 - s), 1)\n return Color.fromHsl(h, sClamped, l, this.a)\n }\n\n desaturate(amount: number): Color {\n const { h, s, l } = this._hsl\n const sClamped = clamp(0, s * (1 - amount), 1)\n return Color.fromHsl(h, sClamped, l, this.a)\n }\n\n rotate(amount: number): Color {\n const { h, s, l } = this._hsl\n const hClamped = (h + 360 + amount) % 360\n return Color.fromHsl(hClamped, s, l, this.a)\n }\n\n opacity(alpha: number): Color {\n const { h, s, l } = this._hsl\n return Color.fromHsl(h, s, l, alpha)\n }\n\n swatch(): Color {\n console.log(\n `%c %c ${this.html}, ${this.rgba}`,\n `background-color: ${this.html}`,\n 'background-color: transparent'\n )\n return this\n }\n\n blend(otherColor: Color, t: number): Color {\n return new Color(\n lerp(this.r, otherColor.r, t),\n lerp(this.g, otherColor.g, t),\n lerp(this.b, otherColor.b, t),\n lerp(this.a, otherColor.a, t)\n )\n }\n\n static blendHue(a: number, b: number, t: number): number {\n const delta = (b - a + 720) % 360\n if (delta < 180) {\n return a + t * delta\n } else {\n return a - (360 - delta) * t\n }\n }\n\n mix(otherColor: Color, t: number): Color {\n const a = this._hsl\n const b = otherColor._hsl\n return Color.fromHsl(\n a.s === 0 ? b.h : b.s === 0 ? a.h : Color.blendHue(a.h, b.h, t),\n lerp(a.s, b.s, t),\n lerp(a.l, b.l, t),\n lerp(this.a, otherColor.a, t)\n )\n }\n\n colorMix(otherColor: Color, t: number): Color {\n return Color.fromCss(\n `color-mix(in hsl, ${this.html}, ${otherColor.html} ${(t * 100).toFixed(\n 0\n )}%)`\n )\n }\n}\n",
|
|
13
|
+
"/*#\n# 2.1 binding arrays\n\nThe most likely source of complexity and performance issues in applications is\ndisplaying large lists or grids of objects. `tosijs` provides robust support\nfor handling this efficiently.\n\n## `bindList` and `bindings.list`\n\nThe basic structure of a **list-binding** is:\n\n div( // container element\n {\n bindList: {\n value: boxed.path.to.array // OR 'path.to.array'\n idPath: 'id' // (optional) path to unique id of array items\n }\n },\n template( // template for the repeated item\n div( // repeated item should have a single root element\n ... // whatever you want\n span({\n bindText: '^.foo.bar' // binding to a given array member's `foo.bar`\n // '^' refers to the array item itself\n })\n )\n )\n )\n\n```js\n import { elements, tosi } from 'tosijs'\n const { listBindingExample } = tosi({\n listBindingExample: {\n array: ['this', 'is', 'an', 'example']\n }\n })\n\n const { h3, ul, li, template } = elements\n\n preview.append(\n h3('binding an array of strings'),\n ul(\n ...listBindingExample.array.tosiListBinding(({li}, item) => li(item))\n )\n )\n```\n\n### tosiListBinding(templateBuilder: ListTemplateBuilder, options?: ListBindingOptions) => [ElementProps, HTMLTemplateElement]\n\n type ListTemplateBuilder<U = any> = (elements: ElementsProxy, item: U) => HTMLElement\n type ListBinding = [ElementProps, HTMLTemplateElement]\n\nThe example leverages new syntax sugar that makes list-binding simpler\nand more intuitive. (It's intended to be as convenient as mapping an array to elements,\nexcept that you get dynamic binding, virtualized lists, versus a static list.)\n\nIf you have a BoxedProxy<T[]>, you can use `tosiListBinding()`\nto create the binding inline (see the example above). Under the hood, the template\ngets created and an object with the necessary specifications is produced.\n\nEven better, `templateBuilder()` is passed the `elements` proxy and a placeholder `BoxedProxy` of\nthe array's type, supporting autocompletion of property names within the template.\n\n### id-paths\n\n**id-paths** are a wrinkle in `xin`'s paths specifically there to make list-binding more efficient.\nThis is because in many cases you will encounter large arrays of objects, each with a unique id somewhere, e.g. it might be `id` or `uid`\nor even buried deeper…\n\n xin.message = [\n {\n id: '1234abcd',\n title: 'hello',\n body: 'hello there!'\n },\n …\n ]\n\nInstead of referring to the first item in `messages` as `messages[0]` it can be referred to\nas `messages[id=1234abcd]`, and this will retrieve the item regardless of its position in messages.\n\nSpecifying an `idPath` in a list-binding will allow the list to be more efficiently updated.\nIt's the equivalent of a `key` in React, the difference being that it's optional and\nspecifically intended to leverage pre-existing keys where available.\n\n## Virtualized Lists\n\nThe real power of `bindList` comes from its support for virtualizing lists.\n\n bindList: {\n value: emojiListExample.array,\n idPath: 'name',\n virtual: {\n height: 30,\n rowChunkSize: 3,\n },\n }\n\nSimply add a `virtual` property to the list-binding specifying a *minimum* `height` (and, optionally,\n`height`) and the list will be `virtualized` (meaning that only visible elements will be rendered,\nmissing elements being replaced by a single padding element above and below the list).\n\nYou can (optionally) specify `rowChunkSize` to virtualize the list in chunks of rows to allow\nconsistent `:nth-child()` styling.\n\nNow you can trivially bind an array of a million objects to the DOM and have it scroll at\n120fps.\n\n```js\nimport { elements, tosi } from 'tosijs'\nconst request = await fetch(\n 'https://raw.githubusercontent.com/tonioloewald/emoji-metadata/master/emoji-metadata.json'\n)\nconst { emojiListExample } = tosi({\n emojiListExample: {\n array: await request.json()\n }\n})\n\nconst { div } = elements\n\npreview.append(\n div(\n {\n class: 'emoji-table'\n },\n ...emojiListExample.array.tosiListBinding(({div, span}, item) =>\n div(\n {\n class: 'emoji-row',\n tabindex: 0,\n },\n span({ bindText: item.chars, class: 'graphic' }),\n span({ bindText: item.name, class: 'no-overflow' }),\n span({ bindText: item.category, class: 'no-overflow' }),\n span({ bindText: item.subcategory, class: 'no-overflow' })\n ),\n {\n value: emojiListExample.array,\n idPath: 'name',\n virtual: {\n height: 30,\n rowChunkSize: 3\n },\n }\n )\n )\n)\n```\n```css\n.emoji-table {\n height: 100%;\n overflow: auto;\n}\n.emoji-row {\n display: grid;\n grid-template-columns: 50px 300px 200px 200px;\n align-items: center;\n height: 30px;\n overflow-x: hidden;\n}\n.emoji-row:nth-child(3n) {\n background: #f002;\n}\n.emoji-row:nth-child(3n+2) {\n background: #00f2;\n}\n\n.emoji-row > .graphic {\n font-size: 20px;\n justify-self: center;\n}\n\n.emoji-row > * {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n```\n\n### Virtualized Grids\n\nYou can virtualize a grid by styling the padding elements (with class `.virtual-list-padding`)\nto have the correct column span. (You can also just specify a fixed `width` for your list.)\n\n```js\nimport { elements, tosi } from 'tosijs'\nconst { div, template } = elements\nconst list = []\nfor (let i = 0; i < 2000; i++) {\n list.push({id: i})\n}\n\nconst { bigBindTest } = tosi({\n bigBindTest: list\n})\n\npreview.append(\n div(\n {\n class: 'virtual-grid-example',\n },\n ...bigBindTest.tosiListBinding(\n ({div}, item) => div({\n class: 'cell',\n bindText: item.id\n }),\n {\n idPath: 'id',\n virtual: {\n height: 40,\n visibleColumns: 7,\n rowChunkSize: 2,\n }\n }\n )\n )\n)\n```\n```css\n.virtual-grid-example {\n height: 100%;\n width: 100%;\n overflow-y: auto;\n display: grid;\n grid-template-columns: 14% 14% 14% 14% 14% 14% 14%;\n}\n\n.virtual-grid-example .virtual-list-padding {\n grid-column: 1 / 8;\n}\n\n.virtual-grid-example .cell {\n height: 40px;\n line-height: 40px;\n text-align: center;\n}\n\n.virtual-grid-example .cell:nth-child(14n+2),\n.virtual-grid-example .cell:nth-child(14n+3),\n.virtual-grid-example .cell:nth-child(14n+4),\n.virtual-grid-example .cell:nth-child(14n+5),\n.virtual-grid-example .cell:nth-child(14n+6),\n.virtual-grid-example .cell:nth-child(14n+7),\n.virtual-grid-example .cell:nth-child(14n+8) {\n background: #0001;\n}\n```\n\n## Filtered Lists\n\nIt's also extremely common to want to filter a rendered list, and `tosijs`\nprovides both simple and powerful methods for doing this.\n\n## `hiddenProp` and `visibleProp`\n\n`hiddenProp` and `visibleProp` allow you to use a property to hide or show array\nelements (and they can be `symbol` values if you want to avoid \"polluting\"\nyour data, e.g. for round-tripping to a database.)\n\n## `filter` and `needle`\n\n bindList: {\n value: filterListExample.array,\n idPath: 'name',\n virtual: {\n height: 30,\n },\n filter: (emojis, needle) => {\n needle = needle.trim().toLocaleLowerCase()\n if (!needle) {\n return emojis\n }\n return emojis.filter(emoji => `${emoji.name} ${emoji.category} ${emoji.subcategory}`.toLocaleLowerCase().includes(needle))\n },\n needle: filterListExample.needle\n }\n\nIf `bindList`'s options provide a `filter` function and a `needle` (proxy or path) then\nthe list will be filtered using the function via throttled updates.\n\n`filter` is passed the whole array, and `needle` can be anything so, `filter` can\nsort the array or even synthesize it entirely.\n\nIn this example the `needle` is an object containing both a `needle` string and `sort`\nvalue, and the `filter` function filters the list if the string is non-empty, and\nsorts the list if `sort` is not \"default\". Also note that an `input` event handler\nis used to `touch` the object and trigger updates.\n\n```js\n// note that this example is styled by the earlier example\n\nimport { elements, tosi } from 'tosijs'\nconst request = await fetch(\n 'https://raw.githubusercontent.com/tonioloewald/emoji-metadata/master/emoji-metadata.json'\n)\nconst { filterListExample } = tosi({\n filterListExample: {\n config: {\n needle: '',\n sort: 'default',\n },\n array: await request.json()\n }\n})\n\nconst { b, div, span, template, label, input, select, option } = elements\n\npreview.append(\n div(\n {\n style: {\n display: 'flex',\n padding: 10,\n gap: 10,\n height: 60,\n alignItems: 'center'\n },\n onInput() {\n // need to trigger change if any prop of config changes\n touch(filterListExample.config)\n },\n },\n b('filtered list'),\n span({style: 'flex: 1'}),\n label(\n span('sort by'),\n select(\n {\n bindValue: filterListExample.config.sort\n },\n option('default'),\n option('name'),\n option('category')\n ),\n ),\n input({\n type: 'search',\n placeholder: 'filter emoji',\n bindValue: filterListExample.config.needle\n })\n ),\n div(\n {\n class: 'emoji-table',\n style: 'height: calc(100% - 60px)',\n },\n ...filterListExample.array.tosiListBinding(\n ({div, span}, item) => div(\n {\n class: 'emoji-row',\n tabindex: 0,\n },\n span({ bindText: item.chars, class: 'graphic' }),\n span({ bindText: item.name, class: 'no-overflow' }),\n span({ bindText: item.category, class: 'no-overflow' }),\n span({ bindText: item.subcategory, class: 'no-overflow' })\n ),\n {\n idPath: 'name',\n virtual: {\n height: 30,\n rowChunkSize: 3,\n },\n filter: (emojis, config) => {\n let { needle, sort } = config\n needle = needle.trim().toLocaleLowerCase()\n if (needle) {\n emojis = emojis.filter(emoji => `${emoji.name} ${emoji.category} ${emoji.subcategory}`.toLocaleLowerCase().includes(needle))\n }\n return config.sort === 'default' ? emojis : emojis.sort((a, b) => a[config.sort] > b[config.sort] ? 1 : -1)\n },\n needle: filterListExample.config\n }\n )\n )\n)\n```\n\n## List Utilities\n\nSuppose you have used the a list binding to bind an array of objects\nto a `<ul>`. So the DOM hierarchy looks something like this:\n\n <ul> <-- array is bound to this element\n <template>\n <li>\n <span>...</span>\n </li>\n </template>\n <li> <-- bound to array[0]\n <span>...</span>\n </li>\n <li> <-- bound to array[1]\n <span>...</span>\n </li>\n ...\n </ul>\n\n### `getListBinding(element: Element): ListBinding | undefined`\n\nThis gets the ListBinding object managing the bound list contained on the provided\nelement (if any). In the example above, you could call it on the `<ul>` and you'd\nget back a `ListBinding` instance that contains all kinds of juicy information.\n\n### `getListItem(element: Element): any`\n\nGets you the array item bound to the list instance containing the element (if any).\n\nYou could call this on an `<li>` element or a any element inside it and get back\nthe array item bound to the `<li>`.\n\n### `getListInstance(element: Element): { element: Element, item: any } | undefined`\n\nThis returns both the root element bound to the array item, and the array item itself.\n\nAgain, you could call this on an `<li>` or its contents.\n\n### `deleteListItem(element: Element): boolean`\n\nIf the element is part of a list instance bound to an array, this splices bound item out of the array\n(and updates the rendered list).\n\nIf you call this on an `<li>` or something inside it, this will splice the bound\narray item out of the array and then triggers an update the bound list.\n\n> `deleteListItem()` requires that the list binding specifies\n> a valid `idPath`, or it will throw an error (and fail).\n*/\nimport { settings } from './settings'\nimport { resizeObserver } from './dom'\nimport { throttle } from './throttle'\nimport { xin } from './xin'\nimport {\n cloneWithBindings,\n elementToBindings,\n BOUND_SELECTOR,\n DataBinding,\n xinValue,\n xinPath,\n LIST_BINDING_REF,\n LIST_INSTANCE_REF,\n} from './metadata'\nimport { XinObject, XinTouchableType, ListBindingOptions } from './xin-types'\nimport { Listener } from './path-listener'\n\nconst SLICE_INTERVAL_MS = 16 // 60fps\nconst FILTER_INTERVAL_MS = 100 // 10fps\n\ninterface VirtualListSlice {\n items: any[]\n firstItem: number\n lastItem: number\n topBuffer: number\n bottomBuffer: number\n}\n\nfunction updateRelativeBindings(element: Element, path: string): void {\n const boundElements = Array.from(element.querySelectorAll(BOUND_SELECTOR))\n if (element.matches(BOUND_SELECTOR)) {\n boundElements.unshift(element)\n }\n for (const boundElement of boundElements) {\n const bindings = elementToBindings.get(boundElement) as DataBinding[]\n for (const binding of bindings) {\n if (binding.path.startsWith('^')) {\n binding.path = `${path}${binding.path.substring(1)}`\n }\n if (binding.binding.toDOM != null) {\n binding.binding.toDOM(boundElement as Element, xin[binding.path])\n }\n }\n }\n}\n\nexport class ListBinding {\n boundElement: Element\n listTop: HTMLElement\n listBottom: HTMLElement\n template: Element\n options: ListBindingOptions\n itemToElement: WeakMap<XinObject, Element>\n array: any[] = []\n private readonly _update?: VoidFunction\n private _previousSlice?: VirtualListSlice\n static filterBoundObservers = new WeakMap<Element, Listener>()\n\n constructor(\n boundElement: Element,\n value: any[],\n options: ListBindingOptions = {}\n ) {\n this.boundElement = boundElement\n this.itemToElement = new WeakMap()\n if (boundElement.children.length !== 1) {\n throw new Error(\n 'ListBinding expects an element with exactly one child element'\n )\n }\n if (boundElement.children[0] instanceof HTMLTemplateElement) {\n const template = boundElement.children[0]\n if (template.content.children.length !== 1) {\n throw new Error(\n 'ListBinding expects a template with exactly one child element'\n )\n }\n this.template = cloneWithBindings(\n template.content.children[0]\n ) as HTMLElement\n } else {\n this.template = boundElement.children[0] as HTMLElement\n this.template.remove()\n }\n this.options = options\n this.listTop = document.createElement('div')\n this.listBottom = document.createElement('div')\n this.listTop.classList.add('virtual-list-padding')\n this.listBottom.classList.add('virtual-list-padding')\n this.boundElement.append(this.listTop)\n this.boundElement.append(this.listBottom)\n if (options.virtual != null) {\n resizeObserver.observe(this.boundElement)\n this._update = throttle(() => {\n this.update(this.array, true)\n }, SLICE_INTERVAL_MS)\n this.boundElement.addEventListener('scroll', this._update)\n this.boundElement.addEventListener('resize', this._update)\n }\n }\n\n private visibleSlice(): VirtualListSlice {\n const { virtual, hiddenProp, visibleProp } = this.options\n let visibleArray = this.array\n if (hiddenProp !== undefined) {\n visibleArray = visibleArray.filter((item) => item[hiddenProp] !== true)\n }\n if (visibleProp !== undefined) {\n visibleArray = visibleArray.filter((item) => item[visibleProp] === true)\n }\n if (this.options.filter && this.needle !== undefined) {\n visibleArray = this.options.filter(visibleArray, this.needle)\n }\n let firstItem = 0\n let lastItem = visibleArray.length - 1\n let topBuffer = 0\n let bottomBuffer = 0\n\n if (virtual != null && this.boundElement instanceof HTMLElement) {\n const width = this.boundElement.offsetWidth\n const height = this.boundElement.offsetHeight\n\n if (virtual.visibleColumns == null) {\n virtual.visibleColumns =\n virtual.width != null\n ? Math.max(1, Math.floor(width / virtual.width))\n : 1\n }\n const visibleRows =\n Math.ceil(height / virtual.height) + (virtual.rowChunkSize || 1)\n const totalRows = Math.ceil(visibleArray.length / virtual.visibleColumns)\n const visibleItems = virtual.visibleColumns * visibleRows\n let topRow = Math.floor(this.boundElement.scrollTop / virtual.height)\n if (topRow > totalRows - visibleRows + 1) {\n topRow = Math.max(0, totalRows - visibleRows + 1)\n }\n if (virtual.rowChunkSize) {\n topRow -= topRow % virtual.rowChunkSize\n }\n\n firstItem = topRow * virtual.visibleColumns\n lastItem = firstItem + visibleItems - 1\n\n topBuffer = topRow * virtual.height\n bottomBuffer = Math.max(\n (totalRows - visibleRows) * virtual.height - topBuffer,\n 0\n )\n }\n\n return {\n items: visibleArray,\n firstItem,\n lastItem,\n topBuffer,\n bottomBuffer,\n }\n }\n\n private needle?: any\n filter = throttle((needle: any) => {\n if (this.needle !== needle) {\n this.needle = needle\n this.update(this.array)\n }\n }, FILTER_INTERVAL_MS)\n\n update(array?: any[], isSlice?: boolean) {\n if (array == null) {\n array = []\n }\n this.array = array\n\n const { hiddenProp, visibleProp } = this.options\n const arrayPath: string = xinPath(array) as string\n\n const slice = this.visibleSlice()\n this.boundElement.classList.toggle(\n '-xin-empty-list',\n slice.items.length === 0\n )\n const previousSlice = this._previousSlice\n const { firstItem, lastItem, topBuffer, bottomBuffer } = slice\n if (\n hiddenProp === undefined &&\n visibleProp === undefined &&\n isSlice === true &&\n previousSlice != null &&\n firstItem === previousSlice.firstItem &&\n lastItem === previousSlice.lastItem\n ) {\n return\n }\n this._previousSlice = slice\n\n let removed = 0\n let moved = 0\n let created = 0\n\n for (const element of Array.from(this.boundElement.children)) {\n if (element === this.listTop || element === this.listBottom) {\n continue\n }\n // @ts-ignore-error if it's there it's there\n const proxy = element[LIST_INSTANCE_REF]\n if (proxy == null) {\n element.remove()\n } else {\n const idx = slice.items.indexOf(proxy)\n if (idx < firstItem || idx > lastItem) {\n element.remove()\n this.itemToElement.delete(proxy)\n removed++\n }\n }\n }\n\n this.listTop.style.height = String(topBuffer) + 'px'\n this.listBottom.style.height = String(bottomBuffer) + 'px'\n\n // build a complete new set of elements in the right order\n const elements: Element[] = []\n const { idPath } = this.options\n for (let i = firstItem; i <= lastItem; i++) {\n const item = slice.items[i]\n if (item === undefined) {\n continue\n }\n let element = this.itemToElement.get(xinValue(item))\n if (element == null) {\n created++\n element = cloneWithBindings(this.template) as HTMLElement\n if (typeof item === 'object') {\n this.itemToElement.set(xinValue(item), element)\n // @ts-ignore-error if it's there it's there\n element[LIST_INSTANCE_REF] = xinValue(item)\n }\n this.boundElement.insertBefore(element, this.listBottom)\n if (idPath != null) {\n const idValue = item[idPath] as string\n const itemPath = `${arrayPath}[${idPath}=${idValue}]`\n updateRelativeBindings(element, itemPath)\n } else {\n const itemPath = `${arrayPath}[${i}]`\n updateRelativeBindings(element, itemPath)\n }\n }\n elements.push(element)\n }\n\n // make sure all the elements are in the DOM and in the correct location\n let insertionPoint: Element | null = null\n for (const element of elements) {\n if (element.previousElementSibling !== insertionPoint) {\n moved++\n if (insertionPoint?.nextElementSibling != null) {\n this.boundElement.insertBefore(\n element,\n insertionPoint.nextElementSibling\n )\n } else {\n this.boundElement.insertBefore(element, this.listBottom)\n }\n }\n insertionPoint = element\n }\n\n if (settings.perf) {\n console.log(arrayPath, 'updated', { removed, created, moved })\n }\n }\n}\n\ninterface ListBoundElement extends Element {\n [LIST_BINDING_REF]?: ListBinding\n}\n\nexport const getListBinding = (\n boundElement: ListBoundElement,\n value?: any[],\n options?: ListBindingOptions\n): ListBinding | undefined => {\n let listBinding = boundElement[LIST_BINDING_REF]\n if (value && listBinding === undefined) {\n listBinding = new ListBinding(boundElement, value, options)\n boundElement[LIST_BINDING_REF] = listBinding\n }\n return listBinding\n}\n\ntype PossiblyBoundElement = Element & {\n [LIST_BINDING_REF]?: ListBinding\n [LIST_INSTANCE_REF]?: ListBinding\n}\n\nexport const getListInstance = (\n element: Element\n): { element: Element; item: any } | undefined => {\n let item: any\n while (\n !(item = (element as PossiblyBoundElement)[LIST_INSTANCE_REF]) &&\n element &&\n element.parentElement\n ) {\n element = element.parentElement\n }\n return item ? { element, item } : undefined\n}\n\nexport const getListItem = (element: Element): any => {\n const instance = getListInstance(element)\n return instance ? instance.item : undefined\n}\n\nexport const deleteListItem = (element: Element): boolean => {\n const instance = getListInstance(element)\n if (!instance) {\n console.error(\n 'deleteListItem failed, element is not part of a list instance',\n element\n )\n return false\n }\n const binding = getListBinding(instance.element.parentElement!)!\n if (!binding.options.idPath) {\n console.error(\n 'deleteListItem failed, list binding has no idPath',\n element.parentElement,\n binding\n )\n return false\n }\n const index = binding.array.indexOf(instance.item)\n if (index > -1) {\n binding.array.splice(index, 1)\n return true\n }\n return false\n}\n",
|
|
14
|
+
"/*#\n# 2.2 bindings\n\n`bindings` is simply a collection of common bindings.\n\nYou can create your own bindings easily enough (and add them to `bindings` if so desired).\n\nA `binding` looks like this:\n\n```\ninterface XinBinding {\n toDOM?: (element: HTMLElement, value: any, options?: XinObject) => void\n fromDOM?: (element: HTMLElement) => any\n}\n```\n\nThe `fromDOM` function is only needed for bindings to elements that trigger `change` or `input`\nevents, typically `<input>`, `<textarea>`, and `<select>` elements, and of course your\nown [Custom Elements](/?components.ts).\n\n## value\n\nThe `value` binding syncs state from `xin` to the bound element's `value` property. In\ngeneral this should only be used for binding simple things, like `<input>` and `<textarea>`\nelements.\n\n## text\n\nThe `text` binding copies state from `xin` to the bound element's `textContent` property.\n\n## enabled & disabled\n\nThe `enabled` and `disabled` bindings allow you to make a widget's enabled status\nbe determined by the truthiness of something in `xin`, e.g.\n\n```\nimport { xinProxy, elements } from 'xinjs'\n\nconst myDoc = xinProxy({\n myDoc: {\n content: ''\n unsavedChanges: false\n }\n}, 1)\n\n// this button will only be enabled if there is something in `myList.array`\ndocument.body.append(\n elements.textarea({\n bindValue: myDoc.content,\n onInput() {\n myDoc.unsavedChanges = true\n }\n }),\n elements.button(\n 'Save Changes',\n {\n bindEnabled: myDoc.unsavedChanges,\n onClick() {\n // save the doc\n myDoc.unsavedChanges = false\n }\n }\n )\n)\n```\n\n## list\n\nThe `list` binding makes a copy of a `template` element inside the bound element\nfor every item in the bound `Array`.\n\nIt uses the existing **single** child element it finds inside the bound element\nas its `template`. If the child is a `<template>` (which is a good idea) then it\nexpects that `template` to have a *single child element*.\n\nE.g. if you have a simple unordered list:\n\n <ul>\n <li></li>\n </ul>\n\nYou can bind an array to the `<ul>` and it will make a copy of the `<li>` inside\nfor each item in the source array.\n\nThe `list` binding accepts as options:\n- `idPath: string`\n- `initInstance: (element, item: any) => void`\n- `updateInstance: (element, item: any) => void`\n- `virtual: {width?: number, height: number}`\n- `hiddenProp: symbol | string`\n- `visibleProp: symbol | string`\n\n`initInstance` is called once for each element created, and is passed\nthat element and the array value that it represents.\n\nMeanwhile, `updateInstance` is called once on creation and then any time the\narray value is updated.\n\n### Virtual List Binding\n\nIf you want to bind large arrays with minimal performance impact, you can make a list\nbinding `virtual` by passing the `height` (and optionally `width`) of an item.\nOnly visible elements will be rendered. Just make sure the values passed represent\nthe *minimum* dimensions of the individual rendered items if they can vary in size.\n\n### Filtered Lists and Detail Views\n\nYou can **filter** the elements you wish to display in a bound list by using the\n`hiddenProp` (to hide elements of the list) and/or `visibleProp` (to show elements\nof the list).\n\nYou can pass a `path` or a `symbol` as either the `hiddenProp` or `visibleProp`.\n\nTypically, you can use `hiddenProp` to power filters and `visibleProp` to power\ndetail views. The beauty of using symbols is that it won't impact the serialized\nvalues of the array and different views of the array can use different selection\nand filtering criteria.\n\n> **Note** for a given list-binding, if you specify `hiddenProp` (but not `visibleProp`),\n> then all items in the array will be shown *unless* `item[hiddenProp] === true`.\n>\n> Conversely, if you specify `visibleProp` (but not `hiddenProp`), then all items\n> in the array will be ignored *unless* `item[visibleProp] === true`.\n>\n> If, for some reason, you specify both then an item will only be visible if\n> it `item[visibleProp] === true` and `item[hiddenProp] !== true`.\n\n### Binding custom-elements using idPath\n\nIf you list-bind a custom-element with `bindValue` implemented and providing an\n`idPath` then the list-binding will bind the array items to the value of the\ncustom-element.\n\n### xin-empty-list class\n\nThe `list` binding will automatically add the class `-xin-empty-list` to a\ncontainer bound to an empty array, making it easier to conditionally render\ninstructions or explanations when a list is empty.\n*/\nimport { XinObject, XinBinding, ValueElement } from './xin-types'\nimport { getListBinding } from './list-binding'\nimport { getValue, setValue } from './dom'\n\nexport const bindings: { [key: string | symbol]: XinBinding<Element> } = {\n value: {\n toDOM: setValue,\n\n fromDOM(element: Element) {\n return getValue(element as ValueElement)\n },\n },\n\n text: {\n toDOM(element: Element, value: any) {\n element.textContent = value\n },\n },\n\n enabled: {\n toDOM(element: Element, value: any) {\n ;(element as HTMLInputElement).disabled = !value\n },\n },\n\n disabled: {\n toDOM(element: Element, value: any) {\n ;(element as HTMLButtonElement).disabled = Boolean(value)\n },\n },\n\n list: {\n toDOM(element: Element, value: any[], options?: XinObject) {\n const listBinding = getListBinding(element, value, options)!\n listBinding.update(value)\n },\n },\n}\n",
|
|
20
15
|
"export function camelToKabob(s: string): string {\n return s.replace(/[A-Z]/g, (c: string): string => {\n return `-${c.toLocaleLowerCase()}`\n })\n}\n\nexport function kabobToCamel(s: string): string {\n return s.replace(/-([a-z])/g, (_: string, c: string): string => {\n return c.toLocaleUpperCase()\n })\n}\n",
|
|
21
|
-
"/*#\n# 3. elements\n\n`xinjs` provides `elements` for easily and efficiently generating DOM elements\nwithout using `innerHTML` or other unsafe methods.\n\n```js\nimport { elements } from 'tosijs'\n\nconst { div, input, label, span } = elements\n\npreview.append(\n div(\n {\n style: {\n display: 'flex',\n flexDirection: 'column',\n padding: 10,\n gap: 10\n }\n },\n label(\n {\n style: {\n display: 'inline-flex'\n }\n },\n span('text'),\n input({value: 'hello world', placeholder: 'type something'})\n ),\n label(\n {\n style: {\n display: 'inline-flex'\n }\n },\n span('checkbox'),\n input({type: 'checkbox', checked: true})\n )\n )\n)\n```\n\n## `ElementCreator` functions\n\n`elements` is a proxy whose properties are element factory functions,\nreferred to throughout this documentation as `elementCreator`s, functions\nof type `ElementCreator`. So `elements.div` is a function that returns a `<div>`\nelement, `elements.foo` creates <foo> elements, and elements.fooBar creates\n`<foo-bar>` elements.\n\nThe arguments of `elementCreator`s can be strings, numbers, other\nelements, or property-maps, which are converted into attributes or properties\n(or bindings).\n\nE.g.\n\n```js\nimport { elements, tosi } from 'tosijs'\n\nconst { elementCreatorDemo } = tosi({\n elementCreatorDemo: {\n isChecked: true,\n someString: 'hello elementCreator',\n someColor: 'blue',\n clicks: 0\n }\n})\n\nconst { div, button, label, input } = elements\n\npreview.append(\n div('I am a div'),\n div(\n {\n style: { color: 'blue' }\n },\n elementCreatorDemo.someString\n ),\n label(\n 'Edit someString',\n input({bindValue: elementCreatorDemo.someString})\n ),\n div(\n button(\n 'Click me',\n {\n onClick() {\n elementCreatorDemo.clicks += 1\n }\n }\n ),\n div(elementCreatorDemo.clicks, ' clicks so far'),\n ),\n label(\n 'isChecked?',\n input({type: 'checkbox', bindValue: elementCreatorDemo.isChecked})\n )\n)\n```\n\n## camelCase conversion\n\nAttributes in camelCase, e.g. `dataInfo`, will be converted to kebab-case,\nso:\n\n span({dataInfo: 'foo'}) // produces <span data-info=\"foo\"></span>\n\n## style properties\n\n`style` properties can be objects, and these are used to modify the\nelement's `style` object (while a string property will just change the\nelement's `style` attribute, eliminating previous changes).\n\n span({style: 'border: 1px solid red'}, {style: 'font-size: 15px'})\n\n…produces `<span style=\"font-size: 15px\"></span>`, which is probably\nnot what was wanted.\n\n span({style: {border: '1px solid red'}, {style: {fontSize: '15px'}}})\n\n…produces `<span style=\"border: 1px solid red; fon-size: 15px></span>`\nwhich is probably what was wanted.\n\n## event handlers\n\nProperties starting with `on` (followed by an uppercase letter)\nwill be converted into event-handlers, so `onMouseup` will be\nturned into a `mouseup` listener.\n\n## binding\n\nYou can [bind](/?bind.ts) an element to state using [bindings](/?bindings.ts)\nusing convenient properties, e.g.\n\n import { elements } from 'tosijs'\n const {div} = elements\n div({ bindValue: 'app.title' })\n\n…is syntax sugar for:\n\n import { elements, bind, bindings } from 'tosijs'\n const { div } = elements\n bind( div(), 'app.title', bindings.value )\n\nIf you want to use your own bindings, you can use `apply`:\n\n const visibleBinding = {\n toDOM(element, value) {\n element.classList.toggle('hidden', !value)\n }\n }\n\n div({ apply(elt){\n bind(elt, 'app.prefs.isVisible', visibleBinding})\n } })\n\n## event-handlers\n\nYou can attach event handlers to elements using `on<EventType>`\nas syntax sugar, e.g.\n\n import { elements } from 'tosijs'\n const { button } = elements\n document.body.append(\n button('click me', {onClick() {\n alert('clicked!')\n }})\n )\n\n…is syntax sugar for:\n\n import { elements, on } from 'tosijs'\n const { button } = elements\n const aButton = button('click me')\n on(aButton, 'click', () => {\n alert('clicked!')\n })\n document.body.append(\n aButton\n )\n\nThere are some subtle but important differences between `on()` and\n`addEventListener` which are discussed in detail in the section on\n[bind](/?bind.ts).\n\n## apply\n\nA property named `apply` is assumed to be a function that will be called\non the element.\n\n span({\n apply(element){ element.textContent = 'foobar'}\n })\n\n…produces `<span>foobar</span>`.\n\n## fragment\n\n`elements.fragment` is produces `DocumentFragment`s, but is otherwise\njust like other element factory functions.\n\n## svgElements\n\n`svgElements` is a proxy just like `elements` but it produces **SVG** elements in\nthe appropriate namespace.\n\n## mathML\n\n`mathML` is a proxy just like `elements` but it products **MathML** elements in\nthe appropriate namespace.\n\n> ### Caution\n>\n> Both `svgElements` and `mathML` are experimental and do not have anything like the\n> degree of testing behind them as `elements`. In particular, the properties of\n> SVG elements (and possible MathML elements) are quite different from ordinary\n> elements, so the underlying `ElementCreator` will never try to set properties\n> directly and will always use `setAttribute(...)`.\n>\n> E.g. `svgElements.svg({viewBox: '0 0 100 100'})` will call `setAttribute()` and\n> not set the property directly, because the `viewBox` property is… weird, but\n> setting the attribute works.\n>\n> Again, use with caution!\n*/\n\nimport { bind, on } from './bind'\nimport { bindings } from './bindings'\nimport {\n ElementPart,\n ElementProps,\n ElementCreator,\n StringMap,\n XinBinding,\n EventType,\n} from './xin-types'\nimport { camelToKabob } from './string-case'\nimport { processProp } from './css'\nimport { xinPath } from './metadata'\n\nconst MATH = 'http://www.w3.org/1998/Math/MathML'\nconst SVG = 'http://www.w3.org/2000/svg'\nexport interface ElementsProxy {\n a: ElementCreator<HTMLAnchorElement>\n abbr: ElementCreator\n acronym: ElementCreator\n address: ElementCreator\n area: ElementCreator<HTMLAreaElement>\n article: ElementCreator\n aside: ElementCreator\n audio: ElementCreator<HTMLAudioElement>\n b: ElementCreator\n base: ElementCreator<HTMLBaseElement>\n basefont: ElementCreator\n bdi: ElementCreator\n bdo: ElementCreator\n big: ElementCreator\n blockquote: ElementCreator<HTMLQuoteElement>\n body: ElementCreator<HTMLBodyElement>\n br: ElementCreator<HTMLBRElement>\n button: ElementCreator<HTMLButtonElement>\n canvas: ElementCreator<HTMLCanvasElement>\n caption: ElementCreator\n center: ElementCreator\n cite: ElementCreator\n code: ElementCreator\n col: ElementCreator<HTMLTableColElement>\n colgroup: ElementCreator<HTMLTableColElement>\n data: ElementCreator<HTMLDataElement>\n datalist: ElementCreator<HTMLDataListElement>\n dd: ElementCreator\n del: ElementCreator\n details: ElementCreator<HTMLDetailsElement>\n dfn: ElementCreator\n dialog: ElementCreator<HTMLDialogElement>\n div: ElementCreator<HTMLDivElement>\n dl: ElementCreator\n dt: ElementCreator\n em: ElementCreator\n embed: ElementCreator<HTMLEmbedElement>\n fieldset: ElementCreator<HTMLFieldSetElement>\n figcaption: ElementCreator\n figure: ElementCreator\n font: ElementCreator\n footer: ElementCreator\n form: ElementCreator<HTMLFormElement>\n frame: ElementCreator\n frameset: ElementCreator\n head: ElementCreator<HTMLHeadElement>\n header: ElementCreator\n hgroup: ElementCreator\n h1: ElementCreator<HTMLHeadingElement>\n h2: ElementCreator<HTMLHeadingElement>\n h3: ElementCreator<HTMLHeadingElement>\n h4: ElementCreator<HTMLHeadingElement>\n h5: ElementCreator<HTMLHeadingElement>\n h6: ElementCreator<HTMLHeadingElement>\n hr: ElementCreator<HTMLHRElement>\n html: ElementCreator<HTMLHtmlElement>\n i: ElementCreator\n iframe: ElementCreator<HTMLIFrameElement>\n img: ElementCreator<HTMLImageElement>\n input: ElementCreator<HTMLInputElement>\n ins: ElementCreator<HTMLModElement>\n kbd: ElementCreator\n keygen: ElementCreator<HTMLUnknownElement>\n label: ElementCreator<HTMLLabelElement>\n legend: ElementCreator<HTMLLegendElement>\n li: ElementCreator<HTMLLIElement>\n link: ElementCreator<HTMLLinkElement>\n main: ElementCreator\n map: ElementCreator<HTMLMapElement>\n mark: ElementCreator\n menu: ElementCreator<HTMLMenuElement>\n menuitem: ElementCreator<HTMLUnknownElement>\n meta: ElementCreator<HTMLMetaElement>\n meter: ElementCreator<HTMLMeterElement>\n nav: ElementCreator\n noframes: ElementCreator\n noscript: ElementCreator\n object: ElementCreator<HTMLObjectElement>\n ol: ElementCreator<HTMLOListElement>\n optgroup: ElementCreator<HTMLOptGroupElement>\n option: ElementCreator<HTMLOptionElement>\n output: ElementCreator<HTMLOutputElement>\n p: ElementCreator<HTMLParagraphElement>\n param: ElementCreator\n picture: ElementCreator<HTMLPictureElement>\n pre: ElementCreator<HTMLPreElement>\n progress: ElementCreator<HTMLProgressElement>\n q: ElementCreator<HTMLQuoteElement>\n rp: ElementCreator\n rt: ElementCreator\n ruby: ElementCreator\n s: ElementCreator\n samp: ElementCreator\n script: ElementCreator<HTMLScriptElement>\n section: ElementCreator\n select: ElementCreator<HTMLSelectElement>\n slot: ElementCreator<HTMLSlotElement>\n small: ElementCreator\n source: ElementCreator<HTMLSourceElement>\n span: ElementCreator<HTMLSpanElement>\n strike: ElementCreator\n strong: ElementCreator\n style: ElementCreator<HTMLStyleElement>\n sub: ElementCreator\n summary: ElementCreator\n table: ElementCreator<HTMLTableElement>\n tbody: ElementCreator<HTMLTableSectionElement>\n td: ElementCreator<HTMLTableCellElement>\n template: ElementCreator<HTMLTemplateElement>\n textarea: ElementCreator<HTMLTextAreaElement>\n tfoot: ElementCreator<HTMLTableSectionElement>\n th: ElementCreator<HTMLTableCellElement>\n thead: ElementCreator<HTMLTableSectionElement>\n time: ElementCreator<HTMLTimeElement>\n title: ElementCreator<HTMLTitleElement>\n tr: ElementCreator<HTMLTableRowElement>\n track: ElementCreator<HTMLTrackElement>\n tt: ElementCreator\n u: ElementCreator\n ul: ElementCreator<HTMLUListElement>\n var: ElementCreator\n video: ElementCreator<HTMLVideoElement>\n wbr: ElementCreator\n [key: string | symbol]: ElementCreator<any>\n}\n\nconst templates: { [key: string]: Element } = {}\n\nconst elementStyle = (elt: HTMLElement, prop: string, value: any) => {\n const processed = processProp(camelToKabob(prop), value)\n if (processed.prop.startsWith('--')) {\n elt.style.setProperty(processed.prop, processed.value)\n } else {\n ;(elt.style as unknown as { [key: string]: string })[prop] = processed.value\n }\n}\n\nconst elementStyleBinding = (prop: string): XinBinding => {\n return {\n toDOM(element, value) {\n elementStyle(element as HTMLElement, prop, value)\n },\n }\n}\n\nconst elementProp = (elt: HTMLElement, key: string, value: any) => {\n if (key === 'style') {\n if (typeof value === 'object') {\n for (const prop of Object.keys(value)) {\n if (xinPath(value[prop])) {\n bind(elt, value[prop], elementStyleBinding(prop))\n } else {\n elementStyle(elt, prop, value[prop])\n }\n }\n } else {\n elt.setAttribute('style', value)\n }\n } else if ((elt as { [key: string]: any })[key] !== undefined) {\n // MathML is only supported on 91% of browsers, and not on the Raspberry Pi Chromium\n const { MathMLElement } = globalThis\n if (\n elt instanceof SVGElement ||\n (MathMLElement !== undefined && elt instanceof MathMLElement)\n ) {\n elt.setAttribute(key, value)\n } else {\n ;(elt as { [key: string]: any })[key] = value\n }\n } else {\n const attr = camelToKabob(key)\n\n if (attr === 'class') {\n value.split(' ').forEach((className: string) => {\n elt.classList.add(className)\n })\n } else if ((elt as { [key: string]: any })[attr] !== undefined) {\n ;(elt as StringMap)[attr] = value\n } else if (typeof value === 'boolean') {\n value ? elt.setAttribute(attr, '') : elt.removeAttribute(attr)\n } else {\n elt.setAttribute(attr, value)\n }\n }\n}\n\nconst elementPropBinding = (key: string): XinBinding => {\n return {\n toDOM(element, value) {\n elementProp(element as HTMLElement, key, value)\n },\n }\n}\n\nconst elementSet = (elt: HTMLElement, key: string, value: any) => {\n if (key === 'apply') {\n value(elt)\n } else if (key.match(/^on[A-Z]/) != null) {\n const eventType = key.substring(2).toLowerCase()\n on(elt, eventType as EventType, value)\n } else if (key === 'bind') {\n const binding =\n typeof value.binding === 'string'\n ? bindings[value.binding]\n : value.binding\n if (binding !== undefined && value.value !== undefined) {\n bind(\n elt,\n value.value,\n value.binding instanceof Function\n ? { toDOM: value.binding }\n : value.binding\n )\n } else {\n throw new Error(`bad binding`)\n }\n } else if (key.match(/^bind[A-Z]/) != null) {\n const bindingType = key.substring(4, 5).toLowerCase() + key.substring(5)\n const binding = bindings[bindingType]\n if (binding !== undefined) {\n bind(elt, value, binding)\n } else {\n throw new Error(\n `${key} is not allowed, bindings.${bindingType} is not defined`\n )\n }\n } else if (xinPath(value)) {\n bind(elt, value, elementPropBinding(key))\n } else {\n elementProp(elt, key, value)\n }\n}\n\nconst create = (tagType: string, ...contents: ElementPart[]): HTMLElement => {\n if (templates[tagType] === undefined) {\n const [tag, namespace] = tagType.split('|')\n if (namespace === undefined) {\n templates[tagType] = globalThis.document.createElement(tag)\n } else {\n templates[tagType] = globalThis.document.createElementNS(namespace, tag)\n }\n }\n const elt = templates[tagType].cloneNode() as HTMLElement\n const elementProps: ElementProps = {}\n for (const item of contents) {\n if (\n item instanceof Element ||\n item instanceof DocumentFragment ||\n typeof item === 'string' ||\n typeof item === 'number'\n ) {\n if (elt instanceof HTMLTemplateElement) {\n elt.content.append(item as Node)\n } else {\n elt.append(item as Node)\n }\n } else if (xinPath(item)) {\n elt.append(elements.span({ bindText: item }))\n } else {\n Object.assign(elementProps, item)\n }\n }\n for (const key of Object.keys(elementProps)) {\n const value: any = elementProps[key]\n elementSet(elt, key, value)\n }\n return elt\n}\n\nconst fragment = (...contents: ElementPart[]): DocumentFragment => {\n const frag = globalThis.document.createDocumentFragment()\n for (const item of contents) {\n frag.append(item as Node)\n }\n return frag\n}\n\n/**\n * elements is a proxy that produces ElementCreators, e.g.\n * elements.div() creates <div> elements and\n * elements.myElement() creates <my-element> elements.\n */\nexport const elements = new Proxy(\n { fragment },\n {\n get(target, tagName: string) {\n tagName = tagName.replace(/[A-Z]/g, (c) => `-${c.toLocaleLowerCase()}`)\n if ((target as StringMap)[tagName] === undefined) {\n ;(target as StringMap)[tagName] = (...contents: ElementPart[]) =>\n create(tagName, ...contents)\n }\n return (target as StringMap)[tagName]\n },\n set() {\n throw new Error('You may not add new properties to elements')\n },\n }\n) as unknown as ElementsProxy\n\ninterface SVGElementsProxy {\n [key: string]: ElementCreator<SVGElement>\n}\n\nexport const svgElements = new Proxy(\n { fragment },\n {\n get(target, tagName: string) {\n if ((target as StringMap)[tagName] === undefined) {\n ;(target as StringMap)[tagName] = (...contents: ElementPart[]) =>\n create(`${tagName}|${SVG}`, ...contents)\n }\n return (target as StringMap)[tagName]\n },\n set() {\n throw new Error('You may not add new properties to elements')\n },\n }\n) as unknown as SVGElementsProxy\n\ninterface MathMLElementsProxy {\n [key: string]: ElementCreator<MathMLElement>\n}\n\nexport const mathML = new Proxy(\n { fragment },\n {\n get(target, tagName: string) {\n if ((target as StringMap)[tagName] === undefined) {\n ;(target as StringMap)[tagName] = (...contents: ElementPart[]) =>\n create(`${tagName}|${MATH}`, ...contents)\n }\n return (target as StringMap)[tagName]\n },\n set() {\n throw new Error('You may not add new properties to elements')\n },\n }\n) as unknown as MathMLElementsProxy\n",
|
|
22
|
-
"/*#\n# 5. css\n\n`xinjs` provides a collection of utilities for working with CSS rules that\nhelp leverage CSS variables to produce highly maintainable and lightweight\ncode that is nonetheless easy to customize.\n\nThe basic goal is to be able to implement some or all of our CSS very efficiently, compactly,\nand reusably in Javascript because:\n\n- Javascript quality tooling is really good, CSS quality tooling is terrible\n- Having to write CSS in Javascript is *inevitable* so it might as well be consistent and painless\n- It turns out you can get by with *much less* and generally *simpler* CSS this way\n- You get some natural wins this way. E.g. writing two definitions of `body {}` is easy to do\n and bad in CSS. In Javascript it's simply an error!\n\nThe `css` module attempts to implement all this the simplest and most obvious way possible,\nproviding syntax sugar to help with best-practices such as `css-variables` and the use of\n`@media` queries to drive consistency, themes, and accessibility.\n\n## css(styleMap: XinStyleMap): string\n\nA function that, given a `XinStyleMap` renders CSS code. What is a XinStyleMap?\nIt's kind of what you'd expect if you wanted to represent CSS as Javascript in\nthe most straightforward way possible. It allows for things like `@import`,\n`@keyframes` and so forth, but knows just enough about CSS to help with things\nlike autocompletion of CSS rules (rendered as camelcase) so that, unlike me, it\ncan remind you that it's `whiteSpace` and not `whitespace`.\n\n```\nimport {elements, css} from 'xinjs'\nconst {style} = elements\n\nconst myStyleMap = {\n body: {\n color: 'red'\n },\n button: {\n borderRadius: 5\n }\n}\n\ndocument.head.append(style(css(myStyleMap)))\n```\n\nThere's a convenient `Stylesheet()` function that does all this and adds an id to the\nresulting `<style>` element to make it easier to figure out where a given stylesheet\ncame from.\n\n```\nStylesheet('my-styles', {\n body: {\n color: 'red'\n },\n button: {\n borderRadius: 5\n }\n})\n```\n\n…inserts the following in the `document.head`:\n\n```\n<style id=\"my-styles\">\nbody {\n color: red;\n}\nbutton {\n border-radius: 5px;\n}\n</style>\n```\n\nIf a bare, non-zero **number** is assigned to a CSS property it will have 'px' suffixed\nto it automatically. There are *no bare numeric*ele properties in CSS except `0`.\n\nWhy `px`? Well the other obvious options would be `rem` and `em` but `px` seems the\nleast surprising option.\n\n`css` should render nested rules, such as `@keyframes` and `@media` correctly.\n\n## Initializing CSS Variables\n\nYou can initialize CSS variables using `_` or `__` prefixes on property names.\nOne bar, turns the camelCase property-name into a --snake-case CSS variable\nname, while two creates a default that can be overridden.\n\n```\nStyleSheet('my-theme', {\n ':root', {\n _fooBar: 'red',\n __bazBar: '10px'\n }\n})\n```\n\nWill produce:\n\n```\n<style id=\"my-theme\">\n :root {\n --foo-bar: red;\n --baz-bar: var(--baz-bar-default, 10px);\n }\n</style>\n```\n```js\nimport { elements, vars } from 'tosijs'\nconst { div } = elements\n\nwindow.CSS.registerProperty({\n name: '--at-bar',\n syntax: '<color>',\n inherits: true,\n initialValue: 'green',\n})\n\npreview.append(\n div(\n {\n style: {\n _fooBar: 'red',\n __bazBar: 'blue',\n }\n },\n div(\n {\n style: { color: vars.fooBar },\n },\n 'fooBar'\n ),\n div(\n {\n style: { color: vars.bazBar },\n },\n 'bazBar'\n ),\n div(\n {\n style: { color: vars.atBar },\n },\n 'atBar'\n ),\n )\n)\n```\n\n> ### @property and CSS.registerProperty() considered harmful\n>\n> This [new CSS feature}(https://developer.mozilla.org/en-US/docs/Web/CSS/@property) \n> is well-intentioned but ill-considered. I advise\n> against using it yourself until its serious flaws are addressed. The problem\n> is that if someone registers a variable you're using or you register\n> a variable someone else is using then your CSS may be broken. And\n> you can't re-register a variable either. \n\n> This is a bit like the problem\n> that xinjs Component works around with tagNames, but in practice far more\n> difficult to solve. It is impossible to tell if a given instance of \n> a given variable name is an intentional reuse or a new separate variable.\n> No-one intentionally defines two different components with the same tag.\n\n## invertLuminance({[key: string]: any}) => {[key: string]: string}\n\nGiven a map of CSS properties (in camelCase) emit a map of those properties that\nhas color values with their luminance inverted.\n\n const myStyleMap = {\n ':root': cssVars, // includes --font-size\n '@media (prefers-color-scheme: dark)': {\n ':root': invertLuminance(cssVars) // omits --font-size\n },\n }\n\n## vars\n\n`vars` is a proxy object that will return a css variable string from\na camelCase property, e.g.\n\n vars.camelCase // 'var(--camel-case)'\n\n> **it isn't called `var`** because that's a reserved word!\n\n### varDefault\n\n`varDefault` is a proxy object just like `vars` except that it returns a\n`function` that takes a property and renders it as a css variable reference\nwith a default, e.g\n\n varDefault.borderColor('red') // `var(--border-color, red)`\n \n## `getCssVar(variable: string, atElement = document.body): string`\n\n`getCssVar()` obtains the css variable evaluated at the specified element \n(an element defined at `:root` can be evaluated at `document.body`). You\ncan provide the name, e.g. `--foo-bar`, or \"wrapped\", e.g. `var(--foo-bar)`.\n\n### Syntax Sugar for `calc(...)`\n\nMore importantly, `vars` allows you to conveniently perform calculations\non css (dimensional) variables by a percentage:\n\n vars.camelSize50 // 'calc(var(--camel-size) * 0.5)'\n vars.camelSize_50 // 'calc(var(--camel-size) * -0.5)'\n\n### Computed Colors\n\n> #### Notes\n>\n> `color()` and `color-mix()` are [now enjoy 91% support](https://caniuse.com/?search=color-mix) as of writing.\n> See [color-mix()](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color-mix) documentation.\n> Where they meet your needs, I'd suggest using them.\n>\n> [contrast-color()](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/contrast-color) is coming in Safari 26, \n> but [currently enjoys 0% upport](https://caniuse.com/?search=contrast-color).\n>\n> **Caution** although these look superficially like the `vars` syntax\n> sugar for `calc()` performed on dimensional variables, they are in fact\n> color calculations are performed on colors *evaluated* on `document.body` at \n> execution time. (So they won'b automatically be recomputed on theme change.)\n\nYou can write:\n\n```\nconst styleSpec = {\n _lineHeight: 24,\n _spacing: 5,\n _buttonHeight: calc(`vars.lineHeight + vars.spacing200`)\n)\n```\n\nAnd then render this as CSS and stick it into a StyleNode and it will work.\n\nYou *cannot* write:\n\n```\nconst styleSpec = {\n _background: '#fafafa',\n _blockColor: vars.background_5b\n}\n```\n\nBecause `--background` isn't defined on `document.body` yet, so vars.background_5b\nwon't be able to tell what `--background` is going to be yet. So either you need to\ndo this in two stags (create a StyleNode that defines the base color `--background`\nthen define the computed colors and add this) OR use a `Color` instance:\n\n```\nconst background = Color.fromCss('#fafafa')\n\ninitVars({\n background: background.toHTML,\n blockColor: background.brighten(-0.05).toHTML\n})\n```\n\nUntil browsers support color calculations the way they support dimension arithmetic with `calc()`\nthis is the miserable existence we all lead. That, or defining huge arrays of color\nvalues that we mostly don't use and are often not exactly what we want. You choose!\n\n> **New** color now supports CSS [named colors](https://developer.mozilla.org/en-US/docs/Web/CSS/named-color),\nsuch as `black`, `red`, and `aliceblue`.\n\n`vars` also allows you to perform color calculations on css (color)\nvariables:\n\n#### Change luminance with `b` (for brighten) suffix\n\nThe scale value is treated as a percentage and moves the brightness\nthat far from its current value to 100% (if positive) or 0% (if negattive).\n\n vars.textColor50b // increases the luminance of textColor\n vars.textColor_50b // halves the luminance of textColor\n\n#### Change saturation with `s` suffix\n\nThe scale value is treated as a percentage and moves the saturation\nthat far from its current value to 100% (if positive) or 0% (if negattive).\n\n vars.textColor50s // increases the saturation of textColor\n vars.textColor_50s // halves the saturation of textColor\n\n#### Rotate hue with `h` suffix\n\n vars.textColor30h // rotates the hue of textColor by 30°\n vars.textColor_90h // rotates the hue of textColor by -90°\n\n#### Set Opacity with `o` suffix\n\nUnlike the other modifiers, `o` simply sets the opacity of the\nresulting color to the value provided.\n\n vars.textColor50o // textColor with opacity set to 0.5\n\n## More to follow?\n\nThe more I use the `css` module, the more I like it and the more ideas I have\nto make it even better, but I have a very tight size/complexity target\nfor `xinjs` so these new ideas really have to earn a spot. Perhaps the\nfeature I have come closest to adding and then decided against was providing\nsyntax-sugar for classs so that:\n\n css({\n _foo: {\n color: 'red'\n }\n })\n\nWould render:\n\n .foo {\n color: 'red'\n }\n\nBut looking at the code I and others have written, the case for this is weak as most class\ndeclarations are not just bare classes. This doesn't help with declarations\nfor `input.foo` or `.foo::after` or `.foo > *` and now there'd be things that\nlook different which violates the \"principle of least surprise\". So, no.\n\n### Something to Declare\n\nWhere I am always looking to improve this module (and all of `xinjs`) is to\ndo a better job of **declaring** things to improve autocomplete behavior and\nminimize casting and other Typescript antipatterns. E.g. adding a ton of\ndeclarations to `elements` and `css` has done wonders to reduce the need for\nstuff like `const nameElement = this.parts.nameField as unknown as HTMLInputElement`\nand prevent css property typos without adding a single byte to the size of\nthe javascript payload.\n*/\nimport { Color } from './color'\nimport { elements } from './elements'\nimport { camelToKabob } from './string-case'\nimport { XinStyleSheet, XinStyleRule } from './css-types'\n\nexport function StyleSheet(id: string, styleSpec: XinStyleSheet) {\n const element = elements.style(css(styleSpec))\n element.id = id\n document.head.append(element)\n}\n\nconst numericProps = [\n 'animation-iteration-count',\n 'flex',\n 'flex-base',\n 'flex-grow',\n 'flex-shrink',\n 'opacity',\n 'order',\n 'tab-size',\n 'widows',\n 'z-index',\n 'zoom',\n]\n\nexport const processProp = (\n prop: string,\n value: string | number\n): { prop: string; value: string } => {\n if (typeof value === 'number' && !numericProps.includes(prop)) {\n value = `${value}px`\n }\n if (prop.startsWith('_')) {\n if (prop.startsWith('__')) {\n prop = '--' + prop.substring(2)\n value = `var(${prop}-default, ${value})`\n } else {\n prop = '--' + prop.substring(1)\n }\n }\n return {\n prop,\n value: String(value),\n }\n}\n\nconst renderProp = (\n indentation: string,\n cssProp: string,\n value: string | number | Color | undefined\n): string => {\n if (value === undefined) {\n return ''\n }\n if (value instanceof Color) {\n value = value.html\n }\n const processed = processProp(cssProp, value)\n return `${indentation} ${processed.prop}: ${processed.value};`\n}\n\nconst renderStatement = (\n key: string,\n value: Color | string | number | XinStyleRule | undefined,\n indentation = ''\n): string => {\n const cssProp = camelToKabob(key)\n if (typeof value === 'object' && !(value instanceof Color)) {\n const renderedRule = Object.keys(value)\n .map((innerKey) =>\n renderStatement(innerKey, value[innerKey], `${indentation} `)\n )\n .join('\\n')\n return `${indentation} ${key} {\\n${renderedRule}\\n${indentation} }`\n } else {\n return renderProp(indentation, cssProp, value)\n }\n}\n\nexport const css = (obj: XinStyleSheet, indentation = ''): string => {\n const selectors = Object.keys(obj).map((selector) => {\n const body = obj[selector]\n if (typeof body === 'string') {\n if (selector === '@import') {\n return `@import url('${body}');`\n }\n throw new Error('top-level string value only allowed for `@import`')\n }\n const rule = Object.keys(body)\n .map((prop) => renderStatement(prop, body[prop]))\n .join('\\n')\n return `${indentation}${selector} {\\n${rule}\\n}`\n })\n return selectors.join('\\n\\n')\n}\n\nexport const initVars = (obj: {\n [key: string]: string | number\n}): XinStyleRule => {\n console.warn('initVars is deprecated. Just use _ and __ prefixes instead.')\n const rule: XinStyleRule = {}\n for (const key of Object.keys(obj)) {\n const value = obj[key]\n const kabobKey = camelToKabob(key)\n rule[`--${kabobKey}`] =\n typeof value === 'number' && value !== 0 ? String(value) + 'px' : value\n }\n return rule\n}\n\nexport const invertLuminance = (map: XinStyleRule): XinStyleRule => {\n const inverted: XinStyleRule = {}\n\n for (const key of Object.keys(map)) {\n const value = map[key]\n if (value instanceof Color) {\n inverted[key] = value.inverseLuminance\n } else if (\n typeof value === 'string' &&\n value.match(/^(#[0-9a-fA-F]{3}|rgba?\\(|hsla?\\()/)\n ) {\n inverted[key] = Color.fromCss(value).inverseLuminance\n }\n }\n\n return inverted\n}\n\nexport const varDefault = new Proxy<{ [key: string]: CssVarBuilder }>(\n {},\n {\n get(target, prop: string) {\n if (target[prop] === undefined) {\n const varName = '--' + camelToKabob(prop)\n target[prop] = (val: string | number) => `var(${varName}, ${val})`\n }\n return target[prop]\n },\n }\n)\n\ntype VarsType = {\n default: typeof varDefault\n} & {\n [key: string]: string\n}\n\nexport const vars = new Proxy<VarsType>({} as VarsType, {\n get(target, prop: string) {\n if (prop === 'default') {\n return varDefault\n }\n if (target[prop] == null) {\n prop = camelToKabob(prop)\n const [, _varName, , isNegative, scaleText, method] = prop.match(\n /^([-\\w]*?)((_)?(\\d+)(\\w?))?$/\n ) || ['', prop]\n const varName = `--${_varName}`\n if (scaleText != null) {\n const scale =\n isNegative == null\n ? Number(scaleText) / 100\n : -Number(scaleText) / 100\n switch (method) {\n case 'b': // brighten\n {\n const baseColor = Color.fromVar(varName)\n target[prop] =\n scale > 0\n ? baseColor.brighten(scale).rgba\n : baseColor.darken(-scale).rgba\n }\n break\n case 's': // saturate\n {\n const baseColor = Color.fromVar(varName)\n target[prop] =\n scale > 0\n ? baseColor.saturate(scale).rgba\n : baseColor.desaturate(-scale).rgba\n }\n break\n case 'h': // hue\n {\n const baseColor = Color.fromVar(varName)\n target[prop] = baseColor.rotate(scale * 100).rgba\n }\n break\n case 'o': // alpha\n {\n const baseColor = Color.fromVar(varName)\n target[prop] = baseColor.opacity(scale).rgba\n }\n break\n case '':\n target[prop] = `calc(var(${varName}) * ${scale})`\n break\n default:\n console.error(method)\n throw new Error(\n `Unrecognized method ${method} for css variable ${varName}`\n )\n }\n } else {\n target[prop] = `var(${varName})`\n }\n }\n return target[prop]\n },\n})\n\ntype CssVarBuilder = (val: string | number) => string\n",
|
|
23
|
-
"/*#\n# 4. web-components\n\n**xinjs** provides the abstract `Component` class to make defining custom-elements\neasier.\n\n## Component\n\nTo define a custom-element you can subclass `Component`, simply add the properties\nand methods you want, with some help from `Component` itself, and then simply\nexport your new class's `elementCreator()` which is a function that defines your\nnew component's element and produces instances of it as needed.\n\n```\nimport {Component} from 'xinjs'\n\nclass ToolBar extends Component {\n static styleSpec = {\n ':host': {\n display: 'flex',\n gap: '10px',\n },\n }\n}\n\nexport const toolBar = ToolBar.elementCreator({ tag: 'tool-bar' })\n```\n\nThis component is just a structural element. By default a `Component` subclass will\ncomprise itself and a `<slot>`. You can change this by giving your subclass its\nown `content` template.\n\nThe last line defines the `ToolBar` class as the implementation of `<tool-bar>`\nHTML elements (`tool-bar` is derived automatically from the class name) and\nreturns an `ElementCreator` function that creates `<tool-bar>` elements.\n\nSee [elements](/?elements.ts) for more information on `ElementCreator` functions.\n\n### Component properties\n\n#### content: Element | Element[] | () => Element | () => Element[] | null\n\nHere's a simple example of a custom-element that simply produces a\n`<label>` wrapped around `<span>` and an `<input>`. Its value is synced\nto that of its `<input>` so the user doesn't need to care about how\nit works internally.\n\n```js\nimport { Component, elements } from 'tosijs'\n\nconst {label, span, input} = elements\n\nclass LabeledInput extends Component {\n caption = 'untitled'\n value = ''\n\n constructor() {\n super()\n this.initAttributes('caption')\n }\n\n content = label(span(), input())\n\n connectedCallback() {\n super.connectedCallback()\n const {input} = this.parts\n input.addEventListener('input', () => {\n this.value = input.value\n })\n }\n\n render() {\n super.render()\n const {span, input} = this.parts\n span.textContent = this.caption\n if (input.value !== this.value) {\n input.value = this.value\n }\n }\n}\n\nconst labeledInput = LabeledInput.elementCreator()\n\npreview.append(\n labeledInput({caption: 'A text field', value: 'some text'})\n)\n```\n\n`content` is, in essence, a template for the internals of the element. By default\nit's a single `<slot>` element. If you explicitly want an element with no content\nyou can set your subclass's content to `null` or omit any `<slot>` from its template.\n\nBy setting content to be a function that returns elements instead of a collection\nof elements you can take customize elements based on the component's properties.\nIn particular, you can use `onXxxx` syntax sugar to bind events.\n\n(Note that you cannot bind to xin paths reliably if your component uses a `shadowDOM`\nbecause `xin` cannot \"see\" elements there. As a general rule, you need to take care\nof anything in the `shadowDOM` yourself.)\n\nIf you'd like to see a more complex example along the same lines, look at\n[xin-form and xin-field](https://ui.xinjs.net/?form.ts).\n\n##### <slot> names and the `slot` attribute\n\n```\nconst {slot} = Component.elements\nclass MenuBar extends Component {\n static styleSpec = {\n ':host, :host > slot': {\n display: 'flex',\n },\n ':host > slot:nth-child(1)': {\n flex: '1 1 auto'\n },\n }\n\n content = [slot(), slot({name: 'gadgets'})]\n}\n\nexport menuBar = MenuBar.elementCreator()\n```\n\nOne of the neat things about custom-elements is that you can give them *multiple*\n`<slot>`s with different `name` attributes and then have children target a specific\nslot using the `slot` attribute.\n\nThis app's layout (the nav sidebar that disappears if the app is in a narrow space, etc.)\nis built using just such a custom-element.\n\n#### `<xin-slot>`\n\nIf you put `<slot>` elements inside a `Component` subclass that doesn't have a\nshadowDOM, they will automatically be replaced with `<xin-slot>` elements that\nhave the expected behavior (i.e. sucking in children in based on their `<slot>`\nattribute).\n\n`<xin-slot>` doesn't support `:slotted` but since there's no shadowDOM, just\nstyle such elements normally, or use `xin-slot` as a CSS-selector.\n\nNote that you cannot give a `<slot>` element attributes (other than `name`) so if\nyou want to give a `<xin-slot>` attributes (such as `class` or `style`), create it\nexplicitly (e.g. using `elements.xinSlot()`) rather than using `<slot>` elements\nand letting them be switched out (because they'll lose any attributes you give them).\n\nHere's a very simple example:\n\n```js\nimport { Component, elements } from 'tosijs'\n\nconst { xinSlot, div } = elements\n\nclass FauxSlotExample extends Component {\n content = [\n div('This is a web-component with no shadow DOM and working slots!'),\n xinSlot({name: 'top'}),\n xinSlot(),\n xinSlot({name: 'bottom'}),\n ]\n}\n\nconst fauxSlotExample = FauxSlotExample.elementCreator({\n tag: 'faux-slot-example',\n styleSpec: {\n ':host': {\n display: 'flex',\n flexDirection: 'column'\n },\n }\n})\n\npreview.append(\n fauxSlotExample(\n div({slot: 'bottom'}, 'I should be on the bottom'),\n div({slot: 'top'}, 'I should be on the top'),\n div('I should be in the middle')\n )\n)\n```\n\n> ##### Background\n>\n> `<slot>` elements do not work as expected in shadowDOM-less components. This is\n> hugely annoying since it prevents components from composing nicely unless they\n> have a shadowDOM, and while the shadowDOM is great for small widgets, it's\n> terrible for composite views and breaks `xinjs`'s bindings (inside the shadow\n> DOM you need to do data- and event- binding manually).\n\n#### styleNode: HTMLStyleElement\n\n`styleNode` is the `<style>` element that will be inserted into the element's\n`shadowRoot`.\n\nIf a `Component` subclass has no `styleNode`, no `shadowRoot` will be\ncreated. This reduces the memory and performance cost of the element.\n\nThis is to avoid the performance/memory costs associated with the `shadowDOM`\nfor custom-elements with no styling.\n\n##### Notes\n\nStyling custom-elements can be tricky, and it's worth learning about\nhow the `:host` and `:slotted()` selectors work.\n\nIt's also very useful to understand how CSS-Variables interact with the\n`shadowDOM`. In particular, CSS-variables are passed into the `shadowDOM`\nwhen other CSS rules are not. You can use css rules to modify css-variables\nwhich will then penetrate the `shadowDOM`.\n\n#### refs: {[key:string]: Element | undefined}\n\n render() {\n super.render() // see note\n const {span, input} = this.parts\n span.textContent = this.caption\n if (input.value !== this.value) {\n input.value = this.value\n }\n }\n\n> **Note**: the `render()` method of the base `Component` class doesn't currently\n> do anything, so calling it is optional (but a good practice in case one day…)\n>\n> It is *necessary* however to call `super.connectedCallback`, `super.disconnectedCallback`\n> and `super()` in the `constructor()` should you override them.\n\n`this.parts` returns a proxy that provides elements conveniently and efficiently. It\nis intended to facilitate access to static elements (it memoizes its values the\nfirst time they are computed).\n\n`this.parts.foo` will return a content element with `data-ref=\"foo\"`. If no such\nelement is found it tries it as a css selector, so `this.parts['.foo']` would find\na content element with `class=\"foo\"` while `this.parts.h1` will find an `<h1>`.\n\n`this.parts` will also remove a `data-ref` attribute once it has been used to find\nthe element. This means that if you use all your refs in `render` or `connectedCallback`\nthen no trace will remain in the DOM for a mounted element.\n\n### Component methods\n\n#### initAttributes(...attributeNames: string[])\n\n class LabeledInput extends Component {\n caption: string = 'untitled'\n value: string = ''\n\n constructor() {\n super()\n this.initAttributes('caption')\n }\n\n ...\n }\n\nSets up basic behavior such as queueing a render if an attribute is changed, setting\nattributes based on the DOM source, updating them if they're changed, implementing\nboolean attributes in the expected manner, and so forth.\n\nCall `initAttributes` in your subclass's `constructor`, and make sure to call `super()`.\n\n#### queueRender(triggerChangeEvent = false): void\n\nUses `requestAnimationFrame` to queue a call to the component's `render` method. If\ncalled with `true` it will also trigger a `change` event.\n\n#### private initValue(): void\n\n**Don't call this!** Sets up expected behavior for an `HTMLElement` with\na value (i.e. triggering a `change` events and `render` when the `value` changes).\n\n#### private hydrate(): void\n\n**Don't call this** Appends `content` to the element (or its `shadowRoot` if it has a `styleNode`)\n\n#### connectedCallback(): void\n\nIf the class has an `onResize` handler then a ResizeObserver will trigger `resize`\nevents on the element when its size changes and `onResize` will be set up to respond\nto `resize` events.\n\nAlso, if the subclass has defined `value`, calls `initValue()`.\n\n`connectedCallback` is a great place to attach **event-handlers** to elements in your component.\n\nBe sure to call `super.connectedCallback()` if you implement `connectedCallback` in the subclass.\n\n#### disconnectedCallback(): void\n\nBe sure to call `super.disconnectedCallback()` if you implement `disconnectedCallback` in the subclass.\n\n#### render(): void\n\nBe sure to call `super.render()` if you implement `render` in the subclass.\n\n### Component static properties\n\n#### Component.elements\n\n const {label, span, input} = Component.elements\n\nThis is simply provided as a convenient way to get to [elements](/?elements.ts)\n\n### Component static methods\n\n#### Component.elementCreator(options? {tag?: string, styleSpec: XinStyleSheet}): ElementCreator\n\n export const toolBar = ToolBar.elementCreator({tag: 'tool-bar'})\n\nReturns a function that creates the custom-element. If you don't pass a `tag` or if the provided tag\nis already in use, a new unique tag will be used.\n\nIf no tag is provided, the Component will try to use introspection to \"snake-case\" the\n\"ClassName\", but if you're using name mangling this won't work and you'll get something\npretty meaningless.\n\nIf you want to create a global `<style>` sheet for the element (especially useful if\nyour component doesn't use the `shadowDOM`) then you can pass `styleSpec`. E.g.\n\n export const toolBar = ToolBar.elementCreator({\n tag: 'tool-bar',\n styleSpec: {\n ':host': { // note that ':host' will be turned into the tagName automatically!\n display: 'flex',\n padding: 'var(--toolbar-padding, 0 8px)',\n gap: '4px'\n }\n }\n })\n\nThis will—assuming \"tool-bar\" is available—create:\n\n <style id=\"tool-bar-helper\">\n tool-bar {\n display: flex;\n padding: var(--toolbar-padding, 0 8px);\n gap: 4px;\n }\n <style>\n\nAnd append it to `document.head` when the first instance of `<tool-bar>` is inserted in the DOM.\n\nFinally, `elementCreator` is memoized and only generated once (and the arguments are\nignored on all subsequent calls).\n\n## Examples\n\n[xinjs-ui](https://ui.xinjs.net) is a component library built using this `Component` class\nthat provides the essential additions to standard HTML elements needed to build many\nuser-interfaces.\n\n- [xin-example](https://ui.xinjs.net/https://ui.xinjs.net/?live-example.ts) uses multiple named slots to implement\n powers the interactive examples used for this site.\n- [xin-sidebar](https://ui.xinjs.net/?side-nav.ts) implements the sidebar navigation\n used on this site.\n- [xin-table](https://ui.xinjs.net/?data-table.ts) implements virtualized tables\n with resizable, reorderable, sortable columns that can handle more data\n than you're probably willing to load.\n- [xin-form and xin-field](https://ui.xinjs.net/?form.ts) allow you to\n quickly create forms that leverage all the built-in functionality of `<input>`\n elements (including powerful validation) even for custom-fields.\n- [xin-md](https://ui.xinjs.net/?markdown-viewer.ts) uses `marked` to render\n markdown.\n- [xin-3d](https://ui.xinjs.net/?babylon-3d.ts) lets you easily embed 3d scenes\n in your application using [babylonjs](https://babylonjs.com/)\n*/\nimport { css } from './css'\nimport { XinStyleSheet } from './css-types'\nimport { deepClone } from './deep-clone'\nimport { appendContentToElement, dispatch, resizeObserver } from './dom'\nimport { elements, ElementsProxy } from './elements'\nimport { camelToKabob, kabobToCamel } from './string-case'\nimport { ElementCreator, ContentType, PartsMap } from './xin-types'\n\nlet anonymousElementCount = 0\n\nfunction anonElementTag(): string {\n return `custom-elt${(anonymousElementCount++).toString(36)}`\n}\nlet instanceCount = 0\n\ninterface ElementCreatorOptions extends ElementDefinitionOptions {\n tag?: string\n styleSpec?: XinStyleSheet\n}\n\nconst globalStyleSheets: {\n [key: string]: string\n} = {}\n\nfunction setGlobalStyle(tagName: string, styleSpec: XinStyleSheet) {\n const existing = globalStyleSheets[tagName]\n const processed = css(styleSpec).replace(/:host\\b/g, tagName)\n globalStyleSheets[tagName] = existing\n ? existing + '\\n' + processed\n : processed\n}\n\nfunction insertGlobalStyles(tagName: string) {\n if (globalStyleSheets[tagName]) {\n document.head.append(\n elements.style({ id: tagName + '-component' }, globalStyleSheets[tagName])\n )\n }\n delete globalStyleSheets[tagName]\n}\n\nexport abstract class Component<T = PartsMap> extends HTMLElement {\n static elements: ElementsProxy = elements\n private static _elementCreator?: ElementCreator<Component>\n instanceId: string\n styleNode?: HTMLStyleElement\n static styleSpec?: XinStyleSheet\n static styleNode?: HTMLStyleElement\n content: ContentType | (() => ContentType) | null = elements.slot()\n isSlotted?: boolean\n private static _tagName: null | string = null\n static get tagName(): null | string {\n return this._tagName\n }\n [key: string]: any\n\n static StyleNode(styleSpec: XinStyleSheet): HTMLStyleElement {\n console.warn(\n 'StyleNode is deprecated, just assign static styleSpec: XinStyleSheet to the class directly'\n )\n return elements.style(css(styleSpec))\n }\n\n static elementCreator<C = Component>(\n this: C,\n options: ElementCreatorOptions = {}\n ): ElementCreator<C> {\n const componentClass = this as Component\n if (componentClass._elementCreator == null) {\n const { tag, styleSpec } = options\n let tagName = options != null ? tag : null\n if (tagName == null) {\n if (\n typeof componentClass.name === 'string' &&\n componentClass.name !== ''\n ) {\n tagName = camelToKabob(componentClass.name)\n if (tagName.startsWith('-')) {\n tagName = tagName.slice(1)\n }\n } else {\n tagName = anonElementTag()\n }\n }\n if (customElements.get(tagName) != null) {\n console.warn(`${tagName} is already defined`)\n }\n if (tagName.match(/\\w+(-\\w+)+/) == null) {\n console.warn(`${tagName} is not a legal tag for a custom-element`)\n tagName = anonElementTag()\n }\n while (customElements.get(tagName) !== undefined) {\n tagName = anonElementTag()\n }\n componentClass._tagName = tagName\n if (styleSpec !== undefined) {\n setGlobalStyle(tagName, styleSpec)\n }\n window.customElements.define(\n tagName,\n this as unknown as CustomElementConstructor,\n options\n )\n componentClass._elementCreator = elements[tagName]\n }\n return componentClass._elementCreator\n }\n\n initAttributes(...attributeNames: string[]): void {\n const attributes: { [key: string]: any } = {}\n const attributeValues: { [key: string]: any } = {}\n const observer = new MutationObserver((mutationsList) => {\n let triggerRender = false\n mutationsList.forEach((mutation) => {\n triggerRender = !!(\n mutation.attributeName &&\n attributeNames.includes(kabobToCamel(mutation.attributeName))\n )\n })\n if (triggerRender && this.queueRender !== undefined)\n this.queueRender(false)\n })\n observer.observe(this, { attributes: true })\n attributeNames.forEach((attributeName) => {\n attributes[attributeName] = deepClone(this[attributeName])\n const attributeKabob = camelToKabob(attributeName)\n Object.defineProperty(this, attributeName, {\n enumerable: false,\n get() {\n if (typeof attributes[attributeName] === 'boolean') {\n return this.hasAttribute(attributeKabob)\n } else {\n if (this.hasAttribute(attributeKabob)) {\n return typeof attributes[attributeName] === 'number'\n ? parseFloat(this.getAttribute(attributeKabob))\n : this.getAttribute(attributeKabob)\n } else if (attributeValues[attributeName] !== undefined) {\n return attributeValues[attributeName]\n } else {\n return attributes[attributeName]\n }\n }\n },\n set(value) {\n if (typeof attributes[attributeName] === 'boolean') {\n if (value !== this[attributeName]) {\n if (value) {\n this.setAttribute(attributeKabob, '')\n } else {\n this.removeAttribute(attributeKabob)\n }\n this.queueRender()\n }\n } else if (typeof attributes[attributeName] === 'number') {\n if (value !== parseFloat(this[attributeName])) {\n this.setAttribute(attributeKabob, value)\n this.queueRender()\n }\n } else {\n if (\n typeof value === 'object' ||\n `${value as string}` !== `${this[attributeName] as string}`\n ) {\n if (\n value === null ||\n value === undefined ||\n typeof value === 'object'\n ) {\n this.removeAttribute(attributeKabob)\n } else {\n this.setAttribute(attributeKabob, value)\n }\n this.queueRender()\n attributeValues[attributeName] = value\n }\n }\n },\n })\n })\n }\n\n private initValue(): void {\n const valueDescriptor = Object.getOwnPropertyDescriptor(this, 'value')\n if (\n valueDescriptor === undefined ||\n valueDescriptor.get !== undefined ||\n valueDescriptor.set !== undefined\n ) {\n return\n }\n let value = this.hasAttribute('value')\n ? this.getAttribute('value')\n : deepClone(this.value)\n delete this.value\n Object.defineProperty(this, 'value', {\n enumerable: false,\n get() {\n return value\n },\n set(newValue: any) {\n if (value !== newValue) {\n value = newValue\n this.queueRender(true)\n }\n },\n })\n }\n\n private _parts?: T\n get parts(): T {\n const root = this.shadowRoot != null ? this.shadowRoot : this\n if (this._parts == null) {\n this._parts = new Proxy(\n {},\n {\n get(target: any, ref: string) {\n if (target[ref] === undefined) {\n let element = root.querySelector(`[part=\"${ref}\"]`)\n if (element == null) {\n element = root.querySelector(ref)\n }\n if (element == null)\n throw new Error(`elementRef \"${ref}\" does not exist!`)\n element.removeAttribute('data-ref')\n target[ref] = element as Element\n }\n return target[ref]\n },\n }\n ) as T\n }\n return this._parts\n }\n\n constructor() {\n super()\n instanceCount += 1\n this.initAttributes('hidden')\n this.instanceId = `${this.tagName.toLocaleLowerCase()}-${instanceCount}`\n this._value = deepClone(this.defaultValue)\n }\n\n connectedCallback(): void {\n insertGlobalStyles((this.constructor as unknown as Component).tagName)\n this.hydrate()\n // super annoyingly, chrome loses its shit if you set *any* attributes in the constructor\n if (this.role != null) this.setAttribute('role', this.role)\n if (this.onResize !== undefined) {\n resizeObserver.observe(this)\n if (this._onResize == null) {\n this._onResize = this.onResize.bind(this)\n }\n this.addEventListener('resize', this._onResize)\n }\n if (this.value != null && this.getAttribute('value') != null) {\n this._value = this.getAttribute('value')\n }\n this.queueRender()\n }\n\n disconnectedCallback(): void {\n resizeObserver.unobserve(this)\n }\n\n private _changeQueued = false\n private _renderQueued = false\n queueRender(triggerChangeEvent = false): void {\n if (!this._hydrated) return\n if (!this._changeQueued) this._changeQueued = triggerChangeEvent\n if (!this._renderQueued) {\n this._renderQueued = true\n requestAnimationFrame(() => {\n // TODO add mechanism to allow component developer to have more control over\n // whether input vs. change events are emitted\n if (this._changeQueued) dispatch(this, 'change')\n this._changeQueued = false\n this._renderQueued = false\n this.render()\n })\n }\n }\n\n private _hydrated = false\n private hydrate(): void {\n if (!this._hydrated) {\n this.initValue()\n const cloneElements = typeof this.content !== 'function'\n const _content: ContentType | null =\n typeof this.content === 'function' ? this.content() : this.content\n\n const { styleSpec } = this.constructor as unknown as Component\n let { styleNode } = this.constructor as unknown as Component\n if (styleSpec) {\n styleNode = (this.constructor as unknown as Component).styleNode =\n elements.style(css(styleSpec))\n delete (this.constructor as unknown as Component).styleNode\n }\n if (this.styleNode) {\n console.warn(\n this,\n 'styleNode is deprecrated, use static styleNode or statc styleSpec instead'\n )\n styleNode = this.styleNode\n }\n if (styleNode) {\n const shadow = this.attachShadow({ mode: 'open' })\n shadow.appendChild(styleNode.cloneNode(true))\n appendContentToElement(shadow, _content, cloneElements)\n } else if (_content !== null) {\n const existingChildren = Array.from(this.childNodes)\n appendContentToElement(this as HTMLElement, _content, cloneElements)\n this.isSlotted = this.querySelector('slot,xin-slot') !== undefined\n const slots = Array.from(this.querySelectorAll('slot'))\n if (slots.length > 0) {\n slots.forEach(XinSlot.replaceSlot)\n }\n if (existingChildren.length > 0) {\n const slotMap: { [key: string]: Element } = { '': this }\n Array.from(this.querySelectorAll('xin-slot')).forEach((slot) => {\n slotMap[(slot as XinSlot).name] = slot\n })\n existingChildren.forEach((child) => {\n const defaultSlot = slotMap['']\n const destSlot =\n child instanceof Element ? slotMap[child.slot] : defaultSlot\n ;(destSlot !== undefined ? destSlot : defaultSlot).append(child)\n })\n }\n }\n this._hydrated = true\n }\n }\n\n render(): void {}\n}\n\ninterface SlotParts extends PartsMap {\n slotty: HTMLSlotElement\n}\n\nclass XinSlot extends Component<SlotParts> {\n name = ''\n content = null\n\n static replaceSlot(slot: HTMLSlotElement): void {\n const _slot = document.createElement('xin-slot')\n if (slot.name !== '') {\n _slot.setAttribute('name', slot.name)\n }\n slot.replaceWith(_slot)\n }\n\n constructor() {\n super()\n this.initAttributes('name')\n }\n}\n\nexport const xinSlot = XinSlot.elementCreator({ tag: 'xin-slot' })\n",
|
|
16
|
+
"/*#\n# A.1 more-math\n\nSome simple functions egregiously missing from the Javascript `Math`\nobject. They are exported from `tosijs` as the `MoreMath` object.\n\n## Functions\n\n`clamp(min, v, max)` will return `v` if it's between `min` and `max`\nand the `min` or `max` otherwise.\n\nIf min > max, the function will return NaN.\n\n```\nclamp(0, 0.5, 1) // produces 0.5\nclamp(0, -0.5, 1) // produces 0\nclamp(-50, 75, 50) // produces 50\n```\n\n`lerp(a, b, t, clamped = true)` will interpolate linearly between `a` and `b` using\nparameter `t`. `t` will be clamped to the interval `[0, 1]`, so\n`lerp` will be clamped *between* a and b unless you pass `false` as the\noptional fourth parameter (allowing `lerp()` to extrapolate).\n\n```\nlerp(0, 10, 0.5) // produces 5\nlerp(0, 10, 2) // produces 10\nlerp(0, 10, 2, false) // produces 20\nlerp(5, -5, 0.75) // produces -2.5\n```\n\n## Constants\n\n`RADIANS_TO_DEGREES` and `DEGREES_TO_RADIANS` are values to multiply\nan angle by to convert between degrees and radians.\n*/\n\nexport const RADIANS_TO_DEGREES = 180 / Math.PI\nexport const DEGREES_TO_RADIANS = Math.PI / 180\n\nexport function clamp(min: number, v: number, max: number): number {\n return max < min ? NaN : v < min ? min : v > max ? max : v\n}\n\nexport function lerp(a: number, b: number, t: number, clamped = true): number {\n if (clamped) t = clamp(0, t, 1)\n return t * (b - a) + a\n}\n\nexport const MoreMath = {\n RADIANS_TO_DEGREES,\n DEGREES_TO_RADIANS,\n clamp,\n lerp,\n}\n",
|
|
17
|
+
"export function getCssVar(\n variableName: string,\n atElement = document.body\n): string {\n const computedStyle = getComputedStyle(atElement)\n if (variableName.endsWith(')') && variableName.startsWith('var(')) {\n variableName = variableName.slice(4, -1)\n }\n return computedStyle.getPropertyValue(variableName).trim()\n}\n",
|
|
18
|
+
"/*#\n# 5.1 color\n\n`tosijs` includes a lightweight, powerful `Color` class for manipulating colors.\nI hope at some point CSS will provide sufficiently capable native color calculations\nso that this will no longer be needed. Some of these methods have begun to appear,\nand are approaching wide implementation.\n\n## Color\n\nThe most straightforward methods for creating a `Color` instance are to use the\n`Color()` constructor to create an `rgb` or `rgba` representation, or using the\n`Color.fromCss()` to create a `Color` from any CSS (s)rgb representation,\ne.g.\n\n```\nnew Color(255, 255, 0) // yellow\nnew Color(0, 128, 0, 0.5) // translucent dark green\nColor.fromCss('#000') // black\nColor.fromCss('hsl(90deg 100% 50%)) // orange\nColor.fromCss('color(srgb 1 0 0.5)) // purple\n```\n\nNote that `Color.fromCss()` is not compatible with non-srgb color spaces. The new CSS\ncolor functions produce color specifications of the form `color(<space> ....)` and\n`Color.fromCSS()` will handle `color(srgb ...)` correctly (this is so it can parse the\noutput of `color-mix(in hsl ...)` but not other [color spaces](https://developer.mozilla.org/en-US/blog/css-color-module-level-4/#whats_new_in_css_colors_module_level_4).\n\n## Manipulating Colors\n\n```js\nimport { elements, Color } from 'tosijs'\n\nconst { label, span, div, input, button } = elements\n\nconst swatches = div({ class: 'swatches' })\nfunction makeSwatch(text) {\n const color = Color.fromCss(colorInput.value)\n const adjustedColor = eval('color.' + text)\n swatches.style.setProperty('--original', color)\n swatches.append(\n div(\n text,\n {\n class: 'swatch',\n title: `${adjustedColor.html} ${adjustedColor.hsla}`,\n style: {\n _adjusted: adjustedColor,\n _text: adjustedColor.contrasting()\n }\n }\n )\n )\n}\n\nconst colorInput = input({\n type: 'color',\n value: '#000',\n onInput: update\n})\nconst red = Color.fromCss('#f00')\nconst gray = Color.fromCss('#888')\nconst teal = Color.fromCss('teal')\nconst aliceblue = Color.fromCss('aliceblue')\n\nfunction update() {\n swatches.textContent = ''\n makeSwatch('brighten(-0.5)')\n makeSwatch('brighten(0.5)')\n makeSwatch('saturate(0.25)')\n makeSwatch('saturate(0.5)')\n makeSwatch('desaturate(0.5)')\n makeSwatch('desaturate(0.75)')\n makeSwatch('contrasting()')\n makeSwatch('contrasting(0.05)')\n makeSwatch('contrasting(0.25)')\n makeSwatch('contrasting(0.45)')\n makeSwatch('inverseLuminance')\n makeSwatch('mono')\n makeSwatch('rotate(-330)')\n makeSwatch('rotate(60)')\n makeSwatch('rotate(-270)')\n makeSwatch('rotate(120)')\n makeSwatch('rotate(-210)')\n makeSwatch('rotate(180)')\n makeSwatch('rotate(-150)')\n makeSwatch('rotate(240)')\n makeSwatch('rotate(-90)')\n makeSwatch('rotate(300)')\n makeSwatch('rotate(-30)')\n makeSwatch('opacity(0.1)')\n makeSwatch('opacity(0.5)')\n makeSwatch('opacity(0.75)')\n makeSwatch('rotate(-90).opacity(0.75)')\n makeSwatch('brighten(0.5).desaturate(0.5)')\n makeSwatch('blend(Color.black, 0.5)')\n makeSwatch('mix(Color.white, 0.4)')\n makeSwatch('blend(gray, 0.4)')\n makeSwatch('mix(red, 0.25)')\n makeSwatch('mix(red, 0.5)')\n makeSwatch('mix(red, 0.75)')\n makeSwatch('mix(teal, 0.25)')\n makeSwatch('mix(teal, 0.5)')\n makeSwatch('mix(teal, 0.75)')\n makeSwatch('colorMix(aliceblue, 0.25)')\n makeSwatch('colorMix(aliceblue, 0.5)')\n makeSwatch('colorMix(aliceblue, 0.75)')\n}\n\nfunction randomColor() {\n colorInput.value = Color.fromHsl(Math.random() * 360, Math.random(), Math.random() * 0.5 + 0.25)\n update()\n}\n\nrandomColor()\n\npreview.append(\n label(\n span('base color'),\n colorInput\n ),\n button(\n 'Random(ish) Color',\n {\n onClick: randomColor\n }\n ),\n swatches\n)\n```\n```css\n.preview .swatches {\n display: flex;\n gap: 4px;\n padding: 4px;\n flex-wrap: wrap;\n font-size: 80%;\n}\n.preview .swatch {\n display: inline-block;\n padding: 2px 6px;\n color: var(--text);\n background: var(--adjusted);\n border: 2px solid var(--original);\n}\n```\n\nEach of these methods creates a new color instance based on the existing color(s).\n\nIn each case `amount` is from 0 to 1, and `degrees` is an angle in degrees.\n\n- `brighten(amount: number)`\n- `darken(amount: number)`\n- `saturate(amount: number)`\n- `desaturate(amount: number)`\n- `rotate(angle: number)`\n- `opacity(amount: number)` — this just creates a color with that opacity (it doesn't adjust it)\n- `mix(otherColor: Color, amount)` — produces a mix of the two colors in HSL-space\n- `colorMix(otherColor: Color, amount)` — uses `color-mix(in hsl...)` to blend the colors\n- `blend(otherColor: Color, amount)` — produces a blend of the two colors in RGB-space (usually icky)\n- `contrasting(amount = 1)` — produces a **contrasting color** by blending the color with black (if its\n `brightness` is > 0.5) or white by `amount`. The new color will always have opacity 1.\n `contrasting()` produce nearly identical results to `contrast-color()`.\n\n> **Note** the captions in the example above are colored using `contrasting()` and thus\n> should always be readable. In general, a base color will produce the worst results when\n> its `brightness` is around 0.5, much as is the case with the new and experimental CSS\n> [contrast-color()](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/contrast-color)\n> function.\n>\n> **Also note** that highly translucent colors might produce disappointing `.contrasting()`\n> results since it's the blended color you need to worry about.\n\nWhere-ever possible, unless otherwise indicated, all of these operations are performed in HSL-space.\nHSL space is not great! For example, `desaturate` essentially blends you with medium gray (`#888`)\nrather than a BT.601 `brightness` value where \"yellow\" is really bright and \"blue\" is really dark.\n\nIf you want to desaturate colors more nicely, you can try blending them with their own `mono`.\n\n## Static Methods\n\nThese are alternatives to the standard `Color(r, g, b, a = 1)` constructor.\n\n`Color.fromVar(cssVariableName: string, element = document.body): Color` evaluates\nthe color at the specified element and then returns a `Color` instance with that\nvalue. It will accept both bare variable names (`--foo-bar`) and wrapped (`var(--foo-bar)`).\n\n`Color.fromCss(cssColor: string): Color` produces a `Color` instance from any\ncss color definition the browser can handle.\n\n`Color.fromHsl(h: number, s: number, l: number, a = 1)` produces a `Color`\ninstance from HSL/HSLA values. The HSL values are cached internally and\nused for internal calculations to reduce precision problems that occur\nwhen converting HSL to RGB and back. It's nowhere near as sophisticated as\nthe models used by (say) Adobe or Apple, but it's less bad than doing all\ncomputations in rgb.\n\n## Static Properties\n\n- `black`, `white` — handy constants\n\n## Properties\n\n- `r`, `g`, `b` are numbers from 0 to 255.\n- `a` is a number from 0 to 1\n\n## Properties (read-only)\n\n- `html` — the color in HTML `#rrggbb[aa]` format\n- `inverse` — the photonegative of the color (light is dark, orange is blue)\n- `opaque` - the color, but guaranteed opaque\n- `inverseLuminance` — inverts luminance but keeps hue, great for \"dark mode\"\n- `rgb` and `rgba` — the color in `rgb(...)` and `rgba(...)` formats.\n- `hsl` and `hsla` — the color in `hsl(...)` and `hsla(...)` formats.\n- `RGBA` and `ARGB` — return the values as arrays of numbers from 0 to 1 for use with\n [WebGL](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API) (for example).\n- `brightness` — this is the brightness of the color based on [BT.601](https://www.itu.int/rec/R-REC-BT.601)\n- `mono` — this produces a `Color` instance that a greyscale version (based on `brightness`)\n\n## Utilities\n\n- `swatch()` emits the color into the console with a swatch and returns the color for chaining.\n- `toString()` emits the `html` property\n*/\n\nimport { lerp, clamp } from './more-math'\nimport { getCssVar } from './get-css-var'\nimport { CSSSystemColor } from './css-system-color'\n\n// http://www.itu.int/rec/R-REC-BT.601\nconst bt601 = (r: number, g: number, b: number): number => {\n return (0.299 * r + 0.587 * g + 0.114 * b) / 255\n}\n\nconst hex2 = (n: number): string =>\n ('00' + Math.round(Number(n)).toString(16)).slice(-2)\n\nclass HslColor {\n h: number\n s: number\n l: number\n\n constructor(r: number, g: number, b: number) {\n r /= 255\n g /= 255\n b /= 255\n const l = Math.max(r, g, b)\n const s = l - Math.min(r, g, b)\n const h =\n s !== 0\n ? l === r\n ? (g - b) / s\n : l === g\n ? 2 + (b - r) / s\n : 4 + (r - g) / s\n : 0\n\n this.h = 60 * h < 0 ? 60 * h + 360 : 60 * h\n this.s = s !== 0 ? (l <= 0.5 ? s / (2 * l - s) : s / (2 - (2 * l - s))) : 0\n this.l = (2 * l - s) / 2\n }\n}\n\nconst span =\n globalThis.document !== undefined\n ? globalThis.document.createElement('span')\n : undefined\nexport class Color {\n r: number\n g: number\n b: number\n a: number\n\n static fromVar(varName: string, element = document.body): Color {\n return Color.fromCss(getCssVar(varName, element))\n }\n\n static fromCss(spec: CSSSystemColor | string): Color {\n let converted = spec\n if (span instanceof HTMLSpanElement) {\n span.style.color = 'black'\n span.style.color = spec\n document.body.appendChild(span)\n converted = getComputedStyle(span).color\n span.remove()\n }\n const [r, g, b, a] = (converted.match(/[\\d.]+/g) as string[]) || [\n '0',\n '0',\n '0',\n '0',\n ]\n const scale = converted.startsWith('color(srgb') ? 255 : 1\n return new Color(\n Number(r) * scale,\n Number(g) * scale,\n Number(b) * scale,\n a == null ? 1 : Number(a)\n )\n }\n\n static fromHsl(h: number, s: number, l: number, a = 1): Color {\n let r: number, g: number, b: number\n\n if (s === 0) {\n r = g = b = l // achromatic\n } else {\n const hue2rgb = (p: number, q: number, t: number): number => {\n if (t < 0) t += 1\n if (t > 1) t -= 1\n if (t < 1 / 6) return p + (q - p) * 6 * t\n if (t < 1 / 2) return q\n if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6\n return p\n }\n\n const q = l < 0.5 ? l * (1 + s) : l + s - l * s\n const p = 2 * l - q\n const hNormalized = (((h % 360) + 360) % 360) / 360\n r = hue2rgb(p, q, hNormalized + 1 / 3)\n g = hue2rgb(p, q, hNormalized)\n b = hue2rgb(p, q, hNormalized - 1 / 3)\n }\n\n const color = new Color(r * 255, g * 255, b * 255, a)\n // Cache the HSL value to avoid re-calculating it later\n color.hslCached = { h: ((h % 360) + 360) % 360, s, l }\n return color\n }\n\n static black = new Color(0, 0, 0)\n static white = new Color(255, 255, 255)\n\n constructor(r: number, g: number, b: number, a = 1) {\n this.r = clamp(0, r, 255)\n this.g = clamp(0, g, 255)\n this.b = clamp(0, b, 255)\n this.a = clamp(0, a, 1)\n }\n\n get inverse(): Color {\n return new Color(255 - this.r, 255 - this.g, 255 - this.b, this.a)\n }\n\n get inverseLuminance(): Color {\n const { h, s, l } = this._hsl\n return Color.fromHsl(h, s, 1 - l, this.a)\n }\n\n get opaque(): Color {\n return this.a === 1 ? this : new Color(this.r, this.g, this.b, 1)\n }\n\n contrasting(amount = 1): Color {\n return this.opaque.blend(\n this.brightness > 0.5 ? Color.black : Color.white,\n amount\n )\n }\n\n get rgb(): string {\n const { r, g, b } = this\n return `rgb(${r.toFixed(0)},${g.toFixed(0)},${b.toFixed(0)})`\n }\n\n get rgba(): string {\n const { r, g, b, a } = this\n return `rgba(${r.toFixed(0)},${g.toFixed(0)},${b.toFixed(0)},${a.toFixed(\n 2\n )})`\n }\n\n get RGBA(): number[] {\n return [this.r / 255, this.g / 255, this.b / 255, this.a]\n }\n\n get ARGB(): number[] {\n return [this.a, this.r / 255, this.g / 255, this.b / 255]\n }\n\n private hslCached?: HslColor\n\n get _hsl(): HslColor {\n if (this.hslCached == null) {\n this.hslCached = new HslColor(this.r, this.g, this.b)\n }\n return this.hslCached\n }\n\n get hsl(): string {\n const { h, s, l } = this._hsl\n return `hsl(${h.toFixed(0)}deg ${(s * 100).toFixed(0)}% ${(l * 100).toFixed(\n 0\n )}%)`\n }\n\n get hsla(): string {\n const { h, s, l } = this._hsl\n return `hsl(${h.toFixed(0)}deg ${(s * 100).toFixed(0)}% ${(l * 100).toFixed(\n 0\n )}% / ${(this.a * 100).toFixed(0)}%)`\n }\n\n get mono(): Color {\n const v = this.brightness * 255\n return new Color(v, v, v)\n }\n\n get brightness(): number {\n return bt601(this.r, this.g, this.b)\n }\n\n get html(): string {\n return this.toString()\n }\n\n toString(): string {\n return this.a === 1\n ? '#' + hex2(this.r) + hex2(this.g) + hex2(this.b)\n : '#' +\n hex2(this.r) +\n hex2(this.g) +\n hex2(this.b) +\n hex2(Math.floor(255 * this.a))\n }\n\n brighten(amount: number): Color {\n const { h, s, l } = this._hsl\n const lClamped = clamp(0, l + amount * (1 - l), 1)\n return Color.fromHsl(h, s, lClamped, this.a)\n }\n\n darken(amount: number): Color {\n const { h, s, l } = this._hsl\n const lClamped = clamp(0, l * (1 - amount), 1)\n return Color.fromHsl(h, s, lClamped, this.a)\n }\n\n saturate(amount: number): Color {\n const { h, s, l } = this._hsl\n const sClamped = clamp(0, s + amount * (1 - s), 1)\n return Color.fromHsl(h, sClamped, l, this.a)\n }\n\n desaturate(amount: number): Color {\n const { h, s, l } = this._hsl\n const sClamped = clamp(0, s * (1 - amount), 1)\n return Color.fromHsl(h, sClamped, l, this.a)\n }\n\n rotate(amount: number): Color {\n const { h, s, l } = this._hsl\n const hClamped = (h + 360 + amount) % 360\n return Color.fromHsl(hClamped, s, l, this.a)\n }\n\n opacity(alpha: number): Color {\n const { h, s, l } = this._hsl\n return Color.fromHsl(h, s, l, alpha)\n }\n\n swatch(): Color {\n console.log(\n `%c %c ${this.html}, ${this.rgba}`,\n `background-color: ${this.html}`,\n 'background-color: transparent'\n )\n return this\n }\n\n blend(otherColor: Color, t: number): Color {\n return new Color(\n lerp(this.r, otherColor.r, t),\n lerp(this.g, otherColor.g, t),\n lerp(this.b, otherColor.b, t),\n lerp(this.a, otherColor.a, t)\n )\n }\n\n static blendHue(a: number, b: number, t: number): number {\n const delta = (b - a + 720) % 360\n if (delta < 180) {\n return a + t * delta\n } else {\n return a - (360 - delta) * t\n }\n }\n\n mix(otherColor: Color, t: number): Color {\n const a = this._hsl\n const b = otherColor._hsl\n return Color.fromHsl(\n a.s === 0 ? b.h : b.s === 0 ? a.h : Color.blendHue(a.h, b.h, t),\n lerp(a.s, b.s, t),\n lerp(a.l, b.l, t),\n lerp(this.a, otherColor.a, t)\n )\n }\n\n colorMix(otherColor: Color, t: number): Color {\n return Color.fromCss(\n `color-mix(in hsl, ${this.html}, ${otherColor.html} ${(t * 100).toFixed(\n 0\n )}%)`\n )\n }\n}\n",
|
|
19
|
+
"/*#\n# 5. css\n\n`tosijs` provides a collection of utilities for working with CSS rules that\nhelp leverage CSS variables to produce highly maintainable and lightweight\ncode that is nonetheless easy to customize.\n\nThe basic goal is to be able to implement some or all of our CSS very efficiently, compactly,\nand reusably in Javascript because:\n\n- Javascript quality tooling is really good, CSS quality tooling is terrible\n- Having to write CSS in Javascript is *inevitable* so it might as well be consistent and painless\n- It turns out you can get by with *much less* and generally *simpler* CSS this way\n- You get some natural wins this way. E.g. writing two definitions of `body {}` is easy to do\n and bad in CSS. In Javascript it's simply an error!\n\nThe `css` module attempts to implement all this the simplest and most obvious way possible,\nproviding syntax sugar to help with best-practices such as `css-variables` and the use of\n`@media` queries to drive consistency, themes, and accessibility.\n\n## css(styleMap: XinStyleMap): string\n\nA function that, given a `XinStyleMap` renders CSS code. What is a XinStyleMap?\nIt's kind of what you'd expect if you wanted to represent CSS as Javascript in\nthe most straightforward way possible. It allows for things like `@import`,\n`@keyframes` and so forth, but knows just enough about CSS to help with things\nlike autocompletion of CSS rules (rendered as camelcase) so that, unlike me, it\ncan remind you that it's `whiteSpace` and not `whitespace`.\n\n```\nimport {elements, css} from 'xinjs'\nconst {style} = elements\n\nconst myStyleMap = {\n body: {\n color: 'red'\n },\n button: {\n borderRadius: 5\n }\n}\n\ndocument.head.append(style(css(myStyleMap)))\n```\n\nThere's a convenient `Stylesheet()` function that does all this and adds an id to the\nresulting `<style>` element to make it easier to figure out where a given stylesheet\ncame from.\n\n```\nStylesheet('my-styles', {\n body: {\n color: 'red'\n },\n button: {\n borderRadius: 5\n }\n})\n```\n\n…inserts the following in the `document.head`:\n\n```\n<style id=\"my-styles\">\nbody {\n color: red;\n}\nbutton {\n border-radius: 5px;\n}\n</style>\n```\n\nIf a bare, non-zero **number** is assigned to a CSS property it will have 'px' suffixed\nto it automatically. There are *no bare numeric*ele properties in CSS except `0`.\n\nWhy `px`? Well the other obvious options would be `rem` and `em` but `px` seems the\nleast surprising option.\n\n`css` should render nested rules, such as `@keyframes` and `@media` correctly.\n\n## Initializing CSS Variables\n\nYou can initialize CSS variables using `_` or `__` prefixes on property names.\nOne bar, turns the camelCase property-name into a --snake-case CSS variable\nname, while two creates a default that can be overridden.\n\n```\nStyleSheet('my-theme', {\n ':root', {\n _fooBar: 'red',\n __bazBar: '10px'\n }\n})\n```\n\nWill produce:\n\n```\n<style id=\"my-theme\">\n :root {\n --foo-bar: red;\n --baz-bar: var(--baz-bar-default, 10px);\n }\n</style>\n```\n```js\nimport { elements, vars } from 'tosijs'\nconst { div } = elements\n\nwindow.CSS.registerProperty({\n name: '--at-bar',\n syntax: '<color>',\n inherits: true,\n initialValue: 'green',\n})\n\npreview.append(\n div(\n {\n style: {\n _fooBar: 'red',\n __bazBar: 'blue',\n }\n },\n div(\n {\n style: { color: vars.fooBar },\n },\n 'fooBar'\n ),\n div(\n {\n style: { color: vars.bazBar },\n },\n 'bazBar'\n ),\n div(\n {\n style: { color: vars.atBar },\n },\n 'atBar'\n ),\n )\n)\n```\n\n> ### @property and CSS.registerProperty() considered harmful\n>\n> This [new CSS feature}(https://developer.mozilla.org/en-US/docs/Web/CSS/@property)\n> is well-intentioned but ill-considered. I advise\n> against using it yourself until its serious flaws are addressed. The problem\n> is that if someone registers a variable you're using or you register\n> a variable someone else is using then your CSS may be broken. And\n> you can't re-register a variable either.\n\n> This is a bit like the problem\n> that xinjs Component works around with tagNames, but in practice far more\n> difficult to solve. It is impossible to tell if a given instance of\n> a given variable name is an intentional reuse or a new separate variable.\n> No-one intentionally defines two different components with the same tag.\n\n## invertLuminance({[key: string]: any}) => {[key: string]: string}\n\nGiven a map of CSS properties (in camelCase) emit a map of those properties that\nhas color values with their luminance inverted.\n\n const myStyleMap = {\n ':root': cssVars, // includes --font-size\n '@media (prefers-color-scheme: dark)': {\n ':root': invertLuminance(cssVars) // omits --font-size\n },\n }\n\n## vars\n\n`vars` is a proxy object that will return a css variable string from\na camelCase property, e.g.\n\n vars.camelCase // 'var(--camel-case)'\n\n> **it isn't called `var`** because that's a reserved word!\n\n### varDefault\n\n`varDefault` is a proxy object just like `vars` except that it returns a\n`function` that takes a property and renders it as a css variable reference\nwith a default, e.g\n\n varDefault.borderColor('red') // `var(--border-color, red)`\n\n## `getCssVar(variable: string, atElement = document.body): string`\n\n`getCssVar()` obtains the css variable evaluated at the specified element\n(an element defined at `:root` can be evaluated at `document.body`). You\ncan provide the name, e.g. `--foo-bar`, or \"wrapped\", e.g. `var(--foo-bar)`.\n\n### Syntax Sugar for `calc(...)`\n\nMore importantly, `vars` allows you to conveniently perform calculations\non css (dimensional) variables by a percentage:\n\n vars.camelSize50 // 'calc(var(--camel-size) * 0.5)'\n vars.camelSize_50 // 'calc(var(--camel-size) * -0.5)'\n\n### Computed Colors\n\n> #### Notes\n>\n> `color()` and `color-mix()` are [now enjoy 91% support](https://caniuse.com/?search=color-mix) as of writing.\n> See [color-mix()](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color-mix) documentation.\n> Where they meet your needs, I'd suggest using them.\n>\n> [contrast-color()](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/contrast-color) is coming in Safari 26,\n> but [currently enjoys 0% upport](https://caniuse.com/?search=contrast-color).\n>\n> **Caution** although these look superficially like the `vars` syntax\n> sugar for `calc()` performed on dimensional variables, they are in fact\n> color calculations are performed on colors *evaluated* on `document.body` at\n> execution time. (So they won'b automatically be recomputed on theme change.)\n\nYou can write:\n\n```\nconst styleSpec = {\n _lineHeight: 24,\n _spacing: 5,\n _buttonHeight: calc(`vars.lineHeight + vars.spacing200`)\n)\n```\n\nAnd then render this as CSS and stick it into a StyleNode and it will work.\n\nYou *cannot* write:\n\n```\nconst styleSpec = {\n _background: '#fafafa',\n _blockColor: vars.background_5b\n}\n```\n\nBecause `--background` isn't defined on `document.body` yet, so vars.background_5b\nwon't be able to tell what `--background` is going to be yet. So either you need to\ndo this in two stags (create a StyleNode that defines the base color `--background`\nthen define the computed colors and add this) OR use a `Color` instance:\n\n```\nconst background = Color.fromCss('#fafafa')\n\ninitVars({\n background: background.toHTML,\n blockColor: background.brighten(-0.05).toHTML\n})\n```\n\nUntil browsers support color calculations the way they support dimension arithmetic with `calc()`\nthis is the miserable existence we all lead. That, or defining huge arrays of color\nvalues that we mostly don't use and are often not exactly what we want. You choose!\n\n> **New** color now supports CSS [named colors](https://developer.mozilla.org/en-US/docs/Web/CSS/named-color),\nsuch as `black`, `red`, and `aliceblue`.\n\n`vars` also allows you to perform color calculations on css (color)\nvariables:\n\n#### Change luminance with `b` (for brighten) suffix\n\nThe scale value is treated as a percentage and moves the brightness\nthat far from its current value to 100% (if positive) or 0% (if negattive).\n\n vars.textColor50b // increases the luminance of textColor\n vars.textColor_50b // halves the luminance of textColor\n\n#### Change saturation with `s` suffix\n\nThe scale value is treated as a percentage and moves the saturation\nthat far from its current value to 100% (if positive) or 0% (if negattive).\n\n vars.textColor50s // increases the saturation of textColor\n vars.textColor_50s // halves the saturation of textColor\n\n#### Rotate hue with `h` suffix\n\n vars.textColor30h // rotates the hue of textColor by 30°\n vars.textColor_90h // rotates the hue of textColor by -90°\n\n#### Set Opacity with `o` suffix\n\nUnlike the other modifiers, `o` simply sets the opacity of the\nresulting color to the value provided.\n\n vars.textColor50o // textColor with opacity set to 0.5\n\n## More to follow?\n\nThe more I use the `css` module, the more I like it and the more ideas I have\nto make it even better, but I have a very tight size/complexity target\nfor `tosijs` so these new ideas really have to earn a spot. Perhaps the\nfeature I have come closest to adding and then decided against was providing\nsyntax-sugar for classs so that:\n\n css({\n _foo: {\n color: 'red'\n }\n })\n\nWould render:\n\n .foo {\n color: 'red'\n }\n\nBut looking at the code I and others have written, the case for this is weak as most class\ndeclarations are not just bare classes. This doesn't help with declarations\nfor `input.foo` or `.foo::after` or `.foo > *` and now there'd be things that\nlook different which violates the \"principle of least surprise\". So, no.\n\n### Something to Declare\n\nWhere I am always looking to improve this module (and all of `tosijs`) is to\ndo a better job of **declaring** things to improve autocomplete behavior and\nminimize casting and other Typescript antipatterns. E.g. adding a ton of\ndeclarations to `elements` and `css` has done wonders to reduce the need for\nstuff like `const nameElement = this.parts.nameField as unknown as HTMLInputElement`\nand prevent css property typos without adding a single byte to the size of\nthe javascript payload.\n*/\nimport { Color } from './color'\nimport { elements } from './elements'\nimport { camelToKabob } from './string-case'\nimport { XinStyleSheet, XinStyleRule } from './css-types'\n\nexport function StyleSheet(id: string, styleSpec: XinStyleSheet) {\n const element = elements.style(css(styleSpec))\n element.id = id\n document.head.append(element)\n}\n\nconst numericProps = [\n 'animation-iteration-count',\n 'flex',\n 'flex-base',\n 'flex-grow',\n 'flex-shrink',\n 'opacity',\n 'order',\n 'tab-size',\n 'widows',\n 'z-index',\n 'zoom',\n]\n\nexport const processProp = (\n prop: string,\n value: string | number\n): { prop: string; value: string } => {\n if (typeof value === 'number' && !numericProps.includes(prop)) {\n value = `${value}px`\n }\n if (prop.startsWith('_')) {\n if (prop.startsWith('__')) {\n prop = '--' + prop.substring(2)\n value = `var(${prop}-default, ${value})`\n } else {\n prop = '--' + prop.substring(1)\n }\n }\n return {\n prop,\n value: String(value),\n }\n}\n\nconst renderProp = (\n indentation: string,\n cssProp: string,\n value: string | number | Color | undefined\n): string => {\n if (value === undefined) {\n return ''\n }\n if (value instanceof Color) {\n value = value.html\n }\n const processed = processProp(cssProp, value)\n return `${indentation} ${processed.prop}: ${processed.value};`\n}\n\nconst renderStatement = (\n key: string,\n value: Color | string | number | XinStyleRule | undefined,\n indentation = ''\n): string => {\n const cssProp = camelToKabob(key)\n if (typeof value === 'object' && !(value instanceof Color)) {\n const renderedRule = Object.keys(value)\n .map((innerKey) =>\n renderStatement(innerKey, value[innerKey], `${indentation} `)\n )\n .join('\\n')\n return `${indentation} ${key} {\\n${renderedRule}\\n${indentation} }`\n } else {\n return renderProp(indentation, cssProp, value)\n }\n}\n\nexport const css = (obj: XinStyleSheet, indentation = ''): string => {\n const selectors = Object.keys(obj).map((selector) => {\n const body = obj[selector]\n if (typeof body === 'string') {\n if (selector === '@import') {\n return `@import url('${body}');`\n }\n throw new Error('top-level string value only allowed for `@import`')\n }\n const rule = Object.keys(body)\n .map((prop) => renderStatement(prop, body[prop]))\n .join('\\n')\n return `${indentation}${selector} {\\n${rule}\\n}`\n })\n return selectors.join('\\n\\n')\n}\n\nexport const initVars = (obj: {\n [key: string]: string | number\n}): XinStyleRule => {\n console.warn('initVars is deprecated. Just use _ and __ prefixes instead.')\n const rule: XinStyleRule = {}\n for (const key of Object.keys(obj)) {\n const value = obj[key]\n const kabobKey = camelToKabob(key)\n rule[`--${kabobKey}`] =\n typeof value === 'number' && value !== 0 ? String(value) + 'px' : value\n }\n return rule\n}\n\nexport const invertLuminance = (map: XinStyleRule): XinStyleRule => {\n const inverted: XinStyleRule = {}\n\n for (const key of Object.keys(map)) {\n const value = map[key]\n if (value instanceof Color) {\n inverted[key] = value.inverseLuminance\n } else if (\n typeof value === 'string' &&\n value.match(/^(#[0-9a-fA-F]{3}|rgba?\\(|hsla?\\()/)\n ) {\n inverted[key] = Color.fromCss(value).inverseLuminance\n }\n }\n\n return inverted\n}\n\nexport const varDefault = new Proxy<{ [key: string]: CssVarBuilder }>(\n {},\n {\n get(target, prop: string) {\n if (target[prop] === undefined) {\n const varName = '--' + camelToKabob(prop)\n target[prop] = (val: string | number) => `var(${varName}, ${val})`\n }\n return target[prop]\n },\n }\n)\n\ntype VarsType = {\n default: typeof varDefault\n} & {\n [key: string]: string\n}\n\nexport const vars = new Proxy<VarsType>({} as VarsType, {\n get(target, prop: string) {\n if (prop === 'default') {\n return varDefault\n }\n if (target[prop] == null) {\n prop = camelToKabob(prop)\n const [, _varName, , isNegative, scaleText, method] = prop.match(\n /^([-\\w]*?)((_)?(\\d+)(\\w?))?$/\n ) || ['', prop]\n const varName = `--${_varName}`\n if (scaleText != null) {\n const scale =\n isNegative == null\n ? Number(scaleText) / 100\n : -Number(scaleText) / 100\n switch (method) {\n case 'b': // brighten\n {\n const baseColor = Color.fromVar(varName)\n target[prop] =\n scale > 0\n ? baseColor.brighten(scale).rgba\n : baseColor.darken(-scale).rgba\n }\n break\n case 's': // saturate\n {\n const baseColor = Color.fromVar(varName)\n target[prop] =\n scale > 0\n ? baseColor.saturate(scale).rgba\n : baseColor.desaturate(-scale).rgba\n }\n break\n case 'h': // hue\n {\n const baseColor = Color.fromVar(varName)\n target[prop] = baseColor.rotate(scale * 100).rgba\n }\n break\n case 'o': // alpha\n {\n const baseColor = Color.fromVar(varName)\n target[prop] = baseColor.opacity(scale).rgba\n }\n break\n case '':\n target[prop] = `calc(var(${varName}) * ${scale})`\n break\n default:\n console.error(method)\n throw new Error(\n `Unrecognized method ${method} for css variable ${varName}`\n )\n }\n } else {\n target[prop] = `var(${varName})`\n }\n }\n return target[prop]\n },\n})\n\ntype CssVarBuilder = (val: string | number) => string\n",
|
|
20
|
+
"import { ElementCreator } from './xin-types'\n\nexport const MATH = 'http://www.w3.org/1998/Math/MathML'\nexport const SVG = 'http://www.w3.org/2000/svg'\nexport interface ElementsProxy {\n a: ElementCreator<HTMLAnchorElement>\n abbr: ElementCreator\n acronym: ElementCreator\n address: ElementCreator\n area: ElementCreator<HTMLAreaElement>\n article: ElementCreator\n aside: ElementCreator\n audio: ElementCreator<HTMLAudioElement>\n b: ElementCreator\n base: ElementCreator<HTMLBaseElement>\n basefont: ElementCreator\n bdi: ElementCreator\n bdo: ElementCreator\n big: ElementCreator\n blockquote: ElementCreator<HTMLQuoteElement>\n body: ElementCreator<HTMLBodyElement>\n br: ElementCreator<HTMLBRElement>\n button: ElementCreator<HTMLButtonElement>\n canvas: ElementCreator<HTMLCanvasElement>\n caption: ElementCreator\n center: ElementCreator\n cite: ElementCreator\n code: ElementCreator\n col: ElementCreator<HTMLTableColElement>\n colgroup: ElementCreator<HTMLTableColElement>\n data: ElementCreator<HTMLDataElement>\n datalist: ElementCreator<HTMLDataListElement>\n dd: ElementCreator\n del: ElementCreator\n details: ElementCreator<HTMLDetailsElement>\n dfn: ElementCreator\n dialog: ElementCreator<HTMLDialogElement>\n div: ElementCreator<HTMLDivElement>\n dl: ElementCreator\n dt: ElementCreator\n em: ElementCreator\n embed: ElementCreator<HTMLEmbedElement>\n fieldset: ElementCreator<HTMLFieldSetElement>\n figcaption: ElementCreator\n figure: ElementCreator\n font: ElementCreator\n footer: ElementCreator\n form: ElementCreator<HTMLFormElement>\n frame: ElementCreator\n frameset: ElementCreator\n head: ElementCreator<HTMLHeadElement>\n header: ElementCreator\n hgroup: ElementCreator\n h1: ElementCreator<HTMLHeadingElement>\n h2: ElementCreator<HTMLHeadingElement>\n h3: ElementCreator<HTMLHeadingElement>\n h4: ElementCreator<HTMLHeadingElement>\n h5: ElementCreator<HTMLHeadingElement>\n h6: ElementCreator<HTMLHeadingElement>\n hr: ElementCreator<HTMLHRElement>\n html: ElementCreator<HTMLHtmlElement>\n i: ElementCreator\n iframe: ElementCreator<HTMLIFrameElement>\n img: ElementCreator<HTMLImageElement>\n input: ElementCreator<HTMLInputElement>\n ins: ElementCreator<HTMLModElement>\n kbd: ElementCreator\n keygen: ElementCreator<HTMLUnknownElement>\n label: ElementCreator<HTMLLabelElement>\n legend: ElementCreator<HTMLLegendElement>\n li: ElementCreator<HTMLLIElement>\n link: ElementCreator<HTMLLinkElement>\n main: ElementCreator\n map: ElementCreator<HTMLMapElement>\n mark: ElementCreator\n menu: ElementCreator<HTMLMenuElement>\n menuitem: ElementCreator<HTMLUnknownElement>\n meta: ElementCreator<HTMLMetaElement>\n meter: ElementCreator<HTMLMeterElement>\n nav: ElementCreator\n noframes: ElementCreator\n noscript: ElementCreator\n object: ElementCreator<HTMLObjectElement>\n ol: ElementCreator<HTMLOListElement>\n optgroup: ElementCreator<HTMLOptGroupElement>\n option: ElementCreator<HTMLOptionElement>\n output: ElementCreator<HTMLOutputElement>\n p: ElementCreator<HTMLParagraphElement>\n param: ElementCreator\n picture: ElementCreator<HTMLPictureElement>\n pre: ElementCreator<HTMLPreElement>\n progress: ElementCreator<HTMLProgressElement>\n q: ElementCreator<HTMLQuoteElement>\n rp: ElementCreator\n rt: ElementCreator\n ruby: ElementCreator\n s: ElementCreator\n samp: ElementCreator\n script: ElementCreator<HTMLScriptElement>\n section: ElementCreator\n select: ElementCreator<HTMLSelectElement>\n slot: ElementCreator<HTMLSlotElement>\n small: ElementCreator\n source: ElementCreator<HTMLSourceElement>\n span: ElementCreator<HTMLSpanElement>\n strike: ElementCreator\n strong: ElementCreator\n style: ElementCreator<HTMLStyleElement>\n sub: ElementCreator\n summary: ElementCreator\n table: ElementCreator<HTMLTableElement>\n tbody: ElementCreator<HTMLTableSectionElement>\n td: ElementCreator<HTMLTableCellElement>\n template: ElementCreator<HTMLTemplateElement>\n textarea: ElementCreator<HTMLTextAreaElement>\n tfoot: ElementCreator<HTMLTableSectionElement>\n th: ElementCreator<HTMLTableCellElement>\n thead: ElementCreator<HTMLTableSectionElement>\n time: ElementCreator<HTMLTimeElement>\n title: ElementCreator<HTMLTitleElement>\n tr: ElementCreator<HTMLTableRowElement>\n track: ElementCreator<HTMLTrackElement>\n tt: ElementCreator\n u: ElementCreator\n ul: ElementCreator<HTMLUListElement>\n var: ElementCreator\n video: ElementCreator<HTMLVideoElement>\n wbr: ElementCreator\n [key: string | symbol]: ElementCreator<any>\n}\n",
|
|
21
|
+
"/*#\n# 3. elements\n\n`tosijs` provides `elements` for easily and efficiently generating DOM elements\nwithout using `innerHTML` or other unsafe methods.\n\n```js\nimport { elements } from 'tosijs'\n\nconst { div, input, label, span } = elements\n\npreview.append(\n div(\n {\n style: {\n display: 'flex',\n flexDirection: 'column',\n padding: 10,\n gap: 10\n }\n },\n label(\n {\n style: {\n display: 'inline-flex'\n }\n },\n span('text'),\n input({value: 'hello world', placeholder: 'type something'})\n ),\n label(\n {\n style: {\n display: 'inline-flex'\n }\n },\n span('checkbox'),\n input({type: 'checkbox', checked: true})\n )\n )\n)\n```\n\n## `ElementCreator` functions\n\n`elements` is a proxy whose properties are element factory functions,\nreferred to throughout this documentation as `elementCreator`s, functions\nof type `ElementCreator`. So `elements.div` is a function that returns a `<div>`\nelement, `elements.foo` creates <foo> elements, and elements.fooBar creates\n`<foo-bar>` elements.\n\nThe arguments of `elementCreator`s can be strings, numbers, other\nelements, or property-maps, which are converted into attributes or properties\n(or bindings).\n\nE.g.\n\n```js\nimport { elements, tosi } from 'tosijs'\n\nconst { elementCreatorDemo } = tosi({\n elementCreatorDemo: {\n isChecked: true,\n someString: 'hello elementCreator',\n someColor: 'blue',\n clicks: 0\n }\n})\n\nconst { div, button, label, input } = elements\n\npreview.append(\n div('I am a div'),\n div(\n {\n style: { color: 'blue' }\n },\n elementCreatorDemo.someString\n ),\n label(\n 'Edit someString',\n input({bindValue: elementCreatorDemo.someString})\n ),\n div(\n button(\n 'Click me',\n {\n onClick() {\n elementCreatorDemo.clicks += 1\n }\n }\n ),\n div(elementCreatorDemo.clicks, ' clicks so far'),\n ),\n label(\n 'isChecked?',\n input({type: 'checkbox', bindValue: elementCreatorDemo.isChecked})\n )\n)\n```\n\n## camelCase conversion\n\nAttributes in camelCase, e.g. `dataInfo`, will be converted to kebab-case,\nso:\n\n span({dataInfo: 'foo'}) // produces <span data-info=\"foo\"></span>\n\n## style properties\n\n`style` properties can be objects, and these are used to modify the\nelement's `style` object (while a string property will just change the\nelement's `style` attribute, eliminating previous changes).\n\n span({style: 'border: 1px solid red'}, {style: 'font-size: 15px'})\n\n…produces `<span style=\"font-size: 15px\"></span>`, which is probably\nnot what was wanted.\n\n span({style: {border: '1px solid red'}, {style: {fontSize: '15px'}}})\n\n…produces `<span style=\"border: 1px solid red; fon-size: 15px></span>`\nwhich is probably what was wanted.\n\n## event handlers\n\nProperties starting with `on` (followed by an uppercase letter)\nwill be converted into event-handlers, so `onMouseup` will be\nturned into a `mouseup` listener.\n\n## binding\n\nYou can [bind](/?bind.ts) an element to state using [bindings](/?bindings.ts)\nusing convenient properties, e.g.\n\n import { elements } from 'tosijs'\n const {div} = elements\n div({ bindValue: 'app.title' })\n\n…is syntax sugar for:\n\n import { elements, bind, bindings } from 'tosijs'\n const { div } = elements\n bind( div(), 'app.title', bindings.value )\n\nIf you want to use your own bindings, you can use `apply`:\n\n const visibleBinding = {\n toDOM(element, value) {\n element.classList.toggle('hidden', !value)\n }\n }\n\n div({ apply(elt){\n bind(elt, 'app.prefs.isVisible', visibleBinding})\n } })\n\n## event-handlers\n\nYou can attach event handlers to elements using `on<EventType>`\nas syntax sugar, e.g.\n\n import { elements } from 'tosijs'\n const { button } = elements\n document.body.append(\n button('click me', {onClick() {\n alert('clicked!')\n }})\n )\n\n…is syntax sugar for:\n\n import { elements, on } from 'tosijs'\n const { button } = elements\n const aButton = button('click me')\n on(aButton, 'click', () => {\n alert('clicked!')\n })\n document.body.append(\n aButton\n )\n\nThere are some subtle but important differences between `on()` and\n`addEventListener` which are discussed in detail in the section on\n[bind](/?bind.ts).\n\n## apply\n\nA property named `apply` is assumed to be a function that will be called\non the element.\n\n span({\n apply(element){ element.textContent = 'foobar'}\n })\n\n…produces `<span>foobar</span>`.\n\n## fragment\n\n`elements.fragment` is produces `DocumentFragment`s, but is otherwise\njust like other element factory functions.\n\n## svgElements\n\n`svgElements` is a proxy just like `elements` but it produces **SVG** elements in\nthe appropriate namespace.\n\n## mathML\n\n`mathML` is a proxy just like `elements` but it products **MathML** elements in\nthe appropriate namespace.\n\n> ### Caution\n>\n> Both `svgElements` and `mathML` are experimental and do not have anything like the\n> degree of testing behind them as `elements`. In particular, the properties of\n> SVG elements (and possible MathML elements) are quite different from ordinary\n> elements, so the underlying `ElementCreator` will never try to set properties\n> directly and will always use `setAttribute(...)`.\n>\n> E.g. `svgElements.svg({viewBox: '0 0 100 100'})` will call `setAttribute()` and\n> not set the property directly, because the `viewBox` property is… weird, but\n> setting the attribute works.\n>\n> Again, use with caution!\n*/\n\nimport { bind, on } from './bind'\nimport { bindings } from './bindings'\nimport {\n ElementPart,\n ElementProps,\n ElementCreator,\n StringMap,\n XinBinding,\n EventType,\n} from './xin-types'\nimport { camelToKabob } from './string-case'\nimport { processProp } from './css'\nimport { xinPath } from './metadata'\nimport { MATH, SVG, type ElementsProxy } from './elements-types'\n\nconst templates: { [key: string]: Element } = {}\n\nconst elementStyle = (elt: HTMLElement, prop: string, value: any) => {\n const processed = processProp(camelToKabob(prop), value)\n if (processed.prop.startsWith('--')) {\n elt.style.setProperty(processed.prop, processed.value)\n } else {\n ;(elt.style as unknown as { [key: string]: string })[prop] = processed.value\n }\n}\n\nconst elementStyleBinding = (prop: string): XinBinding => {\n return {\n toDOM(element, value) {\n elementStyle(element as HTMLElement, prop, value)\n },\n }\n}\n\nconst elementProp = (elt: HTMLElement, key: string, value: any) => {\n if (key === 'style') {\n if (typeof value === 'object') {\n for (const prop of Object.keys(value)) {\n if (xinPath(value[prop])) {\n bind(elt, value[prop], elementStyleBinding(prop))\n } else {\n elementStyle(elt, prop, value[prop])\n }\n }\n } else {\n elt.setAttribute('style', value)\n }\n } else if ((elt as { [key: string]: any })[key] !== undefined) {\n // MathML is only supported on 91% of browsers, and not on the Raspberry Pi Chromium\n const { MathMLElement } = globalThis\n if (\n elt instanceof SVGElement ||\n (MathMLElement !== undefined && elt instanceof MathMLElement)\n ) {\n elt.setAttribute(key, value)\n } else {\n ;(elt as { [key: string]: any })[key] = value\n }\n } else {\n const attr = camelToKabob(key)\n\n if (attr === 'class') {\n value.split(' ').forEach((className: string) => {\n elt.classList.add(className)\n })\n } else if ((elt as { [key: string]: any })[attr] !== undefined) {\n ;(elt as StringMap)[attr] = value\n } else if (typeof value === 'boolean') {\n value ? elt.setAttribute(attr, '') : elt.removeAttribute(attr)\n } else {\n elt.setAttribute(attr, value)\n }\n }\n}\n\nconst elementPropBinding = (key: string): XinBinding => {\n return {\n toDOM(element, value) {\n elementProp(element as HTMLElement, key, value)\n },\n }\n}\n\nconst elementSet = (elt: HTMLElement, key: string, value: any) => {\n if (key === 'apply') {\n value(elt)\n } else if (key.match(/^on[A-Z]/) != null) {\n const eventType = key.substring(2).toLowerCase()\n on(elt, eventType as EventType, value)\n } else if (key === 'bind') {\n const binding =\n typeof value.binding === 'string'\n ? bindings[value.binding]\n : value.binding\n if (binding !== undefined && value.value !== undefined) {\n bind(\n elt,\n value.value,\n value.binding instanceof Function\n ? { toDOM: value.binding }\n : value.binding\n )\n } else {\n throw new Error(`bad binding`)\n }\n } else if (key.match(/^bind[A-Z]/) != null) {\n const bindingType = key.substring(4, 5).toLowerCase() + key.substring(5)\n const binding = bindings[bindingType]\n if (binding !== undefined) {\n bind(elt, value, binding)\n } else {\n throw new Error(\n `${key} is not allowed, bindings.${bindingType} is not defined`\n )\n }\n } else if (xinPath(value)) {\n bind(elt, value, elementPropBinding(key))\n } else {\n elementProp(elt, key, value)\n }\n}\n\nconst create = (tagType: string, ...contents: ElementPart[]): HTMLElement => {\n if (templates[tagType] === undefined) {\n const [tag, namespace] = tagType.split('|')\n if (namespace === undefined) {\n templates[tagType] = globalThis.document.createElement(tag)\n } else {\n templates[tagType] = globalThis.document.createElementNS(namespace, tag)\n }\n }\n const elt = templates[tagType].cloneNode() as HTMLElement\n const elementProps: ElementProps = {}\n for (const item of contents) {\n if (\n item instanceof Element ||\n item instanceof DocumentFragment ||\n typeof item === 'string' ||\n typeof item === 'number'\n ) {\n if (elt instanceof HTMLTemplateElement) {\n elt.content.append(item as Node)\n } else {\n elt.append(item as Node)\n }\n } else if (xinPath(item)) {\n elt.append(elements.span({ bindText: item }))\n } else {\n Object.assign(elementProps, item)\n }\n }\n for (const key of Object.keys(elementProps)) {\n const value: any = elementProps[key]\n elementSet(elt, key, value)\n }\n return elt\n}\n\nconst fragment = (...contents: ElementPart[]): DocumentFragment => {\n const frag = globalThis.document.createDocumentFragment()\n for (const item of contents) {\n frag.append(item as Node)\n }\n return frag\n}\n\n/**\n * elements is a proxy that produces ElementCreators, e.g.\n * elements.div() creates <div> elements and\n * elements.myElement() creates <my-element> elements.\n */\nexport const elements = new Proxy(\n { fragment },\n {\n get(target, tagName: string) {\n tagName = tagName.replace(/[A-Z]/g, (c) => `-${c.toLocaleLowerCase()}`)\n if ((target as StringMap)[tagName] === undefined) {\n ;(target as StringMap)[tagName] = (...contents: ElementPart[]) =>\n create(tagName, ...contents)\n }\n return (target as StringMap)[tagName]\n },\n set() {\n throw new Error('You may not add new properties to elements')\n },\n }\n) as unknown as ElementsProxy\n\ninterface SVGElementsProxy {\n [key: string]: ElementCreator<SVGElement>\n}\n\nexport const svgElements = new Proxy(\n { fragment },\n {\n get(target, tagName: string) {\n if ((target as StringMap)[tagName] === undefined) {\n ;(target as StringMap)[tagName] = (...contents: ElementPart[]) =>\n create(`${tagName}|${SVG}`, ...contents)\n }\n return (target as StringMap)[tagName]\n },\n set() {\n throw new Error('You may not add new properties to elements')\n },\n }\n) as unknown as SVGElementsProxy\n\ninterface MathMLElementsProxy {\n [key: string]: ElementCreator<MathMLElement>\n}\n\nexport const mathML = new Proxy(\n { fragment },\n {\n get(target, tagName: string) {\n if ((target as StringMap)[tagName] === undefined) {\n ;(target as StringMap)[tagName] = (...contents: ElementPart[]) =>\n create(`${tagName}|${MATH}`, ...contents)\n }\n return (target as StringMap)[tagName]\n },\n set() {\n throw new Error('You may not add new properties to elements')\n },\n }\n) as unknown as MathMLElementsProxy\n",
|
|
22
|
+
"/*#\n# 1. tosi\n\n`tosi()` assigns an object passed to it to a global state object,\nand returns an observer proxy (`BoxedProxy`) wrapped around the global state object.\n\nBoxedProxy wraps any object you pull out of it in an observer\nptoxy. It boxes booleans, numbers, and strings in objects and wraps\nthose objects in an observer proxy.\n\nIn rough terms:\n\n```\nconst state = {}\nconst boxed = new Proxy(state, ...)\ntosi = (obj<T>): BoxedProxy<T> => {\n Object.assign(state, obj)\n return state\n}\n```\n\nThis allows the following pattern, which gives Typescript a lot of useful\ninformation for free, allowing autocomplete, etc. with a minimumn of\nboilerplate.\n\n```\nimport { tosi, elements, bind } from 'tosijs'\n\nconst { prefs } = tosi({\n prefs: {\n theme: 'system',\n highcontrast: false\n }\n})\n\n// this example continues…\n```\n\nSo `{ prefs: ... }` is assigned to the state object, and now `prefs`\nholds the same stuff except it's wrapped in a `BoxedProxy`.\n\nThe `BoxedProxy` behaves just like the original object, except\nthat it:\n\n- knows where it came from, so `prefs.xinPath === 'prefs'`\n- will automatically trigger updates if its properties are changed through it\n- can return the underlying value:\n `prefs.xinValue === prefs.valueOf() === the prefs property of the object passed to `tosi()`\n- it will wrap its non-object properties in objects and wrap those objects\n in a BoxedProxy, so `prefs.theme.xinPath === 'prefs.theme'`\n\n```\nprefs.theme instanceof String // true\nprefs.theme.valueOf() === 'system' // true\nprefs.theme.xinValue === 'system' // true\nprefs.theme.xinPath === 'prefs.theme' // true\n```\n\nThe `BoxedProxy` observes changes made through it and updates bound elements\naccordingly:\n\n```\nbind(document.body, prefs.theme, {\n toDOM(element, value) {\n element.classList.toggle('dark-mode', value === 'dark')\n }\n}\n\nconst { select, option } = elements\n\ndocument.body.append(\n select(\n { bindValue: prefs.theme },\n option('system'),\n option('dark'),\n option('light')\n )\n)\n```\n\nSetting up the binding to `document.body` will set the `class`\nappropriately, and modifying `prefs.theme` will update the\nbound element automatically.\n\n```\nproxy.theme.xinValue = 'dark'\n```\n\n> In javascript you can just write `proxy.theme = 'dark'`.\n\nThis, in a nutshell, explains exactly what `tosijs` is designed to do.\n\n`tosi` tracks state and allows you to bind data to your user interface\ndirectly and with almost no code, with clean separation between business\nlogic and presentation.\n\nThe [`elements` proxy](/?elements.ts) lets you build HTML elements with\ndata and event bindings more compactly and efficiently than you can using\nJSX/TSX, and it deals in regular `HTMLElement`—no virtual DOM, tranpilation\nmagic, spooky action at a distance, or any similar nonsense.\n\nIf you need to do complex bindings, the `bind` method lets you directly\nlink data to the DOM and automatically sets up observers for you.\n\n`Component` lets you create reusable web-components.\n\n`css` lets you write CSS economically and makes it easy to leverage the power\nof CSS variables, while `Color` allows you to do color math quickly and\neasily until similar functionality is added to CSS.\n\n> In Finnish, *tosi* means true or really.\n>\n> As conceived, `tosi()` is an observer `Proxy` wrapped around your application's\n> state. It's the **single source of truth for application state**.\n>\n> Note that the interactive examples on the tosijs.net website allow TypeScript\n> but the Typescript is simply stripped to javascript using [sucrase](https://sucrase.io/).\n\n## xin\n\n`xin` is a path-based implementation of the **observer** or **pub/sub**\npattern designed to be very simple and straightforward to use, leverage\nTypescript type-checking and autocompletion, and let you get more done with\nless code and no weird build magic (such as special decorators or \"execution zones\").\n\n## In a nutshell\n\n`xin` is a single object wrapped with an **observer** proxy.\n\n- when you assign an object (or array) to `xin` as a property, you're\n just assigning a property to the object. When you pull it out, you\n get a **proxy** of the underlying value, but the original value is\n still there, untouched.\n ```\n const foo = { bar: 'baz' }\n xin.foo = foo\n xin.foo.bar === foo.bar\n xin.foo.bar === 'baz'\n xin.foo !== foo // xin.foo is a proxy\n xin.foo.xinValue === foo // foo is still there!\n ```\n- if you change a property of something already in `xin` then this\n change will be `observed` and anything *listening* for changes to\n the value at that **path** will be notified.\n- xinjs's `bind` method leverages the proxy to keep the UI synced\n with application state.\n\nIn the following example there's a `<div>` and an `<input>`. The\ntextContent of the former and the value of the latter are bound to\nthe **path** `xinExample.string`.\n\n`xin` is exposed as a global in the console, so you can go into\nconsole and look at `xin.xinExample` and (for example) directly\nchange it via the console.\n\nYou can also turn on Chrome's rendering tools to see how\nefficiently the DOM is updated. And also note that typing into\nthe input field doesn't lose any state (so your text selection\nand insertion point are stable.\n\n```js\nimport { xin, elements } from 'tosijs'\n\nxin.xinExample = {\n string: 'hello, xin'\n}\n\nconst { label, input, div, span } = elements\n\npreview.append(\n div(\n {\n style: {\n display: 'flex',\n flexDirection: 'column',\n gap: 10,\n padding: 10\n }\n },\n div({bindText: 'xinExample.string'}),\n label(\n span('Edit this'),\n input({ bindValue: 'xinExample.string'})\n )\n )\n)\n```\n\n- a **data-path** typically resembles the way you'd reference a value inside\n a javascript object…\n- `xin` also supports **id-paths** which allow you to create stable references\n to elements in arrays using a (hopefully unique) identifier. E.g. instead\n of referring to an item in an array as `xin.foo.array[3]`, assuming it had\n an `id` of `abcd1234` you could write `xin.foo.array[id=abcd1234]`. This makes\n handling large arrays much more efficient.\n- when you pull an object-value out of `xin` it comes wrapped in the xin\n observer proxy, so it continues to support id-paths and so on.\n\n### A Calculator\n\n```js\nimport { xin, elements, touch } from 'tosijs'\n\n// here's a vanilla javascript calculator\nconst calculator = {\n x: 4,\n y: 3,\n op: '+',\n result: 0,\n evaluate() {\n this.result = eval(`${this.x} ${this.op} ${this.y}`)\n }\n}\n\ncalculator.evaluate()\n\nxin.calculatorExample = calculator\n\n// now we'll give it a user interface…\nconst { input, select, option, div, span } = elements\n\npreview.append(\n div(\n {\n onChange() {\n calculator.evaluate()\n touch('calculatorExample.result')\n }\n },\n input({bindValue: 'calculatorExample.x', placeholder: 'x'}),\n select(\n {\n bindValue: 'calculatorExample.op'\n },\n option('+'),\n option('-'),\n option({value: '*'}, '×'),\n option({value: '/'}, '÷'),\n ),\n input({bindValue: 'calculatorExample.y', placeholder: 'y'}),\n span('='),\n span({bindText: 'calculatorExample.result' })\n )\n)\n```\n\nImportant points:\n\n- `xin` points at a single object. It's a [Singleton](https://en.wikipedia.org/wiki/Singleton_pattern).\n- `boxed` points to the **same** object\n- `xin` and `boxed` are observers. They watch the object they point to and\n track changes made by accessing the underlying data through them.\n- because `calculator.evaluate()` changes `calculator.result`\n directly, `touch()` is needed to tell `xin` that the change occurred.\n See [path-listener](/?path-listener.ts) for more documentation on `touch()`.\n- `xin` is more than just an object!\n - `xin['foo.bar']` gets you the same thing `xin.foo.bar` gets you.\n - `xin.foo.bar = 17` tells `xin` that `foo.bar` changed, which triggers DOM updates.\n\n> If you're reading this on xinjs.net then this the demo app you're looking\n> works a bit like this and `xin` (and `boxed`) are exposed as globals so\n> you can play with them in the **debug console**.\n>\n> Try going into the console and typing `xin.app.title` to see what you get,\n> and then try `xin.app.title = 'foobar' and see what happens to the heading.\n>\n> Also try `xin.prefs.theme` and try `app.prefs.theme = 'dark'` etc.\n\nOnce an object is assigned to `xin`, changing it within `xin` is simple.\nTry this in the console:\n\n```\nxin.calculatorExample.x = 17\n```\n\nThis will update the `x` field in the calculator, but not the result.\nThe result is updated when a `change` event is triggered.\n\nIf you wanted the calculator to update based on *any* change to its\ninternal state, you could instead write:\n\n```\nobserve('calculatorExample', () => {\n calculator.evaluate()\n touch('calculatorExample.result')\n})\n```\n\nNow the `onChange` handler isn't necessary at all. `observe`\nis documented in [path-listener](/?path-listener.ts).\n\n```js\nimport { observe, xin, elements } from 'tosijs'\n\nconst { h3, div } = elements\n\nconst history = div('This shows changes made to the preceding example')\n\npreview.append(\n h3('Changes to the calculatorExample'),\n history\n)\n\nobserve(/calculatorExample\\./, path => {\n const value = xin[path]\n history.insertBefore(div(`${path} = ${value}`), history.firstChild)\n})\n```\n\nNow, if you sneakily make changes behind `xin`'s back, e.g. by modifying the values\ndirectly, e.g.\n\n```\nconst emails = await getEmails()\nxin.emails = emails\n\n// notes that xin.emails is really JUST emails\nemails.push(...)\nemails.splice(...)\nemails[17].from = '...'\n```\n\nThen `xin` won't know and observers won't fire. So you can simply `touch` the path\nimpacted:\n\n```\nimport { touch } from 'xinjs'\ntouch('emails')\n```\n\nIn the calculator example, the vanilla `calculator` code calls `evaluate` behind\n`xin`'s back and uses `touch('calculatorExample.result')` to let `xin` know that\n`calculatorExample.result` has changed. This causes `xin` to update the\nDOM.\n\n## How it works\n\n`xin` is a `Proxy` wrapped around a bare object: effectively a map of strings to values.\n\nWhen you access the properties of an object assigned to `xin` it wraps the values in\nsimilar proxies, and tracks the **path** that got you there:\n\n```\nxin.foo = {\n bar: 'baz',\n luhrman: {\n job: 'director'\n }\n}\n```\n\nNow if you pull objects back out of `xin`:\n\n```\nlet foo = xin.foo\nlet luhrman = foo.luhrman\n```\n\n`foo` is a `Proxy` wrapped around the original *untouched* object, and it knows it came from 'foo'.\nSimilarly `luhrman` is a `Proxy` that knows it came from 'foo.luhrman'.\n\nIf you **change** a value in a wrapped object, e.g.\n\n```\nfoo.bar = 'bob'\nluhrman.job = 'writer'\n```\n\nThen it will trigger any observers looking for relevant changes. And each change will fire the observer\nand tell it the `path` that was changed. E.g. an observer watching `lurman` will be fired if `lurman`\nor one of `lurman`'s properties is changed.\n\n## The `boxed` proxy\n\n`boxed` is a sister to `xin` that wraps \"scalar\" values (`boolean`, `number`, `string`) in\nobjects. E.g. if you write something like:\n\n```\nxin.test = { answer: 42 }\nboxed.box = { pie: 'apple' }\n```\n\nThen:\n\n```\nxin.test.answer === 42\nxin.box.pie === 'apple'\n// box wraps \"scalars\" in objects\nboxed.test.answer.valueOf() === 42\nboxed.box.pie.valueOf() === 'apple'\n// anything that comes out of boxed has a path!\nxinPath(boxed.test.answer) === 'test.answer'\nxinPath(boxed.box.pie) === 'box.pie'\n```\n\nAside from always \"boxing\" scalar values, `boxed` works just like `xin`.\n\nIn the console, you can also access `boxed` and look at what happens if you\naccess `boxed.xinExample.string`. Note that this changes the value you get,\nthe underlying value is still what it was. If you set it to a new `string`\nvalue that's what will be stored. `xin` doesn't monkey with the values you\nassign.\n\n### Why?!\n\nAs far as Typescript is concerned, `xinProxy` just passes back what you put into it,\nwhich means that you can now write bindings with type-checking and autocomplete and\nnever use string literals. So something like this *just works*:\n\n```\nconst div = elements.div({bindText: boxed.box.pie})\n```\n\n…because `boxed.box.pie` has a `xinPath` which is what is actually used for binding,\nwhereas `xin.box.pie` is just a scalar value. Without `boxed` you could write\n`bindText: 'box.pie'` but you don't get lint support or autocomplete. (Also, in\nsome cases, you might even mangle the names of an object during minification and\n`boxed` will know the mangled name).\n\n### If you need the thing itself or the path to the thing…\n\n`proxy`s returned by `xin` are typically indistinguishable from the original object, but\nin a pinch `xinPath()` will give you the path (`string`) of a `XinProxy` while `xinValue`\nwill give its \"bare\" value. `xinPath()` can also be used to test if something is actually\na proxy, as it will return `undefined` for regular objects.\n\nE.g.\n\n```\nxinPath(luhrman) === 'foo.luhrman' // true\nconst bareLurhman = xinValue(luhrman) // not wrapped\n```\n\nYou may want the thing itself to, for example, perform a large number of changes to an\nobject without firing observers. You can let `xin` know you've made changes behind its back using\n`touch`, e.g.\n\n```\ndoTerribleThings(xinValue(luhrman))\n// eslint-disable-next-line\ntouch(luhrman)\n```\n\nThis is **useful** because `boxed.foo.bar` always knows where it came from, while\n`xin.foo.bar` only knows where it came from if it's an object value.\n\nThis means you can write:\n\n```js\nimport { boxed, elements } from 'tosijs'\n\nboxed.boxedExample = {\n string: 'hello, boxed'\n}\n\nconst { boxedExample } = boxed\n\nconst { label, input, div, span } = elements\n\npreview.append(\n div(\n {\n style: {\n display: 'flex',\n flexDirection: 'column',\n gap: 10,\n padding: 10\n }\n },\n div({bindText: boxedExample.string}),\n label(\n span('Edit this'),\n input({ bindValue: boxedExample.string})\n )\n )\n)\n```\n\nAnd the difference here is you can bind direct to the reference itself rather\nthan a string. This leverages autocomplete, linting, and so on in a way that\nusing string paths doesn't.\n\nIt does have a downside! `boxedExample.string !== 'hello, boxed'` and in fact\n`boxedExample.string !== boxedExample.string` because they're two different\n`String` objects. This is critical for comparisons such as `===` and `!==`.\nAlways use `boxed.foo.bar.xinValue`, `xinValue(boxed.foo.bar)` or `boxed.foo.bar.valueOf()`\nwhen performing comparisons like this.\n\n## Helper properties and functions\n\n`XinProxy` and `BoxedProxy` provide some helper properties and functions.\n\n- `xinValue` gets you the underlying value\n- `xinPath` gets you the string path\n- `xinBind(element: Element, binding: [XinBinding](/?bindings.ts), options?: {[key: string]: any})` will\n bind the `xinPath` the element with the specified binding.\n ```\n boxed.foo.color.bind(element, {\n toDOM(element, color){\n element.style.backgroundColor = color\n }\n })\n ```\n- `xinOn(element: HTMLElement, eventType: keyof HTMLElementEventMap)` will\n trigger the event handler when the specified element receives\n an event of the specified type.\n- `xinObserve(callback: ObserverCallbackFunction): UnobserveFunction` will\n trigger the provided callback when the value changes, and can be cancelled\n using the returned function.\n\n### To Do List Example\n\nEach of the features described thus far, along with the features of the\n`elementCreator` functions provided by the [elements](/?elements.ts) proxy\nare designed to eliminate boilerplate, simplify your code, and reduce\nthe chance of making costly errors.\n\nThis example puts all of this together.\n\n```js\nimport { elements, tosi } from 'tosijs'\n\nconst { todos } = tosi({\n todos: {\n list: [],\n newItem: ''\n }\n})\n\nconst { h3, div, label, input, button, template } = elements\n\nconst addItem = () => {\n todos.list.push({\n description: todos.newItem\n })\n todos.newItem = ''\n}\n\npreview.append(\n h3('To do'),\n div(\n {\n bindList: {\n value: todos.list\n }\n },\n template(\n div({ bindText: '^.description' })\n )\n ),\n div(\n input({\n placeholder: 'task',\n bindValue: todos.newItem,\n onKeyup(event) {\n if(event.key === 'Enter' && todos.newItem != '') {\n addItem()\n }\n }\n }),\n button('Add', {\n bindEnabled: todos.newItem,\n onClick: addItem\n })\n )\n)\n```\n*/\n\nimport {\n XinProxyObject,\n XinProxyTarget,\n XinObject,\n XinProxy,\n BoxedProxy,\n XinArray,\n XinValue,\n XinBinding,\n PathTestFunction,\n ObserverCallbackFunction,\n XinEventHandler,\n} from './xin-types'\nimport { settings } from './settings'\nimport {\n Listener,\n touch,\n observe as _observe,\n unobserve,\n updates,\n} from './path-listener'\nimport { getByPath, setByPath } from './by-path'\nimport { bind, on } from './bind'\nimport { ElementsProxy } from './elements-types'\nimport { elements } from './elements'\nimport {\n xinValue,\n XIN_VALUE,\n XIN_PATH,\n XIN_OBSERVE,\n XIN_BIND,\n XIN_ON,\n} from './metadata'\n\ninterface ProxyConstructor {\n revocable: <T extends object, P extends object>(\n target: T,\n handler: ProxyHandler<P>\n ) => { proxy: P; revoke: () => void }\n new <T extends object>(target: T, handler: ProxyHandler<T>): T\n new <T extends object, P extends object>(\n target: T,\n handler: ProxyHandler<P>\n ): P\n}\ndeclare let Proxy: ProxyConstructor\n\n// list of Array functions that change the array\nconst ARRAY_MUTATIONS = [\n 'sort',\n 'splice',\n 'copyWithin',\n 'fill',\n 'pop',\n 'push',\n 'reverse',\n 'shift',\n 'unshift',\n]\n\nconst registry: XinObject = {}\nconst debugPaths = true\n\n// in essence this very liberally matches foo ( .bar | [17] | [id=123] ) *\nconst validPath =\n /^\\.?([^.[\\](),])+(\\.[^.[\\](),]+|\\[\\d+\\]|\\[[^=[\\](),]*=[^[\\]()]+\\])*$/\n\nconst isValidPath = (path: string): boolean => validPath.test(path)\n\nconst extendPath = (path = '', prop = ''): string => {\n if (path === '') {\n return prop\n } else {\n if (prop.match(/^\\d+$/) !== null || prop.includes('=')) {\n return `${path}[${prop}]`\n } else {\n return `${path}.${prop}`\n }\n }\n}\n\nconst boxes: { [key: string]: (x: any) => any } = {\n string(s: string) {\n return new String(s)\n },\n boolean(b: boolean) {\n return new Boolean(b)\n },\n bigint(b: bigint) {\n return b\n },\n symbol(s: symbol) {\n return s\n },\n number(n: number) {\n return new Number(n)\n },\n}\n\nconst ACTUAL = Symbol('undefined')\n\nfunction box<T>(x: T, path: string): T {\n if (x === undefined || x === null) {\n return new Proxy<XinProxyTarget, XinObject>(\n { [ACTUAL]: x === null ? 'null' : 'undefined' },\n regHandler(path, true)\n ) as T\n }\n const t = typeof x\n if (t === 'object' || t === 'function') {\n return x\n } else {\n return new Proxy<XinProxyTarget, XinObject>(\n boxes[typeof x](x),\n regHandler(path, true)\n ) as T\n }\n}\n\nconst listElement = () => new Proxy({}, regHandler('^', true))\n\nconst regHandler = (\n path: string,\n boxScalars: boolean\n): ProxyHandler<XinObject> => ({\n get(target: XinObject | XinArray, _prop: string | symbol): XinValue {\n switch (_prop) {\n case XIN_PATH:\n case 'tosiPath':\n return path\n case XIN_VALUE:\n case 'tosiValue':\n return (target as any)[ACTUAL]\n ? (target as any)[ACTUAL] === 'null'\n ? null\n : undefined\n : target.valueOf\n ? target.valueOf()\n : target\n case XIN_OBSERVE:\n case 'tosiObserve':\n return (callback: ObserverCallbackFunction) => {\n const listener = _observe(path, callback)\n return () => unobserve(listener)\n }\n case XIN_ON:\n case 'tosiOn':\n return (\n element: HTMLElement,\n eventType: keyof HTMLElementEventMap\n ): VoidFunction =>\n on(element, eventType, xinValue(target) as XinEventHandler)\n case XIN_BIND:\n case 'tosiBind':\n return (element: Element, binding: XinBinding, options?: XinObject) => {\n bind(element, path, binding, options)\n }\n case 'tosiBinding':\n return (binding: XinBinding) => ({\n bind: {\n value: path,\n binding,\n },\n })\n case 'tosiListBinding':\n return (\n content: (e: ElementsProxy, obj: BoxedProxy) => HTMLElement = ({\n span,\n }) => span({ bindText: '^' }),\n options: XinObject = {}\n ) => [\n {\n bindList: {\n value: path,\n ...options,\n },\n },\n elements.template(content(elements, listElement())),\n ]\n }\n if (typeof _prop === 'symbol') {\n return (target as XinObject)[_prop]\n }\n let prop = _prop\n const compoundProp =\n prop.match(/^([^.[]+)\\.(.+)$/) ?? // basePath.subPath (omit '.')\n prop.match(/^([^\\]]+)(\\[.+)/) ?? // basePath[subPath\n prop.match(/^(\\[[^\\]]+\\])\\.(.+)$/) ?? // [basePath].subPath (omit '.')\n prop.match(/^(\\[[^\\]]+\\])\\[(.+)$/) // [basePath][subPath\n if (compoundProp !== null) {\n const [, basePath, subPath] = compoundProp\n const currentPath = extendPath(path, basePath)\n const value = getByPath(target, basePath)\n return value !== null && typeof value === 'object'\n ? new Proxy<XinObject, XinProxyObject>(\n value,\n regHandler(currentPath, boxScalars)\n )[subPath]\n : value\n }\n if (prop.startsWith('[') && prop.endsWith(']')) {\n prop = prop.substring(1, prop.length - 1)\n }\n if (\n (!Array.isArray(target) && target[prop] !== undefined) ||\n (Array.isArray(target) && prop.includes('='))\n ) {\n let value: XinValue\n if (prop.includes('=')) {\n const [idPath, needle] = prop.split('=')\n value = (target as XinObject[]).find(\n (candidate: XinObject) =>\n `${getByPath(candidate, idPath) as string}` === needle\n )\n } else {\n // we're working around Typescript's incorrect understanding of arrays\n value = (target as XinArray)[prop as unknown as number]\n }\n if (value instanceof Object) {\n const currentPath = extendPath(path, prop)\n return new Proxy<XinObject, XinProxyObject>(\n value instanceof Function ? value.bind(target) : value,\n regHandler(currentPath, boxScalars)\n ) as XinValue\n } else {\n return boxScalars ? box(value, extendPath(path, prop)) : value\n }\n } else if (Array.isArray(target)) {\n const value = target[prop as unknown as number]\n return typeof value === 'function'\n ? (...items: any[]) => {\n const result = value.apply(target, items)\n if (ARRAY_MUTATIONS.includes(prop)) {\n touch(path)\n }\n return result\n }\n : typeof value === 'object'\n ? new Proxy<XinProxyTarget, XinObject>(\n value,\n regHandler(extendPath(path, prop), boxScalars)\n )\n : boxScalars\n ? box(value, extendPath(path, prop))\n : value\n } else {\n return boxScalars\n ? box(target[prop], extendPath(path, prop))\n : target[prop]\n }\n },\n set(_, prop: string, value: any) {\n value = xinValue(value)\n const fullPath = prop !== XIN_VALUE ? extendPath(path, prop) : path\n if (debugPaths && !isValidPath(fullPath)) {\n throw new Error(`setting invalid path ${fullPath}`)\n }\n const existing = xinValue(xin[fullPath])\n if (existing !== value && setByPath(registry, fullPath, value)) {\n touch(fullPath)\n }\n return true\n },\n})\n\nconst observe = (\n test: string | RegExp | PathTestFunction,\n callback: string | ObserverCallbackFunction\n): Listener => {\n const func = typeof callback === 'function' ? callback : xin[callback]\n\n if (typeof func !== 'function') {\n throw new Error(\n `observe expects a function or path to a function, ${\n callback as string\n } is neither`\n )\n }\n\n return _observe(test, func as ObserverCallbackFunction)\n}\n\nconst xin = new Proxy<XinObject, XinProxy<XinObject>>(\n registry,\n regHandler('', false)\n)\n\nconst boxed = new Proxy<XinObject, BoxedProxy<XinObject>>(\n registry,\n regHandler('', true)\n)\n\n// settings and isValidPath are only used for internal testing\nexport { xin, boxed, updates, touch, observe, unobserve, settings, isValidPath }\n",
|
|
23
|
+
"/*#\n# 2. bind\n\n`bind()` lets you synchronize data / application state to the user-interface reliably,\nefficiently, and with a minimum of code.\n\n## An Aside on Reactive Programming vs. the Observer Model\n\nA good deal of front-end code deals with keeping the application's\nstate synchronized with the user-interface. One approach to this problem\nis [Reactive Programming](https://en.wikipedia.org/wiki/Reactive_programming)\nas exemplified by [React](https://reactjs.org) and its many imitators.\n\n`tosijs` works very well with React via the [useTosi](https://github.com/tonioloewald/react-tosijs) React \"hook\".\nBut `tosijs` is not designed for \"reactive programming\" and in fact \"hooks\" aren't\n\"reactive\" at all, so much as an example of the \"observer\" or \"pub/sub\" pattern.\n\n`tosijs` is a \"path-observer\" in that it's an implementation of the\n[Observer Pattern](https://en.wikipedia.org/wiki/Observer_pattern)\nwhere **path strings** serve as a level of *indirection* to the things observed.\nThis allows data to be \"observed\" before it exists, which in particular *decouples* the setup\nof the user interface from the initialization of data and allows user interfaces\nbuilt with `tosijs` to be *deeply asynchronous*.\n\n## `bind()`\n\n```\nbind<T = Element>(\n element: T,\n what: XinTouchableType,\n binding: XinBinding,\n options: XinObject\n): T\n```\n\n`bind()` binds a `path` to an element, syncing the value at the path to and/or from the DOM.\n\n```js\nimport { bind, tosi } from 'tosijs'\n\nconst { simpleBindExample } = tosi({\n simpleBindExample: {\n showThing: true\n }\n})\n\nbind(\n preview.querySelector('b'),\n 'simpleBindExample.showThing',\n {\n toDOM(element, value) {\n element.style.visibility = value ? 'visible' : 'hidden'\n }\n }\n)\n\nbind(\n preview.querySelector('input[type=checkbox]'),\n // the tosi can be used instead of a string path\n simpleBindExample.showThing,\n // we could just use bindings.value here\n {\n toDOM(element, value) {\n element.checked = value\n },\n fromDOM(element) {\n return element.checked\n }\n }\n)\n```\n```html\n<b>The thing</b><br>\n<label>\n <input type=\"checkbox\">\n Show the thing\n</label>\n```\n\nThe `bind` function is a simple way of tying an `HTMLElement`'s properties to\nstate via `path` using [bindings](/?bindings.ts)\n\n```\nimport {bind, bindings, xin, elements, updates} from 'xinjs'\nconst {div, input} = elements\n\nconst divElt = div()\nbind(divElt, 'app.title', bindings.text)\ndocument.body.append(divElt)\n\nconst inputElt = input()\nbind(inputElt, 'app.title', bindings.value)\n\nxin.app = {title: 'hello world'}\nawait updates()\n```\n\nWhat's happening is essentially the same as:\n\n```\ndivElt.textContent = xin.app.title\nobserve('app.title', () => divElt.textContent = xin.app.title)\n\ninputElt.value = xin.app.title\nobserve('app.title', () => inputElt.value = xin.app.title)\ninputElt.addEventListener('change', () => { xin.app.title = inputElt.value })\n```\n\nExcept:\n\n1. this code is harder to write\n2. it will fail if xin.app hasn't been initialized (which it hasn't been!)\n3. inputElt will also trigger *debounced* updates on `input` events\n\nAfter this. `div.textContent` and `inputElt.value` are 'hello world'.\nIf the user edits the value of `inputElt` then `xin.app.title` will\nbe updated, and `app.title` will be listed as a changed path, and\nan update will be fired via `setTimout`. When that update fires,\nanything observer of the paths `app.text` and `app` will be fired.\n\nA `binding` looks like this:\n\n```\ninterface XinBinding {\n toDOM?: (element: HTMLElement, value: any, options?: XinObject) => void\n fromDOM?: (element: HTMLElement) => any\n}\n```\n\nSimply put the `toDOM` method updates the DOM based on changes in state\nwhile `fromDOM` updates state based on data in the DOM. Most bindings\nwill have a `toDOM` method but no `fromDOM` method since `bindings.value`\n(which has both) covers most of the use-cases for `fromDOM`.\n\nIt's easy to write your own `bindings` if those in `bindings` don't meet your\nneed, e.g. here's a custom binding that toggles the visibility of an element\nbased on whether the bound value is neither \"falsy\" nor an empty `Array`.\n\n```\nconst visibility = {\n toDOM(element, value) {\n if (element.dataset.origDisplay === undefined && element.style.display !== 'none') {\n element.dataset.origDisplay = element.style.display\n }\n element.style.display = (value != null && value.length > 0) ? element.dataset.origDisplay : 'none'\n }\n}\nbind(listElement, 'app.bigList', visibility)\n```\n\n## `on()`\n\n```\non(element: Element, eventType: string, handler: XinEventHandler): VoidFunction\n\nexport type XinEventHandler<T extends Event = Event, E extends Element = Element> =\n | ((evt: T & {target: E}) => void)\n | ((evt: T & {target: E}) => Promise<void>)\n | string\n```\n\n```js\nimport { elements, on, tosi } from 'tosijs'\nimport { postNotification } from 'tosijs-ui'\n\nconst makeHandler = (message) => () => {\n postNotification({ message, duration: 2 })\n}\n\nconst { onExample } = tosi({\n onExample: {\n clickHandler: makeHandler('Hello from onExample proxy')\n }\n})\n\nconst { button, div, h2 } = elements\n\nconst hasListener = button('has listener')\nhasListener.addEventListener('click', makeHandler('Hello from addEventListener'))\n\npreview.append(\n div(\n {\n style: {\n display: 'flex',\n flexDirection: 'column',\n padding: 10,\n gap: 10\n }\n },\n h2('Event Handler Examples'),\n hasListener,\n button('just a callback', {onClick: makeHandler('just a callback')}),\n button('via proxy', {onClick: onExample.clickHandler}),\n )\n)\n```\n\n`on()` binds event-handlers to DOM elements.\n\nMore than syntax sugar for `addEventListener`, `on()` allows you to bind event\nhandlers inside `xin` by path (e.g. allowing event-handling code to be loaded\nasynchronously or lazily, or simply allowing event-handlers to be switched dynamically\nwithout rebinding) and it uses event-bubbling to minimize the actual number of\nevent handlers that need to be registered.\n\n`on()` returns a function for removing the event handler.\n\nIn essence, only one event handler of a given type is ever added to the\nDOM by `on()` (at `document.body` level), and then when that event is detected,\nthat handler goes from the original target through to the DOM and fires off\nevent-handlers, passing them an event proxy (so that `stopPropagation()` still\nworks).\n\n## `touchElement()`\n\n```\ntouchElement(element: Element, changedPath?: string)\n```\n\nThis is a low-level function for *immediately* updating a bound element. If you specifically\nwant to force a render of an element (versus anything bound to a path), simply call\n`touchElement(element)`. Specifying a `changedPath` will only trigger bindings bound\nto paths staring with the provided path.\n*/\n\nimport { xin, touch, observe } from './xin'\nimport {\n elementToBindings,\n elementToHandlers,\n DataBindings,\n LIST_BINDING_REF,\n BOUND_CLASS,\n BOUND_SELECTOR,\n EVENT_CLASS,\n EVENT_SELECTOR,\n XinEventBindings,\n XIN_PATH,\n XIN_VALUE,\n} from './metadata'\nimport {\n XinObject,\n XinProps,\n XinEventHandler,\n XinTouchableType,\n XinBinding,\n XinBindingSpec,\n EventType,\n} from './xin-types'\nimport { ListBinding, getListItem } from './list-binding'\n\nconst { document, MutationObserver } = globalThis\n\nexport const touchElement = (element: Element, changedPath?: string): void => {\n const dataBindings = elementToBindings.get(element)\n if (dataBindings == null) {\n return\n }\n for (const dataBinding of dataBindings) {\n const { binding, options } = dataBinding\n let { path } = dataBinding\n const { toDOM } = binding\n if (toDOM != null) {\n if (path.startsWith('^')) {\n const dataSource = getListItem(element)\n if (dataSource != null && (dataSource as XinProps)[XIN_PATH] != null) {\n path = dataBinding.path = `${\n (dataSource as XinProps)[XIN_PATH]\n }${path.substring(1)}`\n } else {\n console.error(\n `Cannot resolve relative binding ${path}`,\n element,\n 'is not part of a list'\n )\n throw new Error(`Cannot resolve relative binding ${path}`)\n }\n }\n if (changedPath == null || path.startsWith(changedPath)) {\n toDOM(element, xin[path], options)\n }\n }\n }\n}\n\n// this is just to allow bind to be testable in node\nif (MutationObserver != null) {\n const observer = new MutationObserver((mutationsList) => {\n mutationsList.forEach((mutation) => {\n Array.from(mutation.addedNodes).forEach((node) => {\n if (node instanceof Element) {\n Array.from(node.querySelectorAll(BOUND_SELECTOR)).forEach((element) =>\n touchElement(element as Element)\n )\n }\n })\n })\n })\n observer.observe(document.body, { subtree: true, childList: true })\n}\n\nobserve(\n () => true,\n (changedPath: string) => {\n const boundElements = Array.from(document.querySelectorAll(BOUND_SELECTOR))\n\n for (const element of boundElements) {\n touchElement(element as HTMLElement, changedPath)\n }\n }\n)\n\nconst handleChange = (event: Event): void => {\n // @ts-expect-error-error\n let target = event.target.closest(BOUND_SELECTOR)\n while (target != null) {\n const dataBindings = elementToBindings.get(target) as DataBindings\n for (const dataBinding of dataBindings) {\n const { binding, path } = dataBinding\n const { fromDOM } = binding\n if (fromDOM != null) {\n let value\n try {\n value = fromDOM(target, dataBinding.options)\n } catch (e) {\n console.error('Cannot get value from', target, 'via', dataBinding)\n throw new Error('Cannot obtain value fromDOM')\n }\n if (value != null) {\n const existing = xin[path]\n if (existing == null) {\n xin[path] = value\n } else {\n const existingActual =\n existing[XIN_PATH] != null\n ? (existing as XinProps)[XIN_VALUE]\n : existing\n const valueActual =\n value[XIN_PATH] != null ? value[XIN_VALUE] : value\n if (existingActual !== valueActual) {\n xin[path] = valueActual\n }\n }\n }\n }\n }\n target = target.parentElement.closest(BOUND_SELECTOR)\n }\n}\n\nif (globalThis.document != null) {\n document.body.addEventListener('change', handleChange, true)\n document.body.addEventListener('input', handleChange, true)\n}\n\ninterface BindingOptions {\n [key: string]: any\n}\n\nexport function bind<T extends Element = Element>(\n element: T,\n what: XinTouchableType | XinBindingSpec,\n binding: XinBinding<T>,\n options?: BindingOptions\n): T {\n if (element instanceof DocumentFragment) {\n throw new Error('bind cannot bind to a DocumentFragment')\n }\n let path: string\n if (\n typeof what === 'object' &&\n (what as XinProps)[XIN_PATH] === undefined &&\n options === undefined\n ) {\n const { value } = what as XinBindingSpec\n path = typeof value === 'string' ? value : value[XIN_PATH]\n options = what as XinObject\n delete options.value\n } else {\n path = typeof what === 'string' ? what : (what as XinProps)[XIN_PATH]\n }\n if (path == null) {\n throw new Error('bind requires a path or object with xin Proxy')\n }\n const { toDOM } = binding\n\n element.classList?.add(BOUND_CLASS)\n let dataBindings = elementToBindings.get(element)\n if (dataBindings == null) {\n dataBindings = []\n elementToBindings.set(element, dataBindings)\n }\n dataBindings.push({\n path,\n binding: binding as XinBinding<Element>,\n options,\n })\n\n if (toDOM != null && !path.startsWith('^')) {\n // not calling toDOM directly here allows virtual list bindings to work\n touch(path)\n }\n\n if (options?.filter && options?.needle) {\n bind(element, options.needle, {\n toDOM(element, value) {\n console.log({ needle: value })\n ;(element as { [LIST_BINDING_REF]?: ListBinding })[\n LIST_BINDING_REF\n ]?.filter(value)\n },\n })\n }\n\n return element\n}\n\nconst handledEventTypes: Set<string> = new Set()\n\nconst handleBoundEvent = (event: Event): void => {\n // @ts-expect-error-error\n let target = event?.target.closest(EVENT_SELECTOR)\n let propagationStopped = false\n\n const wrappedEvent = new Proxy(event, {\n get(target, prop) {\n if (prop === 'stopPropagation') {\n return () => {\n event.stopPropagation()\n propagationStopped = true\n }\n } else {\n const value = (target as any)[prop]\n return typeof value === 'function' ? value.bind(target) : value\n }\n },\n })\n const nohandlers = new Set<XinEventHandler>()\n while (!propagationStopped && target != null) {\n const eventBindings = elementToHandlers.get(target) as XinEventBindings\n const handlers = eventBindings[event.type] || nohandlers\n for (const handler of handlers) {\n if (typeof handler === 'function') {\n handler(wrappedEvent as Event & { target: Element })\n } else {\n const func = xin[handler]\n if (typeof func === 'function') {\n func(wrappedEvent)\n } else {\n throw new Error(`no event handler found at path ${handler}`)\n }\n }\n if (propagationStopped) {\n continue\n }\n }\n target =\n target.parentElement != null\n ? target.parentElement.closest(EVENT_SELECTOR)\n : null\n }\n}\n\ntype RemoveListener = VoidFunction\n\nexport function on<E extends HTMLElement, K extends EventType>(\n element: E,\n eventType: K,\n eventHandler: XinEventHandler<HTMLElementEventMap[K], E>\n): RemoveListener {\n let eventBindings = elementToHandlers.get(element)\n element.classList.add(EVENT_CLASS)\n if (eventBindings == null) {\n eventBindings = {}\n elementToHandlers.set(element, eventBindings)\n }\n if (!eventBindings[eventType]) {\n eventBindings[eventType] = new Set<XinEventHandler>()\n }\n eventBindings[eventType].add(eventHandler as XinEventHandler)\n if (!handledEventTypes.has(eventType)) {\n handledEventTypes.add(eventType)\n document.body.addEventListener(eventType, handleBoundEvent, true)\n }\n return () => {\n eventBindings[eventType].delete(eventHandler as XinEventHandler)\n }\n}\n",
|
|
24
|
+
"/*#\n# 4. web-components\n\n**xinjs** provides the abstract `Component` class to make defining custom-elements\neasier.\n\n## Component\n\nTo define a custom-element you can subclass `Component`, simply add the properties\nand methods you want, with some help from `Component` itself, and then simply\nexport your new class's `elementCreator()` which is a function that defines your\nnew component's element and produces instances of it as needed.\n\n```\nimport {Component} from 'xinjs'\n\nclass ToolBar extends Component {\n static styleSpec = {\n ':host': {\n display: 'flex',\n gap: '10px',\n },\n }\n}\n\nexport const toolBar = ToolBar.elementCreator({ tag: 'tool-bar' })\n```\n\nThis component is just a structural element. By default a `Component` subclass will\ncomprise itself and a `<slot>`. You can change this by giving your subclass its\nown `content` template.\n\nThe last line defines the `ToolBar` class as the implementation of `<tool-bar>`\nHTML elements (`tool-bar` is derived automatically from the class name) and\nreturns an `ElementCreator` function that creates `<tool-bar>` elements.\n\nSee [elements](/?elements.ts) for more information on `ElementCreator` functions.\n\n### Component properties\n\n#### content: Element | Element[] | () => Element | () => Element[] | null\n\nHere's a simple example of a custom-element that simply produces a\n`<label>` wrapped around `<span>` and an `<input>`. Its value is synced\nto that of its `<input>` so the user doesn't need to care about how\nit works internally.\n\n```js\nimport { Component, elements } from 'tosijs'\n\nclass LabeledInput extends Component {\n caption = 'untitled'\n value = ''\n\n constructor() {\n super()\n this.initAttributes('caption')\n }\n\n content = ({label, span, input}) => label(span(), input())\n\n connectedCallback() {\n super.connectedCallback()\n const {input} = this.parts\n input.addEventListener('input', () => {\n this.value = input.value\n })\n }\n\n render() {\n super.render()\n const {span, input} = this.parts\n span.textContent = this.caption\n if (input.value !== this.value) {\n input.value = this.value\n }\n }\n}\n\nconst labeledInput = LabeledInput.elementCreator()\n\npreview.append(\n labeledInput({caption: 'A text field', value: 'some text'})\n)\n```\n\n`content` is, in essence, a template for the internals of the element. By default\nit's a single `<slot>` element. If you explicitly want an element with no content\nyou can set your subclass's content to `null` or omit any `<slot>` from its template.\n\nBy setting content to be a function that returns elements instead of a collection\nof elements you can take customize elements based on the component's properties.\nIn particular, you can use `onXxxx` syntax sugar to bind events.\n\n(Note that you cannot bind to xin paths reliably if your component uses a `shadowDOM`\nbecause `xin` cannot \"see\" elements there. As a general rule, you need to take care\nof anything in the `shadowDOM` yourself.)\n\nIf you'd like to see a more complex example along the same lines, look at\n[xin-form and xin-field](https://ui.xinjs.net/?form.ts).\n\n##### <slot> names and the `slot` attribute\n\n```\nclass MenuBar extends Component {\n static styleSpec = {\n ':host, :host > slot': {\n display: 'flex',\n },\n ':host > slot:nth-child(1)': {\n flex: '1 1 auto'\n },\n }\n\n content = ({slot}) => [slot(), slot({name: 'gadgets'})]\n}\n\nexport menuBar = MenuBar.elementCreator()\n```\n\nOne of the neat things about custom-elements is that you can give them *multiple*\n`<slot>`s with different `name` attributes and then have children target a specific\nslot using the `slot` attribute.\n\nThis app's layout (the nav sidebar that disappears if the app is in a narrow space, etc.)\nis built using just such a custom-element.\n\n#### `<xin-slot>`\n\nIf you put `<slot>` elements inside a `Component` subclass that doesn't have a\nshadowDOM, they will automatically be replaced with `<xin-slot>` elements that\nhave the expected behavior (i.e. sucking in children in based on their `<slot>`\nattribute).\n\n`<xin-slot>` doesn't support `:slotted` but since there's no shadowDOM, just\nstyle such elements normally, or use `xin-slot` as a CSS-selector.\n\nNote that you cannot give a `<slot>` element attributes (other than `name`) so if\nyou want to give a `<xin-slot>` attributes (such as `class` or `style`), create it\nexplicitly (e.g. using `elements.xinSlot()`) rather than using `<slot>` elements\nand letting them be switched out (because they'll lose any attributes you give them).\n\nHere's a very simple example:\n\n```js\nimport { Component, elements } from 'tosijs'\n\nclass FauxSlotExample extends Component {\n content = ({h4, h5, xinSlot}) => [\n h4('This is a web-component with no shadow DOM and working slots!'),\n h5('top slot'),\n xinSlot({name: 'top'}),\n h5('middle slot'),\n xinSlot(),\n h5('bottom slot'),\n xinSlot({name: 'bottom'}),\n ]\n}\n\nconst fauxSlotExample = FauxSlotExample.elementCreator({\n tag: 'faux-slot-example',\n styleSpec: {\n ':host': {\n display: 'flex',\n flexDirection: 'column'\n },\n ':host h4, :host h5': {\n margin: 0,\n },\n ':host xin-slot': {\n border: '2px solid grey'\n }\n }\n})\n\nconst { div } = elements\n\npreview.append(\n fauxSlotExample(\n div({slot: 'bottom'}, 'I should be on the bottom'),\n div({slot: 'top'}, 'I should be on the top'),\n div('I should be in the middle')\n )\n)\n```\n\n> ##### Background\n>\n> `<slot>` elements do not work as expected in shadowDOM-less components. This is\n> hugely annoying since it prevents components from composing nicely unless they\n> have a shadowDOM, and while the shadowDOM is great for small widgets, it's\n> terrible for composite views and breaks `tosijs`'s bindings (inside the shadow\n> DOM you need to do data- and event- binding manually).\n\n#### styleNode: HTMLStyleElement\n\n`styleNode` is the `<style>` element that will be inserted into the element's\n`shadowRoot`.\n\nIf a `Component` subclass has no `styleNode`, no `shadowRoot` will be\ncreated. This reduces the memory and performance cost of the element.\n\nThis is to avoid the performance/memory costs associated with the `shadowDOM`\nfor custom-elements with no styling.\n\n##### Notes\n\nStyling custom-elements can be tricky, and it's worth learning about\nhow the `:host` and `:slotted()` selectors work.\n\nIt's also very useful to understand how CSS-Variables interact with the\n`shadowDOM`. In particular, CSS-variables are passed into the `shadowDOM`\nwhen other CSS rules are not. You can use css rules to modify css-variables\nwhich will then penetrate the `shadowDOM`.\n\n#### refs: {[key:string]: Element | undefined}\n\n render() {\n super.render() // see note\n const {span, input} = this.parts\n span.textContent = this.caption\n if (input.value !== this.value) {\n input.value = this.value\n }\n }\n\n> **Note**: the `render()` method of the base `Component` class doesn't currently\n> do anything, so calling it is optional (but a good practice in case one day…)\n>\n> It is *necessary* however to call `super.connectedCallback`, `super.disconnectedCallback`\n> and `super()` in the `constructor()` should you override them.\n\n`this.parts` returns a proxy that provides elements conveniently and efficiently. It\nis intended to facilitate access to static elements (it memoizes its values the\nfirst time they are computed).\n\n`this.parts.foo` will return a content element with `data-ref=\"foo\"`. If no such\nelement is found it tries it as a css selector, so `this.parts['.foo']` would find\na content element with `class=\"foo\"` while `this.parts.h1` will find an `<h1>`.\n\n`this.parts` will also remove a `data-ref` attribute once it has been used to find\nthe element. This means that if you use all your refs in `render` or `connectedCallback`\nthen no trace will remain in the DOM for a mounted element.\n\n### Component properties\n\n#### content: ((elements: ElementsProxy) => ContentType) | null | ContentType = slot()\n\nA component's content `property` can either be static content (it defaults to being a `<slot>` element) or an arrow function\nthat creates the basic content of the element on hydration. Static content will be deep-cloned.\n\nBy using an arrow function the content created can refer to the custom-element's properties and attributes (and this occurs post-initialization). This also means you can bind event-handlers in the component (which should also be arrow functions unless they don't need to refer to the element)\n\nBecause a `content` function is passed the `elements` proxy, you can easily destructure any element creators you need:\n\n```\ncontent = ({div}) => div('hello world')\n```\n\n`ContentType` can be an HTMLElement or an array of elements.\n\n> Note that if a component does not use the shadowDOM, its `<slot>` elements will be replaced with `<xin-slot>` elements.\n> This allows composition to work as expected without requiring the shadow DOM.\n\n### Component methods\n\n#### initAttributes(...attributeNames: string[])\n\n class LabeledInput extends Component {\n caption: string = 'untitled'\n value: string = ''\n\n constructor() {\n super()\n this.initAttributes('caption')\n }\n\n ...\n }\n\nSets up basic behavior such as queueing a render if an attribute is changed, setting\nattributes based on the DOM source, updating them if they're changed, implementing\nboolean attributes in the expected manner, and so forth.\n\nCall `initAttributes` in your subclass's `constructor`, and make sure to call `super()`.\n\n#### queueRender(triggerChangeEvent = false): void\n\nUses `requestAnimationFrame` to queue a call to the component's `render` method. If\ncalled with `true` it will also trigger a `change` event.\n\n#### private initValue(): void\n\n**Don't call this!** Sets up expected behavior for an `HTMLElement` with\na value (i.e. triggering a `change` events and `render` when the `value` changes).\n\n#### private hydrate(): void\n\n**Don't call this** Appends `content` to the element (or its `shadowRoot` if it has a `styleNode`)\n\n#### connectedCallback(): void\n\nIf the class has an `onResize` handler then a ResizeObserver will trigger `resize`\nevents on the element when its size changes and `onResize` will be set up to respond\nto `resize` events.\n\nAlso, if the subclass has defined `value`, calls `initValue()`.\n\n`connectedCallback` is a great place to attach **event-handlers** to elements in your component.\n\nBe sure to call `super.connectedCallback()` if you implement `connectedCallback` in the subclass.\n\n#### disconnectedCallback(): void\n\nBe sure to call `super.disconnectedCallback()` if you implement `disconnectedCallback` in the subclass.\n\n#### render(): void\n\nBe sure to call `super.render()` if you implement `render` in the subclass.\n\n### Component static properties\n\n#### Component.elements\n\n const {label, span, input} = Component.elements\n\nThis is simply provided as a convenient way to get to [elements](/?elements.ts)\n\n### Component static methods\n\n#### Component.elementCreator(options? {tag?: string, styleSpec: XinStyleSheet}): ElementCreator\n\n export const toolBar = ToolBar.elementCreator({tag: 'tool-bar'})\n\nReturns a function that creates the custom-element. If you don't pass a `tag` or if the provided tag\nis already in use, a new unique tag will be used.\n\nIf no tag is provided, the Component will try to use introspection to \"snake-case\" the\n\"ClassName\", but if you're using name mangling this won't work and you'll get something\npretty meaningless.\n\nIf you want to create a global `<style>` sheet for the element (especially useful if\nyour component doesn't use the `shadowDOM`) then you can pass `styleSpec`. E.g.\n\n export const toolBar = ToolBar.elementCreator({\n tag: 'tool-bar',\n styleSpec: {\n ':host': { // note that ':host' will be turned into the tagName automatically!\n display: 'flex',\n padding: 'var(--toolbar-padding, 0 8px)',\n gap: '4px'\n }\n }\n })\n\nThis will—assuming \"tool-bar\" is available—create:\n\n <style id=\"tool-bar-helper\">\n tool-bar {\n display: flex;\n padding: var(--toolbar-padding, 0 8px);\n gap: 4px;\n }\n <style>\n\nAnd append it to `document.head` when the first instance of `<tool-bar>` is inserted in the DOM.\n\nFinally, `elementCreator` is memoized and only generated once (and the arguments are\nignored on all subsequent calls).\n\n## Examples\n\n[xinjs-ui](https://ui.xinjs.net) is a component library built using this `Component` class\nthat provides the essential additions to standard HTML elements needed to build many\nuser-interfaces.\n\n- [xin-example](https://ui.xinjs.net/https://ui.xinjs.net/?live-example.ts) uses multiple named slots to implement\n powers the interactive examples used for this site.\n- [xin-sidebar](https://ui.xinjs.net/?side-nav.ts) implements the sidebar navigation\n used on this site.\n- [xin-table](https://ui.xinjs.net/?data-table.ts) implements virtualized tables\n with resizable, reorderable, sortable columns that can handle more data\n than you're probably willing to load.\n- [xin-form and xin-field](https://ui.xinjs.net/?form.ts) allow you to\n quickly create forms that leverage all the built-in functionality of `<input>`\n elements (including powerful validation) even for custom-fields.\n- [xin-md](https://ui.xinjs.net/?markdown-viewer.ts) uses `marked` to render\n markdown.\n- [xin-3d](https://ui.xinjs.net/?babylon-3d.ts) lets you easily embed 3d scenes\n in your application using [babylonjs](https://babylonjs.com/)\n*/\nimport { css } from './css'\nimport { XinStyleSheet } from './css-types'\nimport { deepClone } from './deep-clone'\nimport { appendContentToElement, dispatch, resizeObserver } from './dom'\nimport { ElementsProxy } from './elements-types'\nimport { elements } from './elements'\nimport { camelToKabob, kabobToCamel } from './string-case'\nimport { ElementCreator, ContentType, PartsMap } from './xin-types'\n\nlet anonymousElementCount = 0\n\nfunction anonElementTag(): string {\n return `custom-elt${(anonymousElementCount++).toString(36)}`\n}\nlet instanceCount = 0\n\ninterface ElementCreatorOptions extends ElementDefinitionOptions {\n tag?: string\n styleSpec?: XinStyleSheet\n}\n\nconst globalStyleSheets: {\n [key: string]: string\n} = {}\n\nfunction setGlobalStyle(tagName: string, styleSpec: XinStyleSheet) {\n const existing = globalStyleSheets[tagName]\n const processed = css(styleSpec).replace(/:host\\b/g, tagName)\n globalStyleSheets[tagName] = existing\n ? existing + '\\n' + processed\n : processed\n}\n\nfunction insertGlobalStyles(tagName: string) {\n if (globalStyleSheets[tagName]) {\n document.head.append(\n elements.style({ id: tagName + '-component' }, globalStyleSheets[tagName])\n )\n }\n delete globalStyleSheets[tagName]\n}\n\nexport abstract class Component<T = PartsMap> extends HTMLElement {\n static elements: ElementsProxy = elements\n private static _elementCreator?: ElementCreator<Component>\n instanceId: string\n styleNode?: HTMLStyleElement\n static styleSpec?: XinStyleSheet\n static styleNode?: HTMLStyleElement\n content: ContentType | ((e: typeof elements) => ContentType) | null =\n elements.slot()\n isSlotted?: boolean\n private static _tagName: null | string = null\n static get tagName(): null | string {\n return this._tagName\n }\n [key: string]: any\n\n static StyleNode(styleSpec: XinStyleSheet): HTMLStyleElement {\n console.warn(\n 'StyleNode is deprecated, just assign static styleSpec: XinStyleSheet to the class directly'\n )\n return elements.style(css(styleSpec))\n }\n\n static elementCreator<C = Component>(\n this: new () => C,\n options: ElementCreatorOptions = {}\n ): ElementCreator<C> {\n const componentClass = this as unknown as Component\n if (componentClass._elementCreator == null) {\n const { tag, styleSpec } = options\n let tagName = options != null ? tag : null\n if (tagName == null) {\n if (\n typeof componentClass.name === 'string' &&\n componentClass.name !== ''\n ) {\n tagName = camelToKabob(componentClass.name)\n if (tagName.startsWith('-')) {\n tagName = tagName.slice(1)\n }\n } else {\n tagName = anonElementTag()\n }\n }\n if (customElements.get(tagName) != null) {\n console.warn(`${tagName} is already defined`)\n }\n if (tagName.match(/\\w+(-\\w+)+/) == null) {\n console.warn(`${tagName} is not a legal tag for a custom-element`)\n tagName = anonElementTag()\n }\n while (customElements.get(tagName) !== undefined) {\n tagName = anonElementTag()\n }\n componentClass._tagName = tagName\n if (styleSpec !== undefined) {\n setGlobalStyle(tagName, styleSpec)\n }\n window.customElements.define(\n tagName,\n this as unknown as CustomElementConstructor,\n options\n )\n componentClass._elementCreator = elements[tagName]\n }\n return componentClass._elementCreator\n }\n\n initAttributes(...attributeNames: string[]): void {\n const attributes: { [key: string]: any } = {}\n const attributeValues: { [key: string]: any } = {}\n const observer = new MutationObserver((mutationsList) => {\n let triggerRender = false\n mutationsList.forEach((mutation) => {\n triggerRender = !!(\n mutation.attributeName &&\n attributeNames.includes(kabobToCamel(mutation.attributeName))\n )\n })\n if (triggerRender && this.queueRender !== undefined)\n this.queueRender(false)\n })\n observer.observe(this, { attributes: true })\n attributeNames.forEach((attributeName) => {\n attributes[attributeName] = deepClone(this[attributeName])\n const attributeKabob = camelToKabob(attributeName)\n Object.defineProperty(this, attributeName, {\n enumerable: false,\n get() {\n if (typeof attributes[attributeName] === 'boolean') {\n return this.hasAttribute(attributeKabob)\n } else {\n if (this.hasAttribute(attributeKabob)) {\n return typeof attributes[attributeName] === 'number'\n ? parseFloat(this.getAttribute(attributeKabob))\n : this.getAttribute(attributeKabob)\n } else if (attributeValues[attributeName] !== undefined) {\n return attributeValues[attributeName]\n } else {\n return attributes[attributeName]\n }\n }\n },\n set(value) {\n if (typeof attributes[attributeName] === 'boolean') {\n if (value !== this[attributeName]) {\n if (value) {\n this.setAttribute(attributeKabob, '')\n } else {\n this.removeAttribute(attributeKabob)\n }\n this.queueRender()\n }\n } else if (typeof attributes[attributeName] === 'number') {\n if (value !== parseFloat(this[attributeName])) {\n this.setAttribute(attributeKabob, value)\n this.queueRender()\n }\n } else {\n if (\n typeof value === 'object' ||\n `${value as string}` !== `${this[attributeName] as string}`\n ) {\n if (\n value === null ||\n value === undefined ||\n typeof value === 'object'\n ) {\n this.removeAttribute(attributeKabob)\n } else {\n this.setAttribute(attributeKabob, value)\n }\n this.queueRender()\n attributeValues[attributeName] = value\n }\n }\n },\n })\n })\n }\n\n private initValue(): void {\n const valueDescriptor = Object.getOwnPropertyDescriptor(this, 'value')\n if (\n valueDescriptor === undefined ||\n valueDescriptor.get !== undefined ||\n valueDescriptor.set !== undefined\n ) {\n return\n }\n let value = this.hasAttribute('value')\n ? this.getAttribute('value')\n : deepClone(this.value)\n delete this.value\n Object.defineProperty(this, 'value', {\n enumerable: false,\n get() {\n return value\n },\n set(newValue: any) {\n if (value !== newValue) {\n value = newValue\n this.queueRender(true)\n }\n },\n })\n }\n\n private _parts?: T\n get parts(): T {\n const root = this.shadowRoot != null ? this.shadowRoot : this\n if (this._parts == null) {\n this._parts = new Proxy(\n {},\n {\n get(target: any, ref: string) {\n if (target[ref] === undefined) {\n let element = root.querySelector(`[part=\"${ref}\"]`)\n if (element == null) {\n element = root.querySelector(ref)\n }\n if (element == null)\n throw new Error(`elementRef \"${ref}\" does not exist!`)\n element.removeAttribute('data-ref')\n target[ref] = element as Element\n }\n return target[ref]\n },\n }\n ) as T\n }\n return this._parts\n }\n\n constructor() {\n super()\n instanceCount += 1\n this.initAttributes('hidden')\n this.instanceId = `${this.tagName.toLocaleLowerCase()}-${instanceCount}`\n this._value = deepClone(this.defaultValue)\n }\n\n connectedCallback(): void {\n insertGlobalStyles((this.constructor as unknown as Component).tagName)\n this.hydrate()\n // super annoyingly, chrome loses its shit if you set *any* attributes in the constructor\n if (this.role != null) this.setAttribute('role', this.role)\n if (this.onResize !== undefined) {\n resizeObserver.observe(this)\n if (this._onResize == null) {\n this._onResize = this.onResize.bind(this)\n }\n this.addEventListener('resize', this._onResize)\n }\n if (this.value != null && this.getAttribute('value') != null) {\n this._value = this.getAttribute('value')\n }\n this.queueRender()\n }\n\n disconnectedCallback(): void {\n resizeObserver.unobserve(this)\n }\n\n private _changeQueued = false\n private _renderQueued = false\n queueRender(triggerChangeEvent = false): void {\n if (!this._hydrated) return\n if (!this._changeQueued) this._changeQueued = triggerChangeEvent\n if (!this._renderQueued) {\n this._renderQueued = true\n requestAnimationFrame(() => {\n // TODO add mechanism to allow component developer to have more control over\n // whether input vs. change events are emitted\n if (this._changeQueued) dispatch(this, 'change')\n this._changeQueued = false\n this._renderQueued = false\n this.render()\n })\n }\n }\n\n private _hydrated = false\n private hydrate(): void {\n if (!this._hydrated) {\n this.initValue()\n const cloneElements = typeof this.content !== 'function'\n const _content: ContentType | null =\n typeof this.content === 'function'\n ? this.content(elements)\n : this.content\n\n const { styleSpec } = this.constructor as unknown as Component\n let { styleNode } = this.constructor as unknown as Component\n if (styleSpec) {\n styleNode = (this.constructor as unknown as Component).styleNode =\n elements.style(css(styleSpec))\n delete (this.constructor as unknown as Component).styleNode\n }\n if (this.styleNode) {\n console.warn(\n this,\n 'styleNode is deprecrated, use static styleNode or statc styleSpec instead'\n )\n styleNode = this.styleNode\n }\n if (styleNode) {\n const shadow = this.attachShadow({ mode: 'open' })\n shadow.appendChild(styleNode.cloneNode(true))\n appendContentToElement(shadow, _content, cloneElements)\n } else if (_content !== null) {\n const existingChildren = Array.from(this.childNodes)\n appendContentToElement(this as HTMLElement, _content, cloneElements)\n this.isSlotted = this.querySelector('slot,xin-slot') !== undefined\n const slots = Array.from(this.querySelectorAll('slot'))\n if (slots.length > 0) {\n slots.forEach(XinSlot.replaceSlot)\n }\n if (existingChildren.length > 0) {\n const slotMap: { [key: string]: Element } = { '': this }\n Array.from(this.querySelectorAll('xin-slot')).forEach((slot) => {\n slotMap[(slot as XinSlot).name] = slot\n })\n existingChildren.forEach((child) => {\n const defaultSlot = slotMap['']\n const destSlot =\n child instanceof Element ? slotMap[child.slot] : defaultSlot\n ;(destSlot !== undefined ? destSlot : defaultSlot).append(child)\n })\n }\n }\n this._hydrated = true\n }\n }\n\n render(): void {}\n}\n\ninterface SlotParts extends PartsMap {\n slotty: HTMLSlotElement\n}\n\nclass XinSlot extends Component<SlotParts> {\n name = ''\n content = null\n\n static replaceSlot(slot: HTMLSlotElement): void {\n const _slot = document.createElement('xin-slot')\n if (slot.name !== '') {\n _slot.setAttribute('name', slot.name)\n }\n slot.replaceWith(_slot)\n }\n\n constructor() {\n super()\n this.initAttributes('name')\n }\n}\n\nexport const xinSlot = XinSlot.elementCreator({ tag: 'xin-slot' })\n",
|
|
24
25
|
"/*#\n# A.3 hotReload\n\n`hotReload()` persists any root-level paths in `xin` that its test function evaluates as true\nto `localStorage`.\n\n```\nhotReload(test: PathTestFunction = () => true): void\n```\n*/\nimport { xin, observe } from './xin'\nimport { xinValue } from './metadata'\nimport {\n XinObject,\n PathTestFunction,\n ObserverCallbackFunction,\n} from './xin-types'\nimport { debounce } from './throttle'\n\n// TODO reimplement using IndexedDB\n\nexport const hotReload = (test: PathTestFunction = () => true): void => {\n const savedState = localStorage.getItem('xin-state')\n if (savedState != null) {\n const state = JSON.parse(savedState)\n for (const key of Object.keys(state).filter(test)) {\n if (xin[key] !== undefined) {\n Object.assign(xin[key], state[key])\n } else {\n xin[key] = state[key]\n }\n }\n }\n\n const saveState = debounce(() => {\n const obj: XinObject = {}\n const state = xinValue(xin)\n for (const key of Object.keys(state).filter(test)) {\n obj[key] = state[key]\n }\n localStorage.setItem('xin-state', JSON.stringify(obj))\n console.log('xin state saved to localStorage')\n }, 500)\n\n observe(test, saveState as ObserverCallbackFunction)\n}\n",
|
|
25
|
-
"export const version = '1.0.
|
|
26
|
-
"/*#\n# 1.1 tosi, xin, and xinProxy\n\n> This documentation is mainly here for explanatory purposes. Just use `tosi()`\n> as described in section 1.\n\nThe key to managing application state with `
|
|
27
|
-
"/*#\n# 4.2 makeComponent\n\n`makeComponent(tag: string, bluePrint: XinBlueprint<T>): Promise<XinComponentSpec<T>>`\nhydrates [blueprints](/?blueprint-loader.ts) into usable [web-components](./?component.ts).\n\nHere are the relevant interfaces:\n\n```\nexport interface PartsMap<T = Element> {\n [key: string]: T\n}\n\nexport type XinBlueprint<T = PartsMap> = (\n tag: string,\n module: XinFactory\n) => XinComponentSpec<T> | Promise<XinComponentSpec<T>>\n\nexport interface XinComponentSpec<T = PartsMap> {\n type: Component<T>\n styleSpec?: XinStyleSheet\n}\n```\n\nNote that a crucial benefit of blueprints is that the **consumer** of the blueprint gets\nto choose the `tagName` of the custom-element.\n*/\n\nimport { Color } from './color'\nimport { Component } from './component'\nimport { vars, varDefault } from './css'\nimport { XinStyleSheet } from './css-types'\nimport { bind, on } from './bind'\nimport { elements, svgElements, mathML } from './elements'\nimport { ElementCreator, PartsMap } from './xin-types'\nimport { version } from './version'\nimport { xin, boxed } from './xin'\nimport { xinProxy, boxedProxy } from './xin-proxy'\n\nexport interface XinFactory {\n Color: typeof Color\n Component: typeof Component\n elements: typeof elements\n svgElements: typeof svgElements\n mathML: typeof mathML\n vars: typeof vars\n varDefault: typeof varDefault\n xin: typeof xin\n boxed: typeof boxed\n xinProxy: typeof xinProxy\n boxedProxy: typeof boxedProxy\n makeComponent: typeof makeComponent\n bind: typeof bind\n on: typeof on\n version: string\n}\n\nexport interface XinComponentSpec<T = PartsMap> {\n type: Component<T>\n styleSpec?: XinStyleSheet\n}\n\nexport interface XinPackagedComponent<T = PartsMap> {\n type: Component<T>\n creator: ElementCreator\n}\n\nexport const madeComponents: { [key: string]: XinPackagedComponent<any> } = {}\n\nexport type XinBlueprint<T = PartsMap> = (\n tag: string,\n module: XinFactory\n) => XinComponentSpec<T> | Promise<XinComponentSpec<T>>\n\nexport async function makeComponent<T = PartsMap>(\n tag: string,\n blueprint: XinBlueprint<T>\n): Promise<XinPackagedComponent<T>> {\n const { type, styleSpec } = (await blueprint(tag, {\n Color,\n Component,\n elements,\n svgElements,\n mathML,\n varDefault,\n vars,\n xin,\n boxed,\n xinProxy,\n boxedProxy,\n makeComponent,\n bind,\n on,\n version,\n })) as XinComponentSpec<T>\n const packagedComponent = {\n type,\n creator: type.elementCreator({ tag, styleSpec }),\n }\n\n madeComponents[tag] = packagedComponent\n return packagedComponent\n}\n",
|
|
28
|
-
"/*#\n# 4.1 blueprints\n\nOne issue with standard web-components built with xinjs is that building them\n\"sucks in\" the version of `
|
|
26
|
+
"export const version = '1.0.10'",
|
|
27
|
+
"/*#\n# 1.1 tosi, xin, and xinProxy\n\n> This documentation is mainly here for explanatory purposes. Just use `tosi()`\n> as described in section 1.\n\nThe key to managing application state with `tosijs` is the `xin` proxy object\n(and `boxed`). These are documented [here](/?xin.ts).\n\n## `xinProxy()` and `tosi()`\n\n> `tosi()` was formerly called `boxedProxy()`.\n\nAfter coding with `xin` for a while, it became apparent that a common pattern\nwas something like this:\n\n import myThing as _myThing from 'path/to/my-thing'\n import { xin } from 'xinjs'\n\n xin.myThing = _myThing\n export const myThing = xin.myThing as typeof _myThing\n\nNow we can use the new `myThing` in a pretty intuitive way, leverage autocomplete\nmost of the time, and it's all pretty nice.\n\nAnd because `myThing.path.to.something` is actually a `XinProxy` we can actually\nbind to it directly. So instead of typing (or mis-typing):\n\n customElement({bindValue: 'mything.path.to.something'}))\n\nWe can type the following and even use autocomplete:\n\n customElement({bindValue: mything.path.to.something}))\n\nThis gets you:\n\n const { myThing } = xinProxy({ myThing: ... })\n\nAnd after working with that for a while, the question became, what if we could\nleverage autocomplete even for non-object properties?\n\nThis gets us to:\n\n const { myThing } = boxedProxy({ myThing: ... })\n\n…(and also `boxed`).\n\n`boxed` and `boxedProxy` deliver a proxy wrapped around an object wrapped around\nthe original `string`, `boolean`, or `number`. This gets you autocomplete and\nstrong typing in general, at the cost of slight annoyances (e.g. having to write\n`myThing.path.to.string.valueOf() === 'some value'`). That's the tradeoff. In\npractice it's really very nice.\n\n`xinProxy(foo)` is simply declared as a function that takes an object of type T and\nreturns a BoxedProxy<T>.\n\n import { xinProxy } from 'xinjs'\n\n const { foo, bar } = boxedProxy({\n foo: 'bar',\n bar: {\n director: 'luhrmann'\n }\n })\n\nThis is syntax sugar for:\n\n import { boxed } from 'xinjs'\n\n const stuff = {\n foo: 'bar',\n bar: {\n director: 'luhrmann',\n born: 1962\n }\n }\n\n Object.assign(boxed, stuff)\n\n const { foo, bar } = boxed as XinProxy<typeof stuff>\n\nSo, Typescript will know that `foo` is a `string` and `bar` is a `XinProxy<typeof stuff.bar>`.\n\nNow, `boxedProxy` is the same except replace `XinProxy` with `BoxedProxy` and\nnow Typescript will know that `foo` is a `BoxedProxy<string>`, `bar` is a `BoxedProxy<typeof stuff.bar>`\nand `bar.born` is a `BoxedProxy<number>`.\n\nThis lets you write bindings that support autocomplete and lint. Yay!\n*/\nimport { XinProxy, BoxedProxy } from './xin-types'\nimport { xin, boxed } from './xin'\n\nexport function tosi<T extends object>(obj: T): BoxedProxy<T> {\n Object.assign(boxed, obj)\n return boxed as BoxedProxy<T>\n}\n\nexport function boxedProxy<T extends object>(obj: T): BoxedProxy<T> {\n console.warn('boxedProxy is deprecated, please use tosi() instead')\n return tosi(obj)\n}\n\nexport function xinProxy<T extends object>(obj: T, boxed = false): XinProxy<T> {\n if (boxed) {\n console.warn(`xinProxy(..., true) is deprecated; use tosi(...) instead`)\n // @ts-expect-error deprecated\n return boxedProxy(obj)\n }\n Object.keys(obj).forEach((key: string) => {\n xin[key] = (obj as { [key: string]: any })[key]\n })\n return xin as XinProxy<T>\n}\n",
|
|
28
|
+
"/*#\n# 4.2 makeComponent\n\n`makeComponent(tag: string, bluePrint: XinBlueprint<T>): Promise<XinComponentSpec<T>>`\nhydrates [blueprints](/?blueprint-loader.ts) into usable [web-components](./?component.ts).\n\nHere are the relevant interfaces:\n\n```\nexport interface PartsMap<T = Element> {\n [key: string]: T\n}\n\nexport type XinBlueprint<T = PartsMap> = (\n tag: string,\n module: XinFactory\n) => XinComponentSpec<T> | Promise<XinComponentSpec<T>>\n\nexport interface XinComponentSpec<T = PartsMap> {\n type: Component<T>\n styleSpec?: XinStyleSheet\n}\n```\n\nNote that a crucial benefit of blueprints is that the **consumer** of the blueprint gets\nto choose the `tagName` of the custom-element.\n*/\n\nimport { Color } from './color'\nimport { Component } from './component'\nimport { vars, varDefault } from './css'\nimport { XinStyleSheet } from './css-types'\nimport { bind, on } from './bind'\nimport { elements, svgElements, mathML } from './elements'\nimport { ElementCreator, PartsMap } from './xin-types'\nimport { version } from './version'\nimport { xin, boxed } from './xin'\nimport { xinProxy, tosi, boxedProxy } from './xin-proxy'\n\nexport interface XinFactory {\n Color: typeof Color\n Component: typeof Component\n elements: typeof elements\n svgElements: typeof svgElements\n mathML: typeof mathML\n vars: typeof vars\n varDefault: typeof varDefault\n xin: typeof xin\n boxed: typeof boxed\n xinProxy: typeof xinProxy\n boxedProxy: typeof boxedProxy\n tosi: typeof tosi\n makeComponent: typeof makeComponent\n bind: typeof bind\n on: typeof on\n version: string\n}\n\nexport interface XinComponentSpec<T = PartsMap> {\n type: Component<T>\n styleSpec?: XinStyleSheet\n}\n\nexport interface XinPackagedComponent<T = PartsMap> {\n type: Component<T>\n creator: ElementCreator\n}\n\nexport const madeComponents: { [key: string]: XinPackagedComponent<any> } = {}\n\nexport type XinBlueprint<T = PartsMap> = (\n tag: string,\n module: XinFactory\n) => XinComponentSpec<T> | Promise<XinComponentSpec<T>>\n\nexport async function makeComponent<T = PartsMap>(\n tag: string,\n blueprint: XinBlueprint<T>\n): Promise<XinPackagedComponent<T>> {\n const { type, styleSpec } = (await blueprint(tag, {\n Color,\n Component,\n elements,\n svgElements,\n mathML,\n varDefault,\n vars,\n xin,\n boxed,\n xinProxy,\n boxedProxy,\n tosi,\n makeComponent,\n bind,\n on,\n version,\n })) as XinComponentSpec<T>\n const packagedComponent = {\n type,\n creator: type.elementCreator({ tag, styleSpec }),\n }\n\n madeComponents[tag] = packagedComponent\n return packagedComponent\n}\n",
|
|
29
|
+
"/*#\n# 4.1 blueprints\n\nOne issue with standard web-components built with xinjs is that building them\n\"sucks in\" the version of `tosijs` you're working with. This isn't a huge problem\nwith monolithic code-bases, but it does prevent components from being loaded\n\"on-the-fly\" from CDNs and composed on the spot and it does make it hard to\n\"tree shake\" component libraries.\n\n```js\nimport { elements, blueprintLoader, blueprint } from 'tosijs'\n\npreview.append(\n blueprintLoader(\n blueprint({\n tag: 'swiss-clock',\n src: 'https://tonioloewald.github.io/xin-clock/dist/blueprint.js?1234',\n blueprintLoaded({creator}) {\n preview.append(creator())\n }\n }),\n )\n)\n```\n\nAnother issue is name-collision. What if two people create a `<tab-selector>` component\nand you want to use both of them? Or you want to switch to a new and better one but\ndon't want to do it everywhere all at once?\n\nWith blueprints, the *consumer* of the component chooses the `tag`, reducing the\nchance of name-collision. (You can consume the same blueprint multiple times,\ngiving each one its own tag.)\n\nTo address these issues, `tosijs` provides a `<xin-loader>` loader component and\na function `makeComponent` that can define a component given a blueprint\nfunction.\n\n## `<xin-loader>`—the blueprint loader\n\n`<xin-loader>` is a simple custom-element provided by `tosijs` for the dynamic loading\nof component **blueprints**. It will load its `<xin-blueprint>`s in parallel.\n\n```\n<xin-loader>\n <xin-blueprint tag=\"swiss-clock\" src=\"https://loewald.com/lib/swiss-clock\"></xin-blueprint>\n</xin-loader>\n<swiss-clock>\n <code style=\"color: var(--brand-color)\">xinjs</code> rules!\n</swiss-clock>\n```\n\n### `<xin-blueprint>` Attributes\n\n- `src` is the url of the `blueprint` javascript module (required)\n- `tag` is the tagName you wish to use. This defaults to the name of the source file if suitable.\n- `property` allows you to load a named exported property from a blueprint module\n (allowing one blueprint to export multiple blueprints). By default, it's `default`.\n- `loaded` is the `XinPackagedComponent` after loading\n\n#### `<xin-blueprint>` Properties\n\n- `blueprintLoaded(package: XinPackagedComponent)` `<xin-blueprint>` when its blueprint is loaded.\n\n#### `<xin-loader>` Properties\n\n- `allLoaded()` is called when all the blueprints have loaded.\n\n## `makeComponent(tag: string, blueprint: XinBlueprint): Promise<XinPackagedCompoent>`\n\n`makeComponent` takes a `tag` of your choice and a `blueprint` and generates\nthe custom-element's `class` and `elementCreator` as its `type` and `creator`\nproperties.\n\nSo, instead of:\n\n import {myThing} from './path/to/my-thing'\n\n document.body.append(myThing())\n\nYou could write:\n\n import { makeComponent } from 'xinjs'\n import myThingBlueprint from './path/to/my-thing-blueprint'\n\n makeComponent('different-tag', myThingBlueprint).then((packaged) => {\n document.body.append(packaged.creator())\n })\n\nThis is a more complex example that loads two components and only generates\nthe test component once everything is ready:\n\n```js\nimport { blueprintLoader, blueprint } from 'tosijs'\n\nlet clockType = null\n\npreview.append(\n blueprintLoader(\n {\n allLoaded() {\n const xinTest = this.querySelector('[tag=\"xin-test\"]').loaded.creator\n preview.append(\n xinTest({\n description: `${clockType.tagName} registered`,\n test() {\n return (\n preview.querySelector(clockType.tagName) && preview.querySelector(clockType.tagName).constructor !==\n HTMLElement\n )\n },\n })\n )\n },\n },\n blueprint({\n tag: 'swiss-clock',\n src: 'https://tonioloewald.github.io/xin-clock/dist/blueprint.js?1234',\n blueprintLoaded({type, creator}) {\n clockType = type\n preview.append(creator())\n },\n }),\n blueprint({\n tag: 'xin-test',\n src: 'https://tonioloewald.github.io/xin-test/dist/blueprint.js',\n })\n )\n)\n```\n\n## `XinBlueprint`\n\n export interface XinFactory {\n Color: typeof Color\n Component: typeof Component\n elements: typeof elements\n svgElements: typeof svgElements\n mathML: typeof mathML\n vars: typeof vars\n varDefault: typeof varDefault\n xin: typeof xin\n boxed: typeof boxed\n xinProxy: typeof xinProxy\n boxedProxy: typeof boxedProxy // deprecated\n tosi: typeof tosi\n makeComponent: typeof makeComponent\n bind: typeof bind\n on: typeof on\n version: string\n }\n\n export interface XinPackagedComponent {\n type: typeof Component\n creator: ElementCreator\n }\n\n export type XinBlueprint = (\n tag: string,\n module: XinFactory\n ) => XinPackagedComponent\n\n`XinBlueprint` lets you provide a component \"blueprint\", in the form of a function,\nthat can be loaded and turned into an actual component. The beauty of this is that\nunlike an actual component, the blueprint has no special dependencies.\n\nSo instead of defining a component like this:\n\n import { Component, elements, vars, varDefault } from 'xinjs'\n\n const { h2, slot } = elements\n\n export class MyThing extends Component {\n static styleSpec = {\n ':host': {\n color: varDefault.textColor('#222'),\n background: vars.bgColor,\n },\n\n content = () => [\n h2('my thing'),\n slot()\n ]\n }\n }\n\n export const myThing = myThing.elementCreator({\n tag: 'my-thing',\n styleSpec: {\n _bgColor: '#f00'\n }\n })\n\nYou can define a \"blueprint\" like this:\n\n import { XinBlueprint } from 'xinjs'\n\n const blueprint: XinBlueprint = (\n tag,\n { Component, elements, vars, varDefault }\n ) => {\n const {h2, slot} = elements\n\n class MyThing extends Component {\n static styleSpec = {\n ':host': {\n color: varDefault.textColor('#222'),\n background: vars.bgColor,\n },\n\n content = () => [\n h2('my thing'),\n slot()\n ]\n }\n }\n\n return {\n type: MyThing,\n styleSpec: {\n _bgColor: '#f00'\n }\n }\n }\n\nThe blueprint function can be `async`, so you can use async import inside it to pull in dependencies.\n\n> **Note** that in this example the blueprint is a *pure* function (i.e. it has no side-effects).\n> If this blueprint is consumed twice, each will be completely independent. A non-pure blueprint\n> could be implemented such that the different versions of the blueprint share information.\n> E.g. you could maintain a list of all the instances of any version of the blueprint.\n\n*/\n\nimport { Component } from './component'\nimport {\n makeComponent,\n XinBlueprint,\n XinPackagedComponent,\n} from './make-component'\n\nconst loadedBlueprints: { [key: string]: Promise<XinPackagedComponent> } = {}\n\nconst loadModule = (src: string): Promise<any> => import(src)\n\nexport class Blueprint extends Component {\n tag = 'anon-elt'\n src = ''\n property = 'default'\n loaded?: XinPackagedComponent\n blueprintLoaded = (_package: XinPackagedComponent) => {}\n\n async packaged(): Promise<XinPackagedComponent> {\n const { tag, src, property } = this\n const signature = `${tag}.${property}:${src}`\n if (!this.loaded) {\n if (loadedBlueprints[signature] === undefined) {\n loadedBlueprints[signature] = loadModule(src).then((imported) => {\n const blueprint = imported[property] as XinBlueprint\n return makeComponent(tag, blueprint)\n })\n } else {\n console.log(`using cached ${tag} with signature ${signature}`)\n }\n this.loaded = await loadedBlueprints[signature]\n this.blueprintLoaded(this.loaded)\n }\n return this.loaded!\n }\n\n constructor() {\n super()\n\n this.initAttributes('tag', 'src', 'property')\n }\n}\n\nexport const blueprint = Blueprint.elementCreator({\n tag: 'xin-blueprint',\n styleSpec: { ':host': { display: 'none' } },\n})\n\nexport class BlueprintLoader extends Component {\n allLoaded = () => {}\n\n constructor() {\n super()\n }\n\n private async load() {\n const blueprintElements = (\n Array.from(\n this.querySelectorAll(Blueprint.tagName as string)\n ) as Blueprint[]\n ).filter((elt) => elt.src)\n const promises = blueprintElements.map((elt) => elt.packaged())\n await Promise.all(promises)\n this.allLoaded()\n }\n\n connectedCallback() {\n super.connectedCallback()\n\n this.load()\n }\n}\n\nexport const blueprintLoader = BlueprintLoader.elementCreator({\n tag: 'xin-loader',\n styleSpec: { ':host': { display: 'none' } },\n})\n"
|
|
29
30
|
],
|
|
30
|
-
"mappings": "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",
|
|
31
|
-
"debugId": "
|
|
31
|
+
"mappings": "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",
|
|
32
|
+
"debugId": "D83C5C39D6BE877264756E2164756E21",
|
|
32
33
|
"names": []
|
|
33
34
|
}
|