UNPKG

@lucidclient/elements

Version:

A lightweight, reactive UI library that bridges HTML and JavaScript through attributes, powered by SolidJS. Adds reactive state and dynamic behaviors to markup while maintaining simplicity.

1 lines 14.7 kB
{"version":3,"sources":["/Users/williamyallop/Documents/projects/protodigital/internal/lucid-client/packages/elements/dist/index.cjs","../src/core/store/initialise-store.ts","../src/core/state/watch-state.ts","../src/core/state/create-state.ts","../src/core/state/state-observer.ts","../src/core/bind/update-state-attributes.ts"],"names":["watchState","element","store","directives","state","key","signal","type","infer_value_type_default","registerStateEffect","produce","s","storeKey","bindState","createEffect","state_default","watch_state_default","createState","stateMap","defaultValue","createSignal","create_state_default","resolveBindingValue","attribute","targetKey","valueType","valueCache","bindValue","value","stringifyValue","evaluate_path_value_default","stringify_state_default","updateStateAttributes","parent","attributeMaps","stateWithPrefix"],"mappings":"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","file":"/Users/williamyallop/Documents/projects/protodigital/internal/lucid-client/packages/elements/dist/index.cjs","sourcesContent":[null,"import { createRoot } from \"solid-js\";\nimport { createStore } from \"solid-js/store\";\nimport type {\n\tStore,\n\tStoreData,\n\tStoreModule,\n\tStoreActions,\n\tStoreState,\n\tDirectiveMap,\n} from \"../../types/store.js\";\nimport { log } from \"../../helpers.js\";\nimport state from \"../state/index.js\";\nimport ref from \"../ref/index.js\";\nimport Elements from \"../elements.js\";\nimport getStoreInterface from \"./get-store-interface.js\";\nimport bind from \"../bind/index.js\";\nimport effect from \"../effect/index.js\";\nimport loop from \"../loop/index.js\";\n\n/**\n * Creates a store for the given element if one hasnt already been specified.\n * - Populates the store with attribute maps.\n * - Creates state.\n * - Observes state.\n * - Creates refs.\n */\nconst initialiseStore = (\n\telement: Element,\n\tstoreKey: string,\n\tdirectives: DirectiveMap | undefined,\n) => {\n\tcreateRoot((dispose) => {\n\t\t// -----------------\n\t\t// create store\n\t\tconst store = createStore<StoreData<StoreState, StoreActions>>({\n\t\t\tkey: storeKey,\n\t\t\tinitialised: false,\n\t\t\tdispose: dispose,\n\t\t\tstateRegisteredEffects: new Set(),\n\t\t\teffectsRegistered: new Set(),\n\t\t\tstate: {},\n\t\t\tactions: {},\n\t\t\teffects: {\n\t\t\t\tglobal: {},\n\t\t\t\tmanual: {},\n\t\t\t},\n\t\t\trefs: new Map(),\n\t\t}) satisfies Store<StoreState, StoreActions>;\n\n\t\t// get store module and update the store\n\t\tif (Elements.storeModules.has(storeKey)) {\n\t\t\tconst storeModuleFn = Elements.storeModules.get(storeKey) as StoreModule<\n\t\t\t\tStoreState,\n\t\t\t\tStoreActions\n\t\t\t>; // wrong generic type - doesnt matter currently\n\t\t\tconst storeModule = storeModuleFn(getStoreInterface(store));\n\n\t\t\tlog.debug(`Store module found for key \"${storeKey}\"`);\n\n\t\t\tif (storeModule.state) store[1](\"state\", storeModule.state);\n\t\t\tif (storeModule.actions) store[1](\"actions\", storeModule.actions);\n\t\t\tif (storeModule.effects) store[1](\"effects\", storeModule.effects);\n\t\t\tif (storeModule.cleanup) store[1](\"cleanup\", () => storeModule.cleanup);\n\t\t}\n\n\t\t// -----------------\n\t\t// set data\n\t\tstore[1](\"stateObserver\", state.stateObserver(element, store, directives));\n\n\t\t// -----------------\n\t\t// handle state, attribute bindings\n\t\tstate.createState(store, directives);\n\t\tstate.watchState(element, store, directives);\n\t\tref.createRefs(element, store);\n\t\tbind.registerActionEffects(store, directives);\n\t\teffect.registerEffects(store, directives);\n\t\tloop.registerLoops(store, directives);\n\n\t\t// -----------------\n\t\t// update Elements instance\n\t\tElements.stores.set(storeKey, store);\n\n\t\tstore[1](\"initialised\", true);\n\t\tvoid store[0].actions.init?.();\n\n\t\tlog.debug(\n\t\t\t`Store initialised for element \"${element.id || element.tagName}\" with key \"${storeKey}\"`,\n\t\t);\n\t});\n};\n\nexport default initialiseStore;\n","import type {\n\tStore,\n\tStoreState,\n\tStoreActions,\n\tDirectiveMap,\n\tBindStateDirectives,\n} from \"../../types/index.js\";\nimport { createEffect, type Signal } from \"solid-js\";\nimport state from \"./index.js\";\nimport { inferValueType } from \"../../helpers.js\";\nimport { produce } from \"solid-js/store\";\n\n/**\n * Registers effect for each state signal\n * - Updates the state attributes for the parent and all children\n * - Updates attribute bindings\n * - If we're updating a array or object, do nothing.\n * - If we're updating a string, number, boolean, etc, we update the state attribute.\n */\nconst watchState = (\n\telement: Element,\n\tstore: Store<StoreState, StoreActions>,\n\tdirectives: DirectiveMap | undefined,\n) => {\n\tconst state = store[0].state;\n\n\tfor (const [key, signal] of Object.entries(state)) {\n\t\t// check if the state has already had an effect registered for it\n\t\tif (store[0].stateRegisteredEffects.has(key)) continue;\n\n\t\t//* If the two lines bellow are added back, this will disable array/object state being two-way bound with data-state attributes and signals\n\t\t//* I havent decided what the behaviour here should be yet\n\t\t//* Enabled currently, as this causes loops to re-render 1 extra time\n\t\tconst type = inferValueType(signal[0]());\n\t\tif (type === \"object\" || type === \"array\") continue;\n\n\t\tregisterStateEffect(\n\t\t\telement,\n\t\t\tkey,\n\t\t\tsignal,\n\t\t\tstore[0].key,\n\t\t\tdirectives?.bindState,\n\t\t);\n\n\t\tstore[1](\n\t\t\tproduce((s) => {\n\t\t\t\ts.stateRegisteredEffects.add(key);\n\t\t\t}),\n\t\t);\n\t}\n};\n\n/**\n * Register effect for state signal updates to update the state attributes\n *\n * Attribute bindings are update by the state-observer.\n */\nconst registerStateEffect = (\n\telement: Element,\n\tkey: string,\n\tsignal: Signal<unknown>,\n\tstoreKey: string,\n\tbindState: BindStateDirectives | undefined,\n) => {\n\tcreateEffect(() => {\n\t\tstate.updateAttributes(\n\t\t\telement,\n\t\t\t{\n\t\t\t\tkey: key,\n\t\t\t\tvalue: signal[0](),\n\t\t\t},\n\t\t\tstoreKey,\n\t\t\tbindState,\n\t\t);\n\t}, undefined);\n};\n\nexport default watchState;\n","import { createSignal, type Signal } from \"solid-js\";\nimport { produce } from \"solid-js/store\";\nimport type {\n\tStore,\n\tStoreState,\n\tStoreActions,\n\tDirectiveMap,\n} from \"../../types/index.js\";\n\n/**\n * Creates a state object for the store\n * - For each state attribute in the attribute map, create a signal and add it to the state object\n */\nconst createState = (\n\tstore: Store<StoreState, StoreActions>,\n\tdirectives: DirectiveMap | undefined,\n) => {\n\tconst stateMap = directives?.state;\n\tif (!stateMap) return;\n\n\tstore[1](\n\t\tproduce((s) => {\n\t\t\tfor (const [key, defaultValue] of stateMap.entries()) {\n\t\t\t\tif (s.state[key] === undefined) {\n\t\t\t\t\ts.state[key] = createSignal(defaultValue) as Signal<unknown>;\n\t\t\t\t}\n\t\t\t}\n\t\t}),\n\t);\n};\n\nexport default createState;\n","import type {\n\tStore,\n\tStoreState,\n\tStoreActions,\n\tDirectiveMap,\n} from \"../../types/index.js\";\nimport { batch } from \"solid-js\";\nimport { parseStateString, buildAttribute } from \"../../helpers.js\";\nimport bind from \"../bind/index.js\";\nimport Elements from \"../elements.js\";\n\n/**\n * Handles a mutation on a state attribute\n * - Updates the state\n * - Updates the attribute bindings\n */\nexport const handleMutation = (\n\ttarget: Element,\n\tattribute: string,\n\toldValue: string | null,\n\tget: Store<StoreState, StoreActions>[0],\n\tstatePrefix: string,\n\tdirectives: DirectiveMap | undefined,\n\tinitial: boolean,\n) => {\n\tconst key = attribute.slice(statePrefix.length);\n\tconst attributeValue = target.getAttribute(attribute);\n\n\tif (attributeValue === oldValue) return;\n\n\tconst value = parseStateString(attributeValue);\n\n\tif (!directives) return;\n\n\t//* we only need need to update the state signal when the observer calls this, on init, the default value is already populated in the signal\n\tif (!initial) get.state[key]?.[1](value);\n\n\tbind.updateStateAttributes(target, { key, value }, directives);\n};\n\n/**\n * Registers a mutation observer for all of the stores state attributes\n * - Updates all attribute bindings\n *\n * This allows for you to update the state attributes outside of the Elements library and Elements will keep it in sync with its state\n */\nconst stateObserver = (\n\telement: Element,\n\tstore: Store<StoreState, StoreActions>,\n\tdirectives: DirectiveMap | undefined,\n): MutationObserver => {\n\tconst statePrefix = buildAttribute(\n\t\tElements.options.attributes.selectors.state,\n\t);\n\n\tconst stateAttributes = Array.from(directives?.state.keys() ?? []).map(\n\t\t(key) => `${statePrefix}${key}`,\n\t);\n\n\t// sync initial state to bind attributes\n\tfor (const attribute of stateAttributes) {\n\t\thandleMutation(\n\t\t\telement,\n\t\t\tattribute,\n\t\t\tnull,\n\t\t\tstore[0],\n\t\t\tstatePrefix,\n\t\t\tdirectives,\n\t\t\ttrue,\n\t\t);\n\t}\n\n\t// register mutation observer\n\tconst observer = new MutationObserver((mutations) => {\n\t\tbatch(() => {\n\t\t\tfor (const mutation of mutations) {\n\t\t\t\tif (\n\t\t\t\t\tmutation.type === \"attributes\" &&\n\t\t\t\t\tmutation.target instanceof Element &&\n\t\t\t\t\tmutation.attributeName\n\t\t\t\t) {\n\t\t\t\t\thandleMutation(\n\t\t\t\t\t\tmutation.target,\n\t\t\t\t\t\tmutation.attributeName,\n\t\t\t\t\t\tmutation.oldValue,\n\t\t\t\t\t\tstore[0],\n\t\t\t\t\t\tstatePrefix,\n\t\t\t\t\t\tdirectives,\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t});\n\n\t// configure the observer\n\tobserver.observe(element, {\n\t\tattributes: true,\n\t\tattributeFilter: stateAttributes,\n\t\tattributeOldValue: true,\n\t\t// subtree: true, //* state cannot be registered on children\n\t});\n\n\treturn observer;\n};\n\nexport default stateObserver;\n","import type { DirectiveMap } from \"../../types/index.js\";\nimport C from \"../constants.js\";\nimport Elements from \"../elements.js\";\nimport scope from \"../scope/index.js\";\nimport {\n\tevaluatePathValue,\n\tinferValueType,\n\tstringifyState,\n\tbuildAttribute,\n} from \"../../helpers.js\";\n\n/**\n * Updates a single element's attribute based on its binding value\n */\nconst resolveBindingValue = (\n\telement: Element,\n\tattribute: string,\n\ttargetKey: string,\n\tstate: {\n\t\tkey: string;\n\t\tvalue: unknown;\n\t},\n\tvalueType: string,\n\tvalueCache: Map<string, string>,\n) => {\n\tconst bindValue = element.getAttribute(attribute);\n\tif (!bindValue) return;\n\n\tlet value: string;\n\tif (valueCache.has(bindValue)) {\n\t\tvalue = valueCache.get(bindValue) as string;\n\t} else {\n\t\tlet stringifyValue: unknown;\n\n\t\tif (valueType === \"object\" || valueType === \"array\") {\n\t\t\tstringifyValue = evaluatePathValue(\n\t\t\t\tstate.value as Record<string, unknown> | Array<unknown>,\n\t\t\t\tbindValue,\n\t\t\t);\n\t\t} else {\n\t\t\tstringifyValue = state.value;\n\t\t}\n\n\t\tvalue = stringifyState(stringifyValue);\n\t\tvalueCache.set(bindValue, value);\n\t}\n\n\telement.setAttribute(targetKey, value);\n};\n\n/**\n * Updates the attribute bindings for state. Updates the target element and all children.\n */\nconst updateStateAttributes = (\n\tparent: Element,\n\tstate: {\n\t\tkey: string;\n\t\tvalue: unknown;\n\t},\n\tattributeMaps: DirectiveMap,\n) => {\n\tif (!attributeMaps?.bindState) return;\n\tconst stateWithPrefix = `${C.defaults.attributes.denote.state}${state.key}`;\n\tconst stateKey = scope.scopeValue(attributeMaps.scope, stateWithPrefix);\n\n\tconst affectedAttributes = attributeMaps.bindState.get(state.key);\n\tif (!affectedAttributes) return;\n\n\tconst bindPrefix = buildAttribute(Elements.options.attributes.selectors.bind);\n\tconst valueType = inferValueType(state.value);\n\tconst valueCache = new Map<string, string>();\n\n\tfor (const targetKey of affectedAttributes) {\n\t\tconst attribute = `${bindPrefix}${targetKey}`;\n\t\tconst selector = `[${attribute}^=\"${stateKey}\"]`;\n\n\t\tif (parent.matches(selector)) {\n\t\t\tresolveBindingValue(\n\t\t\t\tparent,\n\t\t\t\tattribute,\n\t\t\t\ttargetKey,\n\t\t\t\tstate,\n\t\t\t\tvalueType,\n\t\t\t\tvalueCache,\n\t\t\t);\n\t\t}\n\n\t\tfor (const element of parent.querySelectorAll(selector)) {\n\t\t\tresolveBindingValue(\n\t\t\t\telement,\n\t\t\t\tattribute,\n\t\t\t\ttargetKey,\n\t\t\t\tstate,\n\t\t\t\tvalueType,\n\t\t\t\tvalueCache,\n\t\t\t);\n\t\t}\n\t}\n};\n\nexport default updateStateAttributes;\n"]}