diff --git a/app/assets/javascripts/discourse/app/widgets/render-glimmer.js b/app/assets/javascripts/discourse/app/widgets/render-glimmer.js index a1176215c94..23caaa6f73c 100644 --- a/app/assets/javascripts/discourse/app/widgets/render-glimmer.js +++ b/app/assets/javascripts/discourse/app/widgets/render-glimmer.js @@ -2,6 +2,7 @@ import templateOnly from "@ember/component/template-only"; import { setComponentTemplate } from "@ember/component"; import { tracked } from "@glimmer/tracking"; import { assert } from "@ember/debug"; +import { createWidgetFrom } from "discourse/widgets/widget"; /* @@ -89,7 +90,9 @@ export default class RenderGlimmer { template.name === "factory" ); this.renderInto = renderInto; - this.widget = widget; + if (widget) { + this.widget = widget; + } this.template = template; this.data = data; } @@ -108,7 +111,9 @@ export default class RenderGlimmer { destroy() { if (this._componentInfo) { - this.widget._findView().unmountChildComponent(this._componentInfo); + this.parentMountWidgetComponent.unmountChildComponent( + this._componentInfo + ); } } @@ -132,7 +137,7 @@ export default class RenderGlimmer { } connectComponent() { - const { element, template, widget } = this; + const { element, template } = this; const component = templateOnly(); component.name = "Widgets/RenderGlimmer"; @@ -143,9 +148,39 @@ export default class RenderGlimmer { component, @tracked data: this.data, }; - const parentMountWidgetComponent = widget._findView(); - parentMountWidgetComponent.mountChildComponent(this._componentInfo); + + this.parentMountWidgetComponent.mountChildComponent(this._componentInfo); + } + + get parentMountWidgetComponent() { + return this.widget?._findView() || this._emberView; } } RenderGlimmer.prototype.type = "Widget"; + +/** + * Define a widget shim which renders a Glimmer template. Designed for incrementally migrating + * a widget-based UI to Glimmer. Widget attrs will be made available to your template at `@data`. + * For more details, see documentation for the RenderGlimmer class. + * @param name - the widget's name (which can then be used in `.attach` elsewhere) + * @param tagName - a string describing a new wrapper element (e.g. `div.my-class`) + * @param template - a glimmer template compiled via ember-cli-htmlbars + */ +export function registerWidgetShim(name, tagName, template) { + const RenderGlimmerShim = class MyClass extends RenderGlimmer { + constructor(attrs) { + super(null, tagName, template, attrs); + return this; + } + + get widget() { + return this.parentWidget; + } + + didRenderWidget() {} + willRerenderWidget() {} + }; + + createWidgetFrom(RenderGlimmerShim, name, {}); +} diff --git a/app/assets/javascripts/discourse/app/widgets/widget.js b/app/assets/javascripts/discourse/app/widgets/widget.js index 0985f0283fd..896edfefd3d 100644 --- a/app/assets/javascripts/discourse/app/widgets/widget.js +++ b/app/assets/javascripts/discourse/app/widgets/widget.js @@ -30,6 +30,10 @@ export function queryRegistry(name) { return _registry[name]; } +export function deleteFromRegistry(name) { + return delete _registry[name]; +} + const _decorators = {}; export function decorateWidget(widgetName, cb) { diff --git a/app/assets/javascripts/discourse/tests/integration/components/widgets/render-glimmer-test.js b/app/assets/javascripts/discourse/tests/integration/components/widgets/render-glimmer-test.js index 53d8b8d62d8..497725a05b3 100644 --- a/app/assets/javascripts/discourse/tests/integration/components/widgets/render-glimmer-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/widgets/render-glimmer-test.js @@ -4,9 +4,11 @@ import { setupRenderingTest } from "discourse/tests/helpers/component-test"; import { click, fillIn, render } from "@ember/test-helpers"; import { hbs } from "ember-cli-htmlbars"; import widgetHbs from "discourse/widgets/hbs-compiler"; -import Widget from "discourse/widgets/widget"; +import Widget, { deleteFromRegistry } from "discourse/widgets/widget"; import ClassicComponent from "@ember/component"; -import RenderGlimmer from "discourse/widgets/render-glimmer"; +import RenderGlimmer, { + registerWidgetShim, +} from "discourse/widgets/render-glimmer"; import { bind } from "discourse-common/utils/decorators"; class DemoWidget extends Widget { @@ -126,12 +128,18 @@ module("Integration | Component | Widget | render-glimmer", function (hooks) { this.registry.register("widget:demo-widget", DemoWidget); this.registry.register("widget:toggle-demo-widget", ToggleDemoWidget); this.registry.register("component:demo-component", DemoComponent); + registerWidgetShim( + "render-glimmer-test-shim", + "div.my-wrapper", + hbs`{{@data.attr1}}` + ); }); hooks.afterEach(function () { this.registry.unregister("widget:demo-widget"); this.registry.unregister("widget:toggle-demo-widget"); this.registry.unregister("component:demo-component"); + deleteFromRegistry("render-glimmer-test-shim"); }); test("argument handling", async function (assert) { @@ -310,4 +318,13 @@ module("Integration | Component | Widget | render-glimmer", function (hooks) { await click(".toggleButton"); assert.strictEqual(query("div.glimmer-wrapper").innerText, "One"); }); + + test("registerWidgetShim can register a fake widget", async function (assert) { + await render( + hbs`` + ); + + assert.dom("div.my-wrapper span.shim-content").exists(); + assert.dom("div.my-wrapper span.shim-content").hasText("val1"); + }); });