From 4c320544e1e43b6a8ef0b93a356e242b83d22529 Mon Sep 17 00:00:00 2001 From: Stanislau Kviatkouski <7zete7@gmail.com> Date: Fri, 13 Sep 2024 01:20:51 +0300 Subject: [PATCH] Make RenderController are reactive --- src/Vue/assets/dist/render_controller.d.ts | 6 +- src/Vue/assets/dist/render_controller.js | 45 ++++++++++++- src/Vue/assets/src/render_controller.ts | 76 ++++++++++++++++++++-- 3 files changed, 117 insertions(+), 10 deletions(-) diff --git a/src/Vue/assets/dist/render_controller.d.ts b/src/Vue/assets/dist/render_controller.d.ts index 205d99e8377..fc93c5fe3b2 100644 --- a/src/Vue/assets/dist/render_controller.d.ts +++ b/src/Vue/assets/dist/render_controller.d.ts @@ -6,12 +6,16 @@ export default class extends Controller | null | undefined; + readonly hasPropsValue: boolean; + propsValue: Record | null | undefined; static values: { component: StringConstructor; props: ObjectConstructor; }; + propsValueChanged(newProps: typeof this.propsValue, oldProps: typeof this.propsValue): void; + initialize(): void; connect(): void; disconnect(): void; private dispatchEvent; + private wrapComponent; } diff --git a/src/Vue/assets/dist/render_controller.js b/src/Vue/assets/dist/render_controller.js index 316c8be3a5d..ca670ea5a14 100644 --- a/src/Vue/assets/dist/render_controller.js +++ b/src/Vue/assets/dist/render_controller.js @@ -1,12 +1,35 @@ import { Controller } from '@hotwired/stimulus'; -import { createApp } from 'vue'; +import { shallowReactive, watch, toRaw, createApp, defineComponent, h } from 'vue'; class default_1 extends Controller { + propsValueChanged(newProps, oldProps) { + if (oldProps) { + let removedPropNames = Object.keys(oldProps); + if (newProps) { + removedPropNames = removedPropNames.filter((propName) => !Object.prototype.hasOwnProperty.call(newProps, propName)); + } + removedPropNames.forEach((propName) => { + delete this.props[propName]; + }); + } + if (newProps) { + Object.entries(newProps).forEach(([propName, propValue]) => { + this.props[propName] = propValue; + }); + } + } + initialize() { + const props = this.hasPropsValue && this.propsValue ? this.propsValue : {}; + this.props = shallowReactive({ ...props }); + watch(this.props, (props) => { + this.propsValue = toRaw(props); + }, { flush: 'post' }); + } connect() { - this.props = this.propsValue ?? null; this.dispatchEvent('connect', { componentName: this.componentValue, props: this.props }); const component = window.resolveVueComponent(this.componentValue); - this.app = createApp(component, this.props); + const wrappedComponent = this.wrapComponent(component); + this.app = createApp(wrappedComponent); if (this.element.__vue_app__ !== undefined) { this.element.__vue_app__.unmount(); } @@ -33,6 +56,22 @@ class default_1 extends Controller { dispatchEvent(name, payload) { this.dispatch(name, { detail: payload, prefix: 'vue' }); } + wrapComponent(component) { + return defineComponent({ + setup: () => { + const props = this.props; + return () => h(component, { + ...props, + ...Object.fromEntries(Object.keys(props).map((propName) => [ + `onUpdate:${propName}`, + (value) => { + props[propName] = value; + }, + ])), + }); + }, + }); + } } default_1.values = { component: String, diff --git a/src/Vue/assets/src/render_controller.ts b/src/Vue/assets/src/render_controller.ts index fac9375d156..06aed2b9189 100644 --- a/src/Vue/assets/src/render_controller.ts +++ b/src/Vue/assets/src/render_controller.ts @@ -8,27 +8,70 @@ */ import { Controller } from '@hotwired/stimulus'; -import { type App, createApp } from 'vue'; +import { + type App, + type Component, + createApp, + defineComponent, + h, + type ShallowReactive, + shallowReactive, + toRaw, + watch, +} from 'vue'; export default class extends Controller }> { - private props: Record | null; + private props: ShallowReactive>; private app: App; declare readonly componentValue: string; - declare readonly propsValue: Record | null | undefined; + declare readonly hasPropsValue: boolean; + declare propsValue: Record | null | undefined; static values = { component: String, props: Object, }; - connect() { - this.props = this.propsValue ?? null; + propsValueChanged(newProps: typeof this.propsValue, oldProps: typeof this.propsValue) { + if (oldProps) { + let removedPropNames = Object.keys(oldProps); + + if (newProps) { + removedPropNames = removedPropNames.filter( + (propName) => !Object.prototype.hasOwnProperty.call(newProps, propName) + ); + } + removedPropNames.forEach((propName) => { + delete this.props[propName]; + }); + } + if (newProps) { + Object.entries(newProps).forEach(([propName, propValue]) => { + this.props[propName] = propValue; + }); + } + } + + initialize() { + const props = this.hasPropsValue && this.propsValue ? this.propsValue : {}; + this.props = shallowReactive({ ...props }); + watch( + this.props, + (props) => { + this.propsValue = toRaw(props); + }, + { flush: 'post' } + ); + } + + connect() { this.dispatchEvent('connect', { componentName: this.componentValue, props: this.props }); const component = window.resolveVueComponent(this.componentValue); + const wrappedComponent = this.wrapComponent(component); - this.app = createApp(component, this.props); + this.app = createApp(wrappedComponent); if (this.element.__vue_app__ !== undefined) { this.element.__vue_app__.unmount(); @@ -62,4 +105,25 @@ export default class extends Controller } private dispatchEvent(name: string, payload: any) { this.dispatch(name, { detail: payload, prefix: 'vue' }); } + + private wrapComponent(component: Component): Component { + return defineComponent({ + setup: () => { + const props = this.props; + + return () => + h(component, { + ...props, + ...Object.fromEntries( + Object.keys(props).map((propName) => [ + `onUpdate:${propName}`, + (value: unknown) => { + props[propName] = value; + }, + ]) + ), + }); + }, + }); + } }