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