diff --git a/src/main/scala/com/raquo/laminar/inserters/ChildInserter.scala b/src/main/scala/com/raquo/laminar/inserters/ChildInserter.scala index 42e67239..4b31079b 100644 --- a/src/main/scala/com/raquo/laminar/inserters/ChildInserter.scala +++ b/src/main/scala/com/raquo/laminar/inserters/ChildInserter.scala @@ -3,6 +3,7 @@ package com.raquo.laminar.inserters import com.raquo.airstream.core.Observable import com.raquo.laminar.modifiers.RenderableNode import com.raquo.laminar.nodes.{ChildNode, ParentNode} +import org.scalajs.dom import scala.scalajs.js @@ -11,11 +12,11 @@ object ChildInserter { def apply[Component] ( childSource: Observable[Component], renderable: RenderableNode[Component], - hooks: js.UndefOr[InserterHooks] + initialHooks: js.UndefOr[InserterHooks] ): DynamicInserter = { new DynamicInserter( preferStrictMode = true, - insertFn = (ctx, owner) => { + insertFn = (ctx, owner, hooks) => { // Reset sentinel node on binding too, don't wait for events if (!ctx.strictMode) { ctx.forceSetStrictMode() @@ -27,7 +28,7 @@ object ChildInserter { maybeLastSeenChild = newChildNode }(owner) }, - hooks = hooks + hooks = initialHooks ) } diff --git a/src/main/scala/com/raquo/laminar/inserters/ChildTextInserter.scala b/src/main/scala/com/raquo/laminar/inserters/ChildTextInserter.scala index b066c62a..08bfc1ae 100644 --- a/src/main/scala/com/raquo/laminar/inserters/ChildTextInserter.scala +++ b/src/main/scala/com/raquo/laminar/inserters/ChildTextInserter.scala @@ -14,7 +14,7 @@ object ChildTextInserter { ): DynamicInserter = { new DynamicInserter( preferStrictMode = false, - insertFn = (ctx, owner) => { + insertFn = (ctx, owner, _) => { var maybeTextNode: js.UndefOr[TextNode] = js.undefined textSource.foreach { newValue => maybeTextNode.fold { diff --git a/src/main/scala/com/raquo/laminar/inserters/ChildrenCommandInserter.scala b/src/main/scala/com/raquo/laminar/inserters/ChildrenCommandInserter.scala index 0cf32358..f76e89e9 100644 --- a/src/main/scala/com/raquo/laminar/inserters/ChildrenCommandInserter.scala +++ b/src/main/scala/com/raquo/laminar/inserters/ChildrenCommandInserter.scala @@ -24,11 +24,11 @@ object ChildrenCommandInserter { def apply[Component] ( commands: EventStream[CollectionCommand[Component]], renderableNode: RenderableNode[Component], - hooks: js.UndefOr[InserterHooks] + initialHooks: js.UndefOr[InserterHooks] ): DynamicInserter = { new DynamicInserter( preferStrictMode = true, - insertFn = (ctx, owner) => { + insertFn = (ctx, owner, hooks) => { commands.foreach { command => val nodeCountDiff = updateList( command, @@ -41,7 +41,7 @@ object ChildrenCommandInserter { ctx.extraNodeCount += nodeCountDiff }(owner) }, - hooks = hooks + hooks = initialHooks ) } diff --git a/src/main/scala/com/raquo/laminar/inserters/ChildrenInserter.scala b/src/main/scala/com/raquo/laminar/inserters/ChildrenInserter.scala index e45ba389..24e5aae7 100644 --- a/src/main/scala/com/raquo/laminar/inserters/ChildrenInserter.scala +++ b/src/main/scala/com/raquo/laminar/inserters/ChildrenInserter.scala @@ -20,11 +20,11 @@ object ChildrenInserter { def apply[Component]( childrenSource: Observable[immutable.Seq[Component]], renderableNode: RenderableNode[Component], - hooks: js.UndefOr[InserterHooks] + initialHooks: js.UndefOr[InserterHooks] ): DynamicInserter = { new DynamicInserter( preferStrictMode = true, - insertFn = (ctx, owner) => { + insertFn = (ctx, owner, hooks) => { // Reset sentinel node on binding too, don't wait for events if (!ctx.strictMode) { ctx.forceSetStrictMode() @@ -51,7 +51,7 @@ object ChildrenInserter { // } }(owner) }, - hooks = hooks + hooks = initialHooks ) } diff --git a/src/main/scala/com/raquo/laminar/inserters/Inserter.scala b/src/main/scala/com/raquo/laminar/inserters/Inserter.scala index 754763e1..250c7570 100644 --- a/src/main/scala/com/raquo/laminar/inserters/Inserter.scala +++ b/src/main/scala/com/raquo/laminar/inserters/Inserter.scala @@ -50,7 +50,7 @@ trait StaticInserter extends Inserter { class DynamicInserter( initialContext: Option[InsertContext] = None, preferStrictMode: Boolean, - insertFn: (InsertContext, Owner) => Subscription, + insertFn: (InsertContext, Owner, js.UndefOr[InserterHooks]) => Subscription, hooks: js.UndefOr[InserterHooks] = js.undefined ) extends Inserter with Hookable[DynamicInserter] { @@ -69,7 +69,7 @@ class DynamicInserter( ) ReactiveElement.bindSubscriptionUnsafe(element) { mountContext => - insertFn(insertContext, mountContext.owner) + insertFn(insertContext, mountContext.owner, hooks) } } @@ -78,7 +78,8 @@ class DynamicInserter( } override def withHooks(addHooks: InserterHooks): DynamicInserter = { - new DynamicInserter(initialContext, preferStrictMode, insertFn, addHooks.appendTo(hooks)) + val newHooks = addHooks.appendTo(hooks) + new DynamicInserter(initialContext, preferStrictMode, insertFn, newHooks) } /** Call this to get a copy of Inserter with a context locked to a certain element. diff --git a/src/main/scala/com/raquo/laminar/inserters/InserterHooks.scala b/src/main/scala/com/raquo/laminar/inserters/InserterHooks.scala index 3690438c..b528980e 100644 --- a/src/main/scala/com/raquo/laminar/inserters/InserterHooks.scala +++ b/src/main/scala/com/raquo/laminar/inserters/InserterHooks.scala @@ -10,6 +10,12 @@ import scala.scalajs.js * We currently use it only for slotting elements into web components, * but will likely use it more broadly later. * + * NOTE: Currently hooks do not run on _some_ text nodes. They are also + * not run on some sentinel comment nodes, because only element nodes are + * slottable. So, this is fine for slotting purposes, but that's the + * kind of thing that will need a more principled contract if this API + * is to be used more widely. + * * WARNING: Your hooks should not throw! * Any thrown errors will be sent to Airstream unhandled errors. * diff --git a/src/main/scala/com/raquo/laminar/nodes/Slot.scala b/src/main/scala/com/raquo/laminar/nodes/Slot.scala index 9acdf4a3..589ff130 100644 --- a/src/main/scala/com/raquo/laminar/nodes/Slot.scala +++ b/src/main/scala/com/raquo/laminar/nodes/Slot.scala @@ -1,5 +1,6 @@ package com.raquo.laminar.nodes +import com.raquo.airstream.core.AirstreamError import com.raquo.laminar.inserters.{Hookable, Inserter, InserterHooks} import org.scalajs.dom @@ -18,11 +19,11 @@ class Slot(val name: String) { case el: dom.Element => el.setAttribute("slot", name) case text: dom.Text => - dom.console.error( - s"Error: You are trying to insert a raw text node `${text.textContent}` into the `${name}` slot of <${parent.ref.tagName}>.\n" + - " - Cause: This is not possible. Only elements can be slotted.\n" + - " - Suggestion: Wrap your text node into span()" - ) + AirstreamError.sendUnhandledError(new Exception( + s"Error: You tried to insert a raw text node `${text.textContent}` into the `${name}` slot of <${parent.ref.tagName.toLowerCase}>.\n" + + " - Cause: This is not possible: named slots only accept elements. Your node was inserted into the default slot instead.\n" + + " - Suggestion: Wrap your text node into `span()`" + )) case _ => () // Do nothing with comment nodes } diff --git a/src/main/scala/com/raquo/laminar/receivers/ChildReceiver.scala b/src/main/scala/com/raquo/laminar/receivers/ChildReceiver.scala index 5168388a..88e0c659 100644 --- a/src/main/scala/com/raquo/laminar/receivers/ChildReceiver.scala +++ b/src/main/scala/com/raquo/laminar/receivers/ChildReceiver.scala @@ -19,7 +19,7 @@ object ChildReceiver { } def <--(childSource: Source[ChildNode.Base]): DynamicInserter = { - ChildInserter(childSource.toObservable, RenderableNode.nodeRenderable, hooks = js.undefined) + ChildInserter(childSource.toObservable, RenderableNode.nodeRenderable, initialHooks = js.undefined) } implicit class RichChildReceiver(val self: ChildReceiver.type) extends AnyVal { @@ -38,7 +38,7 @@ object ChildReceiver { )( implicit renderable: RenderableNode[Component] ): DynamicInserter = { - ChildInserter(childSource.toObservable, renderable, hooks = js.undefined) + ChildInserter(childSource.toObservable, renderable, initialHooks = js.undefined) } } diff --git a/src/main/scala/com/raquo/laminar/receivers/ChildTextReceiver.scala b/src/main/scala/com/raquo/laminar/receivers/ChildTextReceiver.scala index 7285a678..cb606b0c 100644 --- a/src/main/scala/com/raquo/laminar/receivers/ChildTextReceiver.scala +++ b/src/main/scala/com/raquo/laminar/receivers/ChildTextReceiver.scala @@ -32,7 +32,7 @@ object ChildTextReceiver { // #TODO[Perf] Test performance vs regular child.text, see if we need to improve this. // This .asInstanceOf is safe because `textNodeRenderable` only applies if `TextLike` is `TextNode`. val nodes = textSource.toObservable.asInstanceOf[Observable[TextNode]] - ChildInserter(nodes, RenderableNode.nodeRenderable, hooks = js.undefined) + ChildInserter(nodes, RenderableNode.nodeRenderable, initialHooks = js.undefined) } else { ChildTextInserter(textSource.toObservable, renderable) } diff --git a/src/main/scala/com/raquo/laminar/receivers/ChildrenCommandReceiver.scala b/src/main/scala/com/raquo/laminar/receivers/ChildrenCommandReceiver.scala index a21ba31a..5d80e2d2 100644 --- a/src/main/scala/com/raquo/laminar/receivers/ChildrenCommandReceiver.scala +++ b/src/main/scala/com/raquo/laminar/receivers/ChildrenCommandReceiver.scala @@ -13,6 +13,6 @@ object ChildrenCommandReceiver { )( implicit renderableNode: RenderableNode[Component] ): DynamicInserter = { - ChildrenCommandInserter(commands.toObservable, renderableNode, hooks = js.undefined) + ChildrenCommandInserter(commands.toObservable, renderableNode, initialHooks = js.undefined) } } diff --git a/src/main/scala/com/raquo/laminar/receivers/ChildrenReceiver.scala b/src/main/scala/com/raquo/laminar/receivers/ChildrenReceiver.scala index 90a9ab05..1981a5be 100644 --- a/src/main/scala/com/raquo/laminar/receivers/ChildrenReceiver.scala +++ b/src/main/scala/com/raquo/laminar/receivers/ChildrenReceiver.scala @@ -32,7 +32,7 @@ object ChildrenReceiver { // Let me know if you have a compelling use case for this. def <--(childrenSource: Source[immutable.Seq[ChildNode.Base]]): DynamicInserter = { - ChildrenInserter(childrenSource.toObservable, RenderableNode.nodeRenderable, hooks = js.undefined) + ChildrenInserter(childrenSource.toObservable, RenderableNode.nodeRenderable, initialHooks = js.undefined) } def <--[Component]( @@ -40,7 +40,7 @@ object ChildrenReceiver { )( implicit renderableNode: RenderableNode[Component] ): DynamicInserter = { - ChildrenInserter(childrenSource.toObservable, renderableNode, hooks = js.undefined) + ChildrenInserter(childrenSource.toObservable, renderableNode, initialHooks = js.undefined) } implicit class RichChildrenReceiver(val self: ChildrenReceiver.type) extends AnyVal {