@panphora/hyper-cms 0.11.3 → 0.11.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hypercms.min.js +44 -42
- package/package.json +3 -3
- package/src/lib/content-dom.js +58 -28
- package/src/lib/region-capabilities.js +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@panphora/hyper-cms",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.4",
|
|
4
4
|
"description": "Live edit-in-place CMS sidebar for HTML pages with a hyper-html-api rules tag",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"HTML",
|
|
@@ -47,8 +47,8 @@
|
|
|
47
47
|
"test:all": "npm run test:node && npm run test:browser"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"hyper-html-api": "^0.10.
|
|
51
|
-
"hyper-morph": "^1.
|
|
50
|
+
"hyper-html-api": "^0.10.1",
|
|
51
|
+
"hyper-morph": "^1.6.2"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@open-wc/testing": "^4.0.0",
|
package/src/lib/content-dom.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// GENERATED from clayjs/src/lib/content-dom.js. Edit ClayJS and run `npm run propagate:foundation`.
|
|
2
2
|
|
|
3
|
-
import { capabilitySelector, hasCapability } from './region-capabilities.js'
|
|
3
|
+
import { capabilitySelector, hasCapability, CAPABILITIES, expandsTo, hasPolicyToken } from './region-capabilities.js'
|
|
4
4
|
|
|
5
5
|
const UNSUPPORTED_SELECTOR = /:(?:focus(?:-within|-visible)?|hover|active|visited|defined)\b/i
|
|
6
6
|
|
|
@@ -9,10 +9,9 @@ function copyControlState(source, copy) {
|
|
|
9
9
|
const type = (source.getAttribute?.('type') || '').toLowerCase()
|
|
10
10
|
if ('value' in source && 'value' in copy && source.tagName !== 'OPTION' && type !== 'checkbox' && type !== 'radio' && type !== 'file') copy.value = source.value
|
|
11
11
|
if (source.tagName === 'INPUT' && (type === 'checkbox' || type === 'radio')) copy.checked = source.checked
|
|
12
|
+
// Each surviving option carries its own selection, so a <select> whose excluded
|
|
13
|
+
// options were removed from the copy keeps the rest in step with the page.
|
|
12
14
|
if (source.tagName === 'OPTION') copy.selected = source.selected
|
|
13
|
-
if (source.tagName === 'SELECT') {
|
|
14
|
-
for (let i = 0; i < source.options.length; i++) copy.options[i].selected = source.options[i].selected
|
|
15
|
-
}
|
|
16
15
|
if ('indeterminate' in source && 'indeterminate' in copy) copy.indeterminate = source.indeterminate
|
|
17
16
|
}
|
|
18
17
|
|
|
@@ -27,46 +26,77 @@ function matchesWithin(node, selector, boundary, inherit) {
|
|
|
27
26
|
return false
|
|
28
27
|
}
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
29
|
+
// One native deep clone, then a lockstep walk of the two isomorphic trees that
|
|
30
|
+
// records provenance, collects controls, and marks excluded subtrees for removal.
|
|
31
|
+
// The browser's clone is far cheaper than importing node by node. The root answers
|
|
32
|
+
// for its ancestors once; below it the walk is top-down, so with inheritance each
|
|
33
|
+
// element answers only for its own markers: an excluded ancestor is never entered.
|
|
34
|
+
function excluded(source, capability, capabilityMatch, exclude, boundary, inherit) {
|
|
35
|
+
return (inherit
|
|
36
|
+
? hasCapability(source, capability)
|
|
37
|
+
: matchesWithin(source, capabilityMatch, boundary, false)) ||
|
|
38
|
+
!!(exclude && (inherit && source !== boundary ? source.matches(exclude) : matchesWithin(source, exclude, boundary, inherit)))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// The test closestWithCapability makes at each level, for this element alone.
|
|
42
|
+
function hasOwnCapability(element, capability) {
|
|
43
|
+
const definition = CAPABILITIES[capability]
|
|
44
|
+
return definition.tokens.some(token => expandsTo(element, token)) ||
|
|
45
|
+
definition.bundles.some(bundle => hasPolicyToken(element, bundle))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const CONTROL_TAGS = new Set(['INPUT', 'TEXTAREA', 'SELECT', 'OPTION'])
|
|
49
|
+
|
|
50
|
+
function pairTree(source, copy, maps, drop) {
|
|
51
|
+
if (source.nodeType === 1 && drop(source)) { maps.dropped.push(copy); return }
|
|
38
52
|
maps.cloneToLive.set(copy, source)
|
|
39
53
|
maps.liveToClone.set(source, copy)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (
|
|
43
|
-
maps.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
copyChildren.
|
|
50
|
-
|
|
54
|
+
let sourceChildren = source
|
|
55
|
+
let copyChildren = copy
|
|
56
|
+
if (source.nodeType === 1) {
|
|
57
|
+
if (CONTROL_TAGS.has(source.tagName)) maps.controls.push(source, copy)
|
|
58
|
+
if (source.tagName === 'TEMPLATE') {
|
|
59
|
+
// The deep clone also copied the template's own child nodes, which are not its
|
|
60
|
+
// content and were never part of the view.
|
|
61
|
+
while (copy.firstChild) copy.removeChild(copy.firstChild)
|
|
62
|
+
sourceChildren = source.content
|
|
63
|
+
copyChildren = copy.content
|
|
64
|
+
maps.cloneToLive.set(copyChildren, sourceChildren)
|
|
65
|
+
maps.liveToClone.set(sourceChildren, copyChildren)
|
|
51
66
|
}
|
|
67
|
+
} else if (source.nodeType !== 11) return
|
|
68
|
+
let c = copyChildren.firstChild
|
|
69
|
+
for (let s = sourceChildren.firstChild; s && c; s = s.nextSibling) {
|
|
70
|
+
pairTree(s, c, maps, drop)
|
|
71
|
+
c = c.nextSibling
|
|
52
72
|
}
|
|
53
|
-
if (source.nodeType === 1) copyControlState(source, copy)
|
|
54
|
-
return copy
|
|
55
73
|
}
|
|
56
74
|
|
|
57
|
-
export function createContentView(context, { capability = 'data', exclude = null, inherit = true } = {}) {
|
|
75
|
+
export function createContentView(context, { capability = 'data', exclude = null, inherit = true, provenance = null } = {}) {
|
|
58
76
|
if (!context) throw new TypeError('createContentView requires a DOM context')
|
|
59
77
|
const sourceDocument = context.nodeType === 9 ? context : context.ownerDocument
|
|
60
78
|
if (!sourceDocument?.implementation?.createHTMLDocument) {
|
|
61
79
|
throw new TypeError('createContentView requires an HTML DOM implementation')
|
|
62
80
|
}
|
|
63
81
|
const inertDocument = sourceDocument.implementation.createHTMLDocument('')
|
|
64
|
-
const cloneToLive = new WeakMap()
|
|
82
|
+
const cloneToLive = provenance || new WeakMap()
|
|
65
83
|
const liveToClone = new WeakMap()
|
|
66
84
|
const sourceRoot = context.nodeType === 9 ? context.documentElement : context
|
|
67
85
|
if (exclude) inertDocument.documentElement.matches(exclude)
|
|
68
86
|
const capabilityMatch = capabilitySelector(capability)
|
|
69
|
-
const
|
|
87
|
+
const drop = inherit
|
|
88
|
+
? (el) => hasOwnCapability(el, capability) || !!(exclude && el.matches(exclude))
|
|
89
|
+
: (el) => excluded(el, capability, capabilityMatch, exclude, sourceRoot, false)
|
|
90
|
+
let root = null
|
|
91
|
+
if (sourceRoot.nodeType !== 1 || !excluded(sourceRoot, capability, capabilityMatch, exclude, sourceRoot, inherit)) {
|
|
92
|
+
root = inertDocument.importNode(sourceRoot, true)
|
|
93
|
+
const maps = { cloneToLive, liveToClone, dropped: [], controls: [] }
|
|
94
|
+
pairTree(sourceRoot, root, maps, drop)
|
|
95
|
+
for (const node of maps.dropped) node.remove()
|
|
96
|
+
// Control state is copied once the copy holds only what the view keeps, so a
|
|
97
|
+
// <select> sees exactly the options the old node-by-node import gave it.
|
|
98
|
+
for (let i = 0; i < maps.controls.length; i += 2) copyControlState(maps.controls[i], maps.controls[i + 1])
|
|
99
|
+
}
|
|
70
100
|
if (context.nodeType === 9 && root) {
|
|
71
101
|
inertDocument.replaceChild(root, inertDocument.documentElement)
|
|
72
102
|
liveToClone.set(context, inertDocument)
|