UNPKG

@zag-js/signature-pad

Version:

Core logic for the signature-pad widget implemented as a state machine

144 lines (143 loc) 4.4 kB
// src/signature-pad.connect.ts import { dataAttr, getEventTarget, getRelativePoint, isLeftClick, isModifierKey } from "@zag-js/dom-query"; import { parts } from "./signature-pad.anatomy.mjs"; import * as dom from "./signature-pad.dom.mjs"; function connect(service, normalize) { const { state, send, prop, computed, context, scope } = service; const drawing = state.matches("drawing"); const empty = computed("isEmpty"); const interactive = computed("isInteractive"); const disabled = !!prop("disabled"); const required = !!prop("required"); const translations = prop("translations"); return { empty, drawing, currentPath: context.get("currentPath"), paths: context.get("paths"), clear() { send({ type: "CLEAR" }); }, getDataUrl(type, quality) { if (computed("isEmpty")) return Promise.resolve(""); return dom.getDataUrl(scope, { type, quality }); }, getLabelProps() { return normalize.label({ ...parts.label.attrs, dir: prop("dir"), id: dom.getLabelId(scope), "data-disabled": dataAttr(disabled), "data-required": dataAttr(required), htmlFor: dom.getHiddenInputId(scope), onClick(event) { if (!interactive) return; if (event.defaultPrevented) return; const controlEl = dom.getControlEl(scope); controlEl?.focus({ preventScroll: true }); } }); }, getRootProps() { return normalize.element({ ...parts.root.attrs, dir: prop("dir"), "data-disabled": dataAttr(disabled), id: dom.getRootId(scope) }); }, getControlProps() { return normalize.element({ ...parts.control.attrs, dir: prop("dir"), tabIndex: disabled ? void 0 : 0, id: dom.getControlId(scope), role: "application", "aria-roledescription": "signature pad", "aria-label": translations.control, "aria-disabled": disabled, "data-disabled": dataAttr(disabled), onPointerDown(event) { if (!isLeftClick(event)) return; if (isModifierKey(event)) return; if (!interactive) return; const target = getEventTarget(event); if (target?.closest("[data-part=clear-trigger]")) return; event.currentTarget.setPointerCapture(event.pointerId); const point = { x: event.clientX, y: event.clientY }; const controlEl = dom.getControlEl(scope); if (!controlEl) return; const { offset } = getRelativePoint(point, controlEl); send({ type: "POINTER_DOWN", point: offset, pressure: event.pressure }); }, onPointerUp(event) { if (!interactive) return; if (event.currentTarget.hasPointerCapture(event.pointerId)) { event.currentTarget.releasePointerCapture(event.pointerId); } }, style: { position: "relative", touchAction: "none", userSelect: "none", WebkitUserSelect: "none" } }); }, getSegmentProps() { return normalize.svg({ ...parts.segment.attrs, style: { position: "absolute", top: 0, left: 0, width: "100%", height: "100%", pointerEvents: "none", fill: prop("drawing").fill } }); }, getSegmentPathProps(props) { return normalize.path({ ...parts.segmentPath.attrs, d: props.path }); }, getGuideProps() { return normalize.element({ ...parts.guide.attrs, dir: prop("dir"), "data-disabled": dataAttr(disabled) }); }, getClearTriggerProps() { return normalize.button({ ...parts.clearTrigger.attrs, dir: prop("dir"), type: "button", "aria-label": translations.clearTrigger, hidden: !context.get("paths").length || drawing, disabled, onClick() { send({ type: "CLEAR" }); } }); }, getHiddenInputProps(props) { return normalize.input({ id: dom.getHiddenInputId(scope), type: "text", hidden: true, disabled, required: prop("required"), readOnly: true, name: prop("name"), value: props.value }); } }; } export { connect };