diff --git a/framework/src/index.ts b/framework/src/index.ts
index 0eb37f9..49b1fa1 100644
--- a/framework/src/index.ts
+++ b/framework/src/index.ts
@@ -1 +1,2 @@
export * from "./lib/decorators/on";
+export { default as spread } from "./lib/directives/spread";
diff --git a/framework/src/lib/directives/spread.ts b/framework/src/lib/directives/spread.ts
new file mode 100644
index 0000000..4802a68
--- /dev/null
+++ b/framework/src/lib/directives/spread.ts
@@ -0,0 +1,44 @@
+import { ElementPart, nothing } from "lit";
+import { directive, Directive } from "lit/directive.js";
+
+interface Stringable {
+ toString(): string;
+}
+
+/**
+ * A simple implementation of React's spread operator (`...`) for Lit. Assigns
+ * each `{ [key]: value }` entry in the provided object as an HTML attribute to
+ * the element it's invoked on.
+ *
+ * React:
+ * @example
+ * ```jsx
+ * export default (props) =>
+ * ```
+ *
+ * Lit:
+ * @example
+ * ```typescript
+ * export class FooElement extends LitElement {
+ * props: Record = {};
+ * override render = () => html``
+ * }
+ * ```
+ *
+ * @note
+ * The object entries are assigned as HTML _attributes_, not _properties_, so
+ * each enumerable value in the provided object must have a meaningnful
+ * `toString()` implementation.
+ */
+export default directive(class Spread extends Directive {
+ override render(_: Record) {
+ return nothing;
+ }
+
+ override update(part: ElementPart, [attrs]: [Record]) {
+ for (let [key, value] of Object.entries(attrs)) {
+ part.element.setAttribute(key, value.toString());
+ }
+ return part;
+ }
+});
diff --git a/studio-frontend/src/app/icon.element.ts b/studio-frontend/src/app/icon.element.ts
index 5ed1409..5099b4c 100644
--- a/studio-frontend/src/app/icon.element.ts
+++ b/studio-frontend/src/app/icon.element.ts
@@ -1,7 +1,7 @@
+import { spread } from "@storyteller/framework";
import { match } from "@storyteller/utility";
-import { ElementPart, LitElement, TemplateResult, html, nothing, unsafeCSS } from "lit";
+import { LitElement, TemplateResult, html, nothing, unsafeCSS } from "lit";
import { customElement, property } from "lit/decorators.js";
-import { Directive, directive } from "lit/directive.js";
import { unsafeSVG } from "lit/directives/unsafe-svg.js";
import fasCircleInfo from "@fortawesome/fontawesome-pro/svgs/solid/circle-info.svg?raw";
@@ -59,17 +59,3 @@ function renderSvg(iconId: string) {
`;
}
-
-// TODO: Definitely move this
-const spread = directive(class extends Directive {
- override render(_: Record) {
- return nothing;
- }
-
- override update(part: ElementPart, [attrs]: [Record]) {
- for (let [key, value] of Object.entries(attrs)) {
- part.element.setAttribute(key, value);
- }
- return part;
- }
-});