UNPKG

@zag-js/avatar

Version:

Core logic for the avatar widget implemented as a state machine

166 lines (160 loc) 4.43 kB
'use strict'; var anatomy$1 = require('@zag-js/anatomy'); var domQuery = require('@zag-js/dom-query'); var core = require('@zag-js/core'); var types = require('@zag-js/types'); var utils = require('@zag-js/utils'); // src/avatar.anatomy.ts var anatomy = anatomy$1.createAnatomy("avatar").parts("root", "image", "fallback"); var parts = anatomy.build(); // src/avatar.dom.ts var getRootId = (ctx) => ctx.ids?.root ?? `avatar:${ctx.id}`; var getImageId = (ctx) => ctx.ids?.image ?? `avatar:${ctx.id}:image`; var getFallbackId = (ctx) => ctx.ids?.fallback ?? `avatar:${ctx.id}:fallback`; var getRootEl = (ctx) => ctx.getById(getRootId(ctx)); var getImageEl = (ctx) => ctx.getById(getImageId(ctx)); // src/avatar.connect.ts function connect(service, normalize) { const { state, send, prop, scope } = service; const loaded = state.matches("loaded"); return { loaded, setSrc(src) { const img = getImageEl(scope); img?.setAttribute("src", src); }, setLoaded() { send({ type: "img.loaded", src: "api" }); }, setError() { send({ type: "img.error", src: "api" }); }, getRootProps() { return normalize.element({ ...parts.root.attrs, dir: prop("dir"), id: getRootId(scope) }); }, getImageProps() { return normalize.img({ ...parts.image.attrs, hidden: !loaded, dir: prop("dir"), id: getImageId(scope), "data-state": loaded ? "visible" : "hidden", onLoad() { send({ type: "img.loaded", src: "element" }); }, onError() { send({ type: "img.error", src: "element" }); } }); }, getFallbackProps() { return normalize.element({ ...parts.fallback.attrs, dir: prop("dir"), id: getFallbackId(scope), hidden: loaded, "data-state": loaded ? "hidden" : "visible" }); } }; } var machine = core.createMachine({ initialState() { return "loading"; }, effects: ["trackImageRemoval", "trackSrcChange"], on: { "src.change": { target: "loading" }, "img.unmount": { target: "error" } }, states: { loading: { entry: ["checkImageStatus"], on: { "img.loaded": { target: "loaded", actions: ["invokeOnLoad"] }, "img.error": { target: "error", actions: ["invokeOnError"] } } }, error: { on: { "img.loaded": { target: "loaded", actions: ["invokeOnLoad"] } } }, loaded: { on: { "img.error": { target: "error", actions: ["invokeOnError"] } } } }, implementations: { actions: { invokeOnLoad({ prop }) { prop("onStatusChange")?.({ status: "loaded" }); }, invokeOnError({ prop }) { prop("onStatusChange")?.({ status: "error" }); }, checkImageStatus({ send, scope }) { const imageEl = getImageEl(scope); if (!imageEl?.complete) return; const type = hasLoaded(imageEl) ? "img.loaded" : "img.error"; send({ type, src: "ssr" }); } }, effects: { trackImageRemoval({ send, scope }) { const rootEl = getRootEl(scope); return domQuery.observeChildren(rootEl, { callback(records) { const removedNodes = Array.from(records[0].removedNodes); const removed = removedNodes.find( (node) => node.nodeType === Node.ELEMENT_NODE && node.matches("[data-scope=avatar][data-part=image]") ); if (removed) { send({ type: "img.unmount" }); } } }); }, trackSrcChange({ send, scope }) { const imageEl = getImageEl(scope); return domQuery.observeAttributes(imageEl, { attributes: ["src", "srcset"], callback() { send({ type: "src.change" }); } }); } } } }); function hasLoaded(image) { return image.complete && image.naturalWidth !== 0 && image.naturalHeight !== 0; } var props = types.createProps()(["dir", "id", "ids", "onStatusChange", "getRootNode"]); var splitProps = utils.createSplitProps(props); exports.anatomy = anatomy; exports.connect = connect; exports.machine = machine; exports.props = props; exports.splitProps = splitProps;