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");
+ });
});