diff --git a/CHANGELOG.md b/CHANGELOG.md index 4013003..1a8021d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ * Remove `beforeNodePantried` callback option. This addition in v0.4.0 was an unfortunate necessity of the old `twoPass` mode, but is no longer needed with the new algorithm. (@botandrose) * Added: - * New `restoreFocus` option. On older browsers, moving the focused element (or one of its parents) can result in loss of focus and selection state. This option will restore this state for IDed elements, at the cost of firing extra `focus` and `selection` events. (@botandrose) + * New on-by-default `restoreFocus` option. On older browsers, moving the focused element (or one of its parents) can result in loss of focus and selection state. This option restores this state for IDed elements, at the cost of firing extra `focus` and `selection` events. (@botandrose) * Fixed: * Boolean attributes are now correctly set to `""` instead of `"true"`. https://developer.mozilla.org/en-US/docs/Glossary/Boolean/HTML (@MichaelWest22) diff --git a/README.md b/README.md index 99ce64d..1337774 100644 --- a/README.md +++ b/README.md @@ -89,29 +89,29 @@ This will replace the _inner_ content of the existing node with the new content. Idiomorph supports the following options: -| option | meaning | example | -|---------------------|-------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------| -| `morphStyle` | The style of morphing to use, either `innerHTML` or `outerHTML` | `Idiomorph.morph(..., {morphStyle:'innerHTML'})` | -| `ignoreActive` | If set to `true`, idiomorph will skip the active element | `Idiomorph.morph(..., {ignoreActive:true})` | -| `ignoreActiveValue` | If set to `true`, idiomorph will not update the active element's value | `Idiomorph.morph(..., {ignoreActiveValue:true})` | -| `restoreFocus` | If set to `true`, idiomorph will attempt to restore any lost focus and selection state after the morph. | `Idiomorph.morph(..., {restoreFocus:true})` | -| `head` | Allows you to control how the `head` tag is merged. See the [head](#the-head-tag) section for more details | `Idiomorph.morph(..., {head:{style:merge}})` | -| `callbacks` | Allows you to insert callbacks when events occur in the morph life cycle, see the callback table below | `Idiomorph.morph(..., {callbacks:{beforeNodeAdded:function(node){...}})` | +| option (with default) | meaning | example | +|-------------------------------|------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------| +| `morphStyle: 'outerHTML'` | The style of morphing to use, either `outerHTML` or `innerHTML` | `Idiomorph.morph(..., {morphStyle:'innerHTML'})` | +| `ignoreActive: false` | If `true`, idiomorph will skip the active element | `Idiomorph.morph(..., {ignoreActive:true})` | +| `ignoreActiveValue: false` | If `true`, idiomorph will not update the active element's value | `Idiomorph.morph(..., {ignoreActiveValue:true})` | +| `restoreFocus: true` | If `true`, idiomorph will attempt to restore any lost focus and selection state after the morph. | `Idiomorph.morph(..., {restoreFocus:true})` | +| `head: {style: 'merge', ...}` | Allows you to control how the `head` tag is merged. See the [head](#the-head-tag) section for more details | `Idiomorph.morph(..., {head:{style:'merge'}})` | +| `callbacks: {...}` | Allows you to insert callbacks when events occur in the morph lifecycle. See the callback table below | `Idiomorph.morph(..., {callbacks:{beforeNodeAdded:function(node){...}})` | #### Callbacks Idiomorph provides the following callbacks, which can be used to intercept and, for some callbacks, modify the swapping behavior of the algorithm. -| callback | description | return value meaning | -|--------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------| -| beforeNodeAdded(node) | Called before a new node is added to the DOM | return false to not add the node | -| afterNodeAdded(node) | Called after a new node is added to the DOM | none | -| beforeNodeMorphed(oldNode, newNode) | Called before a node is morphed in the DOM | return false to skip morphing the node | -| afterNodeMorphed(oldNode, newNode) | Called after a node is morphed in the DOM | none | -| beforeNodeRemoved(node) | Called before a node is removed from the DOM | return false to not remove the node | -| afterNodeRemoved(node) | Called after a node is removed from the DOM | none | -| beforeAttributeUpdated(attributeName, node, mutationType) | Called before an attribute on an element is updated or removed (`mutationType` is either "update" or "remove") | return false to not update or remove the attribute | +| callback | description | return value meaning | +|-----------------------------------------------------------|----------------------------------------------------------------------------------------------------------------|----------------------------------------------------| +| beforeNodeAdded(node) | Called before a new node is added to the DOM | return false to not add the node | +| afterNodeAdded(node) | Called after a new node is added to the DOM | none | +| beforeNodeMorphed(oldNode, newNode) | Called before a node is morphed in the DOM | return false to skip morphing the node | +| afterNodeMorphed(oldNode, newNode) | Called after a node is morphed in the DOM | none | +| beforeNodeRemoved(node) | Called before a node is removed from the DOM | return false to not remove the node | +| afterNodeRemoved(node) | Called after a node is removed from the DOM | none | +| beforeAttributeUpdated(attributeName, node, mutationType) | Called before an attribute on an element is updated or removed (`mutationType` is either "update" or "remove") | return false to not update or remove the attribute | ### The `head` tag diff --git a/src/idiomorph.js b/src/idiomorph.js index 44d5594..38d25f7 100644 --- a/src/idiomorph.js +++ b/src/idiomorph.js @@ -141,7 +141,7 @@ var Idiomorph = (function () { shouldRemove: noOp, afterHeadMorphed: noOp, }, - restoreFocus: false, + restoreFocus: true, }; /** diff --git a/test/preserve-focus.js b/test/preserve-focus.js index 4381f24..23cf630 100644 --- a/test/preserve-focus.js +++ b/test/preserve-focus.js @@ -1,4 +1,4 @@ -describe("Preserves focus where possible", function () { +describe("Preserves focus algorithmically where possible", function () { setup(); function assertFocusPreservation( @@ -12,6 +12,7 @@ describe("Preserves focus where possible", function () { setFocusAndSelection(focusId, selection); Idiomorph.morph(getWorkArea(), after, { morphStyle: "innerHTML", + restoreFocus: false, }); getWorkArea().innerHTML.should.equal(after); // for when we fall short of the ideal diff --git a/test/restore-focus.js b/test/restore-focus.js index 607e005..ce39323 100644 --- a/test/restore-focus.js +++ b/test/restore-focus.js @@ -1,281 +1,589 @@ describe("Option to forcibly restore focus after morph", function () { setup(); - it("restores focus and selection state with and outerHTML morphStyle", function () { - const div = make(` -
- - -
- `); - getWorkArea().append(div); - setFocusAndSelection("focused", "b"); - - let finalSrc = ` -
- - -
- `; - Idiomorph.morph(div, finalSrc, { - morphStyle: "outerHTML", - restoreFocus: true, - }); - - getWorkArea().innerHTML.should.equal(finalSrc); - assertFocusAndSelection("focused", "b"); - }); - - it("restores focus and selection state when elements are moved to different levels of the DOM", function () { - getWorkArea().innerHTML = ` -
- + describe("defaults to on", function () { + it("restores focus and selection state with outerHTML morphStyle", function () { + const div = make(`
+
-
- `; - setFocusAndSelection("focused", "b"); - - let finalSrc = ` -
- - -
- `; - Idiomorph.morph(getWorkArea(), finalSrc, { - morphStyle: "innerHTML", - restoreFocus: true, - }); - - getWorkArea().innerHTML.should.equal(finalSrc); - assertFocusAndSelection("focused", "b"); - }); + `); + getWorkArea().append(div); + setFocusAndSelection("focused", "b"); - it("restores focus and selection state when elements are moved between different containers", function () { - getWorkArea().innerHTML = ` -
-
+ let finalSrc = ` +
+
- - `; - setFocusAndSelection("focused", "b"); + `; + setFocusAndSelection("focused", "b"); - let finalSrc = ` -
-
+ let finalSrc = ` +
-
- -
- `; - Idiomorph.morph(getWorkArea(), finalSrc, { - morphStyle: "innerHTML", - restoreFocus: true, + `; + Idiomorph.morph(getWorkArea(), finalSrc, { + morphStyle: "innerHTML", + }); + + getWorkArea().innerHTML.should.equal(finalSrc); + assertFocusAndSelection("focused", "b"); }); - getWorkArea().innerHTML.should.equal(finalSrc); - assertFocusAndSelection("focused", "b"); - }); + it("restores focus and selection state when elements are moved between different containers", function () { + getWorkArea().innerHTML = ` +
+
+ +
+ +
+ `; + setFocusAndSelection("focused", "b"); + + let finalSrc = ` +
+
+ +
+ +
+ `; + Idiomorph.morph(getWorkArea(), finalSrc, { + morphStyle: "innerHTML", + }); - it("restores focus and selection state when parents are reorderd", function () { - getWorkArea().innerHTML = ` -
-
+ getWorkArea().innerHTML.should.equal(finalSrc); + assertFocusAndSelection("focused", "b"); + }); + + it("restores focus and selection state when parents are reorderd", function () { + getWorkArea().innerHTML = ` +
+
+ +
+ +
+ `; + setFocusAndSelection("focused", "b"); + + let finalSrc = ` +
+ +
+ +
+
+ `; + Idiomorph.morph(getWorkArea(), finalSrc, { + morphStyle: "innerHTML", + }); + + getWorkArea().innerHTML.should.equal(finalSrc); + assertFocusAndSelection("focused", "b"); + }); + + it("restores focus and selection state with outerHTML morphStyle", function () { + const div = make(` +
+
- - `; - setFocusAndSelection("focused", "b"); + `; + Idiomorph.morph(div, finalSrc, { + morphStyle: "outerHTML", + }); + + getWorkArea().innerHTML.should.equal(finalSrc); + assertFocusAndSelection("focused", "b"); + }); - let finalSrc = ` -
-