shape transform event
This commit is contained in:
parent
a5b5d390dd
commit
14f78e3871
|
|
@ -46,8 +46,8 @@
|
|||
<folk-event-propagator
|
||||
source="#box3"
|
||||
target="#box4"
|
||||
triggers="move"
|
||||
expression="y: from.x,
|
||||
triggers="transform"
|
||||
expression="y: from.x,
|
||||
rotation: from.x"
|
||||
></folk-event-propagator>
|
||||
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@
|
|||
</ul>
|
||||
|
||||
<h2>Demos</h2>
|
||||
<p id="disclaimer">(Make sure to checkout the dev tools, everything you see is authored just in HTML!)</p>
|
||||
<p id="disclaimer">(Make sure to checkout the dev tools, all of the you see are just authored in HTML!)</p>
|
||||
|
||||
<ul id="links">
|
||||
{{ LINKS }}
|
||||
|
|
|
|||
|
|
@ -126,8 +126,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
document.addEventListener('move', handleProximity);
|
||||
document.addEventListener('resize', handleProximity);
|
||||
document.addEventListener('transform', handleProximity);
|
||||
|
||||
document.addEventListener('playing', (e) => {
|
||||
proximitySet.forEach((el) => el.firstElementChild.play?.());
|
||||
|
|
|
|||
|
|
@ -44,8 +44,7 @@
|
|||
});
|
||||
}
|
||||
|
||||
document.addEventListener('move', handleCollision);
|
||||
document.addEventListener('resize', handleCollision);
|
||||
document.addEventListener('transform', handleCollision);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -191,8 +191,7 @@ export class AbstractArrow extends HTMLElement {
|
|||
if (this.#sourceElement === null) {
|
||||
throw new Error('source is not a valid element');
|
||||
} else if (this.#sourceElement instanceof FolkShape) {
|
||||
this.#sourceElement.addEventListener('resize', this.#sourceHandler);
|
||||
this.#sourceElement.addEventListener('move', this.#sourceHandler);
|
||||
this.#sourceElement.addEventListener('transform', this.#sourceHandler);
|
||||
this.#sourceRect = this.#sourceElement.getBoundingClientRect();
|
||||
} else if (this.#sourceElement instanceof HTMLIFrameElement && this.#sourceIframeSelector) {
|
||||
window.addEventListener('message', this.#sourcePostMessage);
|
||||
|
|
@ -212,8 +211,7 @@ export class AbstractArrow extends HTMLElement {
|
|||
if (this.#sourceElement === null) return;
|
||||
|
||||
if (this.#sourceElement instanceof FolkShape) {
|
||||
this.#sourceElement.removeEventListener('resize', this.#sourceHandler);
|
||||
this.#sourceElement.removeEventListener('move', this.#sourceHandler);
|
||||
this.#sourceElement.removeEventListener('transform', this.#sourceHandler);
|
||||
} else if (this.#sourceElement instanceof HTMLIFrameElement && this.#sourceIframeSelector) {
|
||||
window.removeEventListener('message', this.#sourcePostMessage);
|
||||
clientRectObserver.unobserve(this.#sourceElement, this.#sourceIframeCallback);
|
||||
|
|
@ -242,8 +240,7 @@ export class AbstractArrow extends HTMLElement {
|
|||
if (!this.#targetElement) {
|
||||
throw new Error('target is not a valid element');
|
||||
} else if (this.#targetElement instanceof FolkShape) {
|
||||
this.#targetElement.addEventListener('resize', this.#targetHandler);
|
||||
this.#targetElement.addEventListener('move', this.#targetHandler);
|
||||
this.#targetElement.addEventListener('transform', this.#targetHandler);
|
||||
} else if (this.#targetElement instanceof HTMLIFrameElement && this.#targetIframeSelector) {
|
||||
window.addEventListener('message', this.#targetPostMessage);
|
||||
clientRectObserver.observe(this.#targetElement, this.#targetIframeCallback);
|
||||
|
|
@ -262,8 +259,7 @@ export class AbstractArrow extends HTMLElement {
|
|||
if (this.#targetElement === null) return;
|
||||
|
||||
if (this.#targetElement instanceof FolkShape) {
|
||||
this.#targetElement.removeEventListener('resize', this.#targetHandler);
|
||||
this.#targetElement.removeEventListener('move', this.#targetHandler);
|
||||
this.#targetElement.removeEventListener('transform', this.#targetHandler);
|
||||
} else if (this.#targetElement instanceof HTMLIFrameElement && this.#targetIframeSelector) {
|
||||
window.removeEventListener('message', this.#targetPostMessage);
|
||||
clientRectObserver.unobserve(this.#targetElement, this.#targetIframeCallback);
|
||||
|
|
|
|||
|
|
@ -81,8 +81,7 @@ if (window.parent !== window) {
|
|||
observedSelectors.set(element, selector);
|
||||
|
||||
if (element instanceof FolkShape) {
|
||||
element.addEventListener('move', onGeometryChange);
|
||||
element.addEventListener('resize', onGeometryChange);
|
||||
element.addEventListener('transform', onGeometryChange);
|
||||
|
||||
window.parent.postMessage({
|
||||
type: 'folk-element-change',
|
||||
|
|
@ -101,8 +100,7 @@ if (window.parent !== window) {
|
|||
if (element === undefined) return;
|
||||
|
||||
if (element instanceof FolkShape) {
|
||||
element.removeEventListener('move', onGeometryChange);
|
||||
element.removeEventListener('resize', onGeometryChange);
|
||||
element.removeEventListener('transform', onGeometryChange);
|
||||
observedElements.delete(selector);
|
||||
observedSelectors.delete(element);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -42,18 +42,14 @@ export class DistanceField extends HTMLElement {
|
|||
|
||||
window.addEventListener('resize', this.handleResize);
|
||||
this.shapes.forEach((geometry) => {
|
||||
geometry.addEventListener('move', this.handleGeometryUpdate);
|
||||
geometry.addEventListener('resize', this.handleGeometryUpdate);
|
||||
geometry.addEventListener('rotate', this.handleGeometryUpdate);
|
||||
geometry.addEventListener('transform', this.handleGeometryUpdate);
|
||||
});
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
window.removeEventListener('resize', this.handleResize);
|
||||
this.shapes.forEach((geometry) => {
|
||||
geometry.removeEventListener('move', this.handleGeometryUpdate);
|
||||
geometry.removeEventListener('resize', this.handleGeometryUpdate);
|
||||
geometry.removeEventListener('rotate', this.handleGeometryUpdate);
|
||||
geometry.removeEventListener('transform', this.handleGeometryUpdate);
|
||||
});
|
||||
this.cleanupWebGLResources();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -141,8 +141,7 @@ export class FolkProximity extends HTMLElement {
|
|||
constructor() {
|
||||
super();
|
||||
|
||||
this.addEventListener('move', this.#handleProximity);
|
||||
this.addEventListener('resize', this.#handleProximity);
|
||||
this.addEventListener('transform', this.#handleProximity);
|
||||
}
|
||||
|
||||
#handleProximity = (e: Event) => {
|
||||
|
|
|
|||
|
|
@ -7,27 +7,54 @@ const resizeObserver = new ResizeObserverManager();
|
|||
|
||||
export type Shape = 'rectangle' | 'circle' | 'triangle';
|
||||
|
||||
export type MoveEventDetail = { movementX: number; movementY: number };
|
||||
export type TransformEventDetail = {
|
||||
rotate: number;
|
||||
};
|
||||
|
||||
export class MoveEvent extends CustomEvent<MoveEventDetail> {
|
||||
constructor(detail: MoveEventDetail) {
|
||||
super('move', { detail, cancelable: true, bubbles: true });
|
||||
// TODO: expose previous and current rects
|
||||
export class TransformEvent extends Event {
|
||||
constructor() {
|
||||
super('transform', { cancelable: true, bubbles: true });
|
||||
}
|
||||
}
|
||||
|
||||
export type ResizeEventDetail = { movementX: number; movementY: number };
|
||||
|
||||
export class ResizeEvent extends CustomEvent<MoveEventDetail> {
|
||||
constructor(detail: MoveEventDetail) {
|
||||
super('resize', { detail, cancelable: true, bubbles: true });
|
||||
#xPrevented = false;
|
||||
get xPrevented() {
|
||||
return this.defaultPrevented || this.#xPrevented;
|
||||
}
|
||||
preventX() {
|
||||
this.#xPrevented = true;
|
||||
}
|
||||
}
|
||||
|
||||
export type RotateEventDetail = { rotate: number };
|
||||
#yPrevented = false;
|
||||
get yPrevented() {
|
||||
return this.defaultPrevented || this.#yPrevented;
|
||||
}
|
||||
preventY() {
|
||||
this.#yPrevented = true;
|
||||
}
|
||||
|
||||
export class RotateEvent extends CustomEvent<RotateEventDetail> {
|
||||
constructor(detail: RotateEventDetail) {
|
||||
super('rotate', { detail, cancelable: true, bubbles: true });
|
||||
#heightPrevented = false;
|
||||
get heightPrevented() {
|
||||
return this.defaultPrevented || this.#heightPrevented;
|
||||
}
|
||||
preventHeight() {
|
||||
this.#heightPrevented = true;
|
||||
}
|
||||
|
||||
#widthPrevented = false;
|
||||
get widthPrevented() {
|
||||
return this.defaultPrevented || this.#widthPrevented;
|
||||
}
|
||||
preventWidth() {
|
||||
this.#widthPrevented = true;
|
||||
}
|
||||
|
||||
#rotatePrevented = false;
|
||||
get rotatePrevented() {
|
||||
return this.defaultPrevented || this.#rotatePrevented;
|
||||
}
|
||||
preventRotate() {
|
||||
this.#rotatePrevented = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -209,9 +236,9 @@ export class FolkShape extends HTMLElement {
|
|||
|
||||
set width(width: Dimension) {
|
||||
if (width === 'auto') {
|
||||
resizeObserver.observe(this, this.#onResize);
|
||||
resizeObserver.observe(this, this.#onAutoResize);
|
||||
} else if (this.#width === 'auto' && this.#height !== 'auto') {
|
||||
resizeObserver.unobserve(this, this.#onResize);
|
||||
resizeObserver.unobserve(this, this.#onAutoResize);
|
||||
}
|
||||
this.#previousWidth = this.#width;
|
||||
this.#width = width;
|
||||
|
|
@ -229,9 +256,9 @@ export class FolkShape extends HTMLElement {
|
|||
|
||||
set height(height: Dimension) {
|
||||
if (height === 'auto') {
|
||||
resizeObserver.observe(this, this.#onResize);
|
||||
resizeObserver.observe(this, this.#onAutoResize);
|
||||
} else if (this.#height === 'auto' && this.#width !== 'auto') {
|
||||
resizeObserver.unobserve(this, this.#onResize);
|
||||
resizeObserver.unobserve(this, this.#onAutoResize);
|
||||
}
|
||||
|
||||
this.#previousHeight = this.#height;
|
||||
|
|
@ -280,8 +307,10 @@ export class FolkShape extends HTMLElement {
|
|||
this.width = Number(this.getAttribute('width')) || 'auto';
|
||||
}
|
||||
|
||||
#isConnected = false;
|
||||
connectedCallback() {
|
||||
this.#update(new Set(['type', 'x', 'y', 'height', 'width', 'rotation']));
|
||||
this.#isConnected = true;
|
||||
}
|
||||
|
||||
getClientRect(): RotatedDOMRect {
|
||||
|
|
@ -453,6 +482,8 @@ export class FolkShape extends HTMLElement {
|
|||
#isUpdating = false;
|
||||
|
||||
async #requestUpdate(property: string) {
|
||||
if (!this.#isConnected) return;
|
||||
|
||||
this.#updatedProperties.add(property);
|
||||
|
||||
if (this.#isUpdating) return;
|
||||
|
|
@ -473,86 +504,60 @@ export class FolkShape extends HTMLElement {
|
|||
// See https://www.smashingmagazine.com/2024/05/modern-guide-making-css-shapes/
|
||||
}
|
||||
|
||||
if (updatedProperties.has('x') || updatedProperties.has('y')) {
|
||||
// Although the change in movement isn't useful inside this component, the outside world might find it helpful to calculate acceleration and other physics
|
||||
const notCancelled = this.dispatchEvent(
|
||||
new MoveEvent({
|
||||
movementX: this.#x - this.#previousX,
|
||||
movementY: this.#y - this.#previousY,
|
||||
})
|
||||
);
|
||||
this.#dispatchTransformEvent(updatedProperties);
|
||||
}
|
||||
|
||||
if (notCancelled) {
|
||||
if (updatedProperties.has('x')) {
|
||||
// In the future, when CSS `attr()` is supported we could define this x/y projection in CSS.
|
||||
this.style.left = `${this.#x}px`;
|
||||
}
|
||||
#dispatchTransformEvent(updatedProperties: Set<string>) {
|
||||
const event = new TransformEvent();
|
||||
|
||||
if (updatedProperties.has('y')) {
|
||||
this.style.top = `${this.#y}px`;
|
||||
}
|
||||
} else {
|
||||
this.dispatchEvent(event);
|
||||
|
||||
if (updatedProperties.has('x')) {
|
||||
if (event.xPrevented) {
|
||||
this.#x = this.#previousX;
|
||||
this.#y = this.#previousY;
|
||||
} else {
|
||||
this.style.left = `${this.#x}px`;
|
||||
}
|
||||
}
|
||||
|
||||
if (updatedProperties.has('width') || updatedProperties.has('height')) {
|
||||
// Although the change in resize isn't useful inside this component, the outside world might find it helpful to calculate acceleration and other physics
|
||||
const notCancelled = this.dispatchEvent(
|
||||
new ResizeEvent({
|
||||
movementX: this.width - (this.#previousWidth === 'auto' ? 0 : this.#previousWidth),
|
||||
movementY: this.height - (this.#previousHeight === 'auto' ? 0 : this.#previousHeight),
|
||||
})
|
||||
);
|
||||
if (notCancelled) {
|
||||
if (updatedProperties.has('width')) {
|
||||
this.style.width = this.#width === 'auto' ? '' : `${this.#width}px`;
|
||||
}
|
||||
|
||||
if (updatedProperties.has('height')) {
|
||||
this.style.height = this.#height === 'auto' ? '' : `${this.#height}px`;
|
||||
}
|
||||
if (updatedProperties.has('y')) {
|
||||
if (event.yPrevented) {
|
||||
this.#y = this.#previousY;
|
||||
} else {
|
||||
// TODO: Revert changes to position too
|
||||
this.style.top = `${this.#y}px`;
|
||||
}
|
||||
}
|
||||
|
||||
if (updatedProperties.has('height')) {
|
||||
if (event.heightPrevented) {
|
||||
this.#height = this.#previousHeight;
|
||||
} else {
|
||||
this.style.height = this.#height === 'auto' ? '' : `${this.#height}px`;
|
||||
}
|
||||
}
|
||||
|
||||
if (updatedProperties.has('width')) {
|
||||
if (event.widthPrevented) {
|
||||
this.#width = this.#previousWidth;
|
||||
} else {
|
||||
this.style.width = this.#width === 'auto' ? '' : `${this.#width}px`;
|
||||
}
|
||||
}
|
||||
|
||||
if (updatedProperties.has('rotation')) {
|
||||
// Although the change in resize isn't useful inside this component, the outside world might find it helpful to calculate acceleration and other physics
|
||||
const notCancelled = this.dispatchEvent(new RotateEvent({ rotate: this.#rotation - this.#previousRotation }));
|
||||
|
||||
if (notCancelled) {
|
||||
if (updatedProperties.has('rotation')) {
|
||||
this.style.rotate = `${this.#rotation}rad`;
|
||||
}
|
||||
} else {
|
||||
if (event.rotatePrevented) {
|
||||
this.#rotation = this.#previousRotation;
|
||||
} else {
|
||||
this.style.rotate = `${this.#rotation}rad`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#onResize = (entry: ResizeObserverEntry) => {
|
||||
#onAutoResize = (entry: ResizeObserverEntry) => {
|
||||
const previousRect = this.#autoContentRect;
|
||||
this.#autoContentRect = entry.contentRect;
|
||||
|
||||
const notCancelled = this.dispatchEvent(
|
||||
new ResizeEvent({
|
||||
movementX: this.width - (this.#previousWidth === 'auto' ? previousRect.width : this.#previousWidth),
|
||||
movementY: this.height - (this.#previousHeight === 'auto' ? previousRect.height : this.#previousHeight),
|
||||
})
|
||||
);
|
||||
|
||||
if (!notCancelled) {
|
||||
if (this.#height === 'auto') {
|
||||
this.height = previousRect?.height || 0;
|
||||
}
|
||||
|
||||
if (this.#width === 'auto') {
|
||||
this.width = previousRect?.width || 0;
|
||||
}
|
||||
}
|
||||
this.#previousHeight = previousRect.height;
|
||||
this.#previousWidth = previousRect.width;
|
||||
this.#dispatchTransformEvent(new Set(['width', 'height']));
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue