From 24f6962aa457d32319756f6217aafde7b0a9c752 Mon Sep 17 00:00:00 2001 From: Hayleigh Thompson Date: Tue, 23 Jan 2024 00:09:45 +0000 Subject: =?UTF-8?q?=E2=9C=A8=20Add=20universal=20components=20that=20can?= =?UTF-8?q?=20run=20on=20the=20server=20(#39)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :heavy_plus_sign: Add gleam_erlang gleam_otp and gleam_json dependencies. * :sparkles: Add json encoders for elememnts and attributes. * :sparkles: Add the ability to perform an effect with a custom dispatch function. * :construction: Experiment with a server-side component runtime. * :construction: Expose special server click events. * :construction: Experiment with a server-side component runtime. * :construction: Experiment with a server-side component runtime. * :construction: Experiment with a server-side component runtime. * :construction: Create a basic server component client bundle. * :construction: Create a basic server component demo. * :bug: Fixed a bug where the runtime stopped performing patches. * :refactor: Roll back introduction of shadow dom. * :recycle: Refactor to Custom Element-based approach to encapsulating server components. * :truck: Move some things around. * :sparkles: Add a minified version of the server component runtime. * :wrench: Add lustre/server/* to internal modules. * :recycle: on_attribute_change and on_client_event handlers are now functions not dicts. * :recycle: Refactor server component event handling to no longer need explicit tags. * :fire: Remove unnecessary attempt to stringify events. * :memo: Start documeint lustre/server functions. * :construction: Experiment with a js implementation of the server component backend runtime. * :recycle: Experiment with an API that makes heavier use of conditional complilation. * :recycle: Big refactor to unify server components, client components, and client apps. * :bug: Fixed some bugs with client runtimes. * :recycle: Update examples to new lustre api/ * :truck: Move server demo into examples/ folder/ * :wrench: Add lustre/runtime to internal modules. * :construction: Experiment with a diffing implementation. * :wrench: Hide internal modules from docs. * :heavy_plus_sign: Update deps to latest versions. * :recycle: Move diffing and vdom code into separate internal modules. * :sparkles: Bring server components to feature parity with client components. * :recycle: Update server component demo. * :bug: Fix bug where attribute changes weren't properly broadcast. * :fire: Remove unused 'Patch' type. * :recycle: Stub out empty js implementations so we can build for js. * :memo: Docs for the docs gods. * :recycle: Rename lustre.server_component to lustre.component. --- src/client-component.ffi.mjs | 74 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 src/client-component.ffi.mjs (limited to 'src/client-component.ffi.mjs') diff --git a/src/client-component.ffi.mjs b/src/client-component.ffi.mjs new file mode 100644 index 0000000..0970e7f --- /dev/null +++ b/src/client-component.ffi.mjs @@ -0,0 +1,74 @@ +import { Ok, Error, isEqual } from "./gleam.mjs"; +import { Dispatch, Shutdown } from "./lustre/runtime.mjs"; +import { + ComponentAlreadyRegistered, + BadComponentName, + NotABrowser, +} from "./lustre.mjs"; +import { LustreClientApplication, is_browser } from "./client-runtime.ffi.mjs"; + +export function register({ init, update, view, on_attribute_change }, name) { + if (!is_browser()) return new Error(new NotABrowser()); + if (!name.includes("-")) return new Error(new BadComponentName(name)); + if (window.customElements.get(name)) { + return new Error(new ComponentAlreadyRegistered(name)); + } + + window.customElements.define( + name, + class LustreClientComponent extends HTMLElement { + #root = document.createElement("div"); + #application = null; + + static get observedAttributes() { + return on_attribute_change.entries().map(([name, _]) => name); + } + + constructor() { + super(); + on_attribute_change.forEach((decoder, name) => { + Object.defineProperty(this, name, { + get() { + return this[`_${name}`] || this.getAttribute(name); + }, + + set(value) { + const prev = this[name]; + const decoded = decoder(value); + + if (decoded.isOk() && !isEqual(prev, value)) { + this.#application + ? this.#application.send(new Dispatch(decoded[0])) + : window.requestAnimationFrame(() => + this.#application.send(new Dispatch(decoded[0])) + ); + } + + if (typeof value === "string") { + this.setAttribute(name, value); + } else { + this[`_${name}`] = value; + } + }, + }); + }); + } + + connectedCallback() { + this.#application = new LustreClientApplication( + init(), + update, + view, + this.#root + ); + this.appendChild(this.#root); + } + + disconnectedCallback() { + this.#application.send(new Shutdown()); + } + } + ); + + return new Ok(null); +} -- cgit v1.2.3