diff --git a/demo/arrow.html b/demo/arrow.html deleted file mode 100644 index f562b96..0000000 --- a/demo/arrow.html +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - Arrow - - - - - - - - - - diff --git a/demo/event-propagator.html b/demo/event-propagator.html new file mode 100644 index 0000000..1b3e1e1 --- /dev/null +++ b/demo/event-propagator.html @@ -0,0 +1,47 @@ + + + + + + Event Propagator + + + + + Hello World + + + + + diff --git a/src/arrows/event-propagator.ts b/src/arrows/event-propagator.ts new file mode 100644 index 0000000..ae0e25e --- /dev/null +++ b/src/arrows/event-propagator.ts @@ -0,0 +1,65 @@ +import { SpatialConnection } from './spatial-connection'; + +export class EventPropagator extends SpatialConnection { + static tagName = 'event-propagator'; + + #triggers = (this.getAttribute('triggers') || '').split(','); + get triggers() { + return this.#triggers; + } + set triggers(triggers) { + this.#triggers = triggers; + } + + #expression = ''; + #function = new Function(); + get expression() { + return this.#expression; + } + set expression(expression) { + this.#expression = expression; + this.#function = new Function('$source', '$target', '$event', expression); + } + + constructor() { + super(); + + this.expression = this.getAttribute('expression') || ''; + } + + observeSource() { + super.observeSource(); + + for (const trigger of this.#triggers) { + // TODO: add special triggers for intersection, rAF, etc. + this.sourceElement?.addEventListener(trigger, this.evaluateExpression); + } + + this.evaluateExpression(); + } + + unobserveSource() { + super.unobserveSource(); + + for (const trigger of this.#triggers) { + // TODO: add special triggers for intersection, rAF, etc. + this.sourceElement?.removeEventListener(trigger, this.evaluateExpression); + } + } + + observeTarget() { + super.observeTarget(); + this.evaluateExpression(); + } + + unobserveTarget() { + super.unobserveTarget(); + } + + // Do we need the event at all? + evaluateExpression = (event?: Event) => { + if (this.sourceElement === null || this.targetElement === null) return; + + this.#function(this.sourceElement, this.targetElement, event); + }; +} diff --git a/src/arrows/scoped-propagator.ts b/src/arrows/scoped-propagator.ts deleted file mode 100644 index 9b54e4b..0000000 --- a/src/arrows/scoped-propagator.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { getBoxToBoxArrow } from 'perfect-arrows'; -import { AbstractArrow } from './abstract-arrow'; -import { pointsOnBezierCurves } from './points-on-path'; -import getStroke, { StrokeOptions } from 'perfect-freehand'; - -export type Arrow = [ - /** The x position of the (padded) starting point. */ - sx: number, - /** The y position of the (padded) starting point. */ - sy: number, - /** The x position of the control point. */ - cx: number, - /** The y position of the control point. */ - cy: number, - /** The x position of the (padded) ending point. */ - ex: number, - /** The y position of the (padded) ending point. */ - ey: number, - /** The angle (in radians) for an ending arrowhead. */ - ae: number, - /** The angle (in radians) for a starting arrowhead. */ - as: number, - /** The angle (in radians) for a center arrowhead. */ - ac: number -]; - -export class ScopedPropagator extends AbstractArrow { - static tagName = 'scoped-propagator'; - - #options: StrokeOptions = { - size: 10, - thinning: 0.5, - smoothing: 0.5, - streamline: 0.5, - simulatePressure: true, - // TODO: figure out how to expose these as attributes - easing: (t) => t, - start: { - taper: 50, - easing: (t) => t, - cap: true, - }, - end: { - taper: 0, - easing: (t) => t, - cap: true, - }, - }; - - render(sourceRect: DOMRectReadOnly, targetRect: DOMRectReadOnly) { - const [sx, sy, cx, cy, ex, ey, ae] = getBoxToBoxArrow( - sourceRect.x, - sourceRect.y, - sourceRect.width, - sourceRect.height, - targetRect.x, - targetRect.y, - targetRect.width, - targetRect.height - ) as Arrow; - - const points = pointsOnBezierCurves([ - [sx, sy], - [cx, cy], - [ex, ey], - [ex, ey], - ]); - - const stroke = getStroke(points, this.#options); - const path = getSvgPathFromStroke(stroke); - this.style.clipPath = `path('${path}')`; - this.style.backgroundColor = 'black'; - } -} - -function getSvgPathFromStroke(stroke: number[][]): string { - if (stroke.length === 0) return ''; - - for (const point of stroke) { - point[0] = Math.round(point[0] * 100) / 100; - point[1] = Math.round(point[1] * 100) / 100; - } - - const d = stroke.reduce( - (acc, [x0, y0], i, arr) => { - const [x1, y1] = arr[(i + 1) % arr.length]; - acc.push(x0, y0, (x0 + x1) / 2, (y0 + y1) / 2); - return acc; - }, - ['M', ...stroke[0], 'Q'] - ); - - d.push('Z'); - return d.join(' '); -}