lib0 1.0.0-rc.2 → 1.0.0-rc.21
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/README.md +0 -1315
- package/{types → dist}/buffer.d.ts +2 -10
- package/{types → dist}/delta/delta.d.ts +582 -144
- package/dist/delta/position.d.ts +65 -0
- package/dist/delta/rdt/delta.d.ts +57 -0
- package/dist/delta/rdt/dom.d.ts +102 -0
- package/dist/delta/rdt.d.ts +180 -0
- package/dist/delta/transformer/attr.d.ts +64 -0
- package/dist/delta/transformer/attribution-to-format.d.ts +52 -0
- package/dist/delta/transformer/children.d.ts +69 -0
- package/dist/delta/transformer/conform.d.ts +83 -0
- package/dist/delta/transformer/core.d.ts +189 -0
- package/dist/delta/transformer/full-attributions.d.ts +37 -0
- package/dist/delta/transformer/id.d.ts +2 -0
- package/dist/delta/transformer/inline.d.ts +89 -0
- package/dist/delta/transformer/pipe.d.ts +182 -0
- package/dist/delta/transformer/project.d.ts +223 -0
- package/dist/delta/transformer/rename-attrs.d.ts +75 -0
- package/dist/delta/transformer/rename.d.ts +41 -0
- package/dist/delta/transformer/value.d.ts +92 -0
- package/dist/delta/transformer.d.ts +14 -0
- package/dist/diff/patience.d.ts +25 -0
- package/{types → dist}/environment.d.ts +10 -0
- package/dist/hash/fnv1a.d.ts +11 -0
- package/{types → dist}/schema.d.ts +49 -38
- package/{types → dist}/ts.d.ts +4 -0
- package/dist/webcrypto.d.ts +3 -0
- package/package.json +148 -82
- package/src/bin/0serve.js +11 -3
- package/src/broadcastchannel.js +6 -1
- package/src/buffer.js +35 -25
- package/src/decoding.js +1 -1
- package/src/delta/delta.js +2531 -564
- package/src/delta/position.js +165 -0
- package/src/delta/rdt/delta.js +105 -0
- package/src/delta/rdt/dom.js +307 -0
- package/src/delta/rdt.js +255 -0
- package/src/delta/transformer/attr.js +126 -0
- package/src/delta/transformer/attribution-to-format.js +335 -0
- package/src/delta/transformer/children.js +219 -0
- package/src/delta/transformer/conform.js +401 -0
- package/src/delta/transformer/core.js +323 -0
- package/src/delta/transformer/full-attributions.js +282 -0
- package/src/delta/transformer/id.js +13 -0
- package/src/delta/transformer/inline.js +818 -0
- package/src/delta/transformer/pipe.js +272 -0
- package/src/delta/transformer/project.js +526 -0
- package/src/delta/transformer/rename-attrs.js +152 -0
- package/src/delta/transformer/rename.js +84 -0
- package/src/delta/transformer/value.js +214 -0
- package/src/delta/transformer.js +38 -0
- package/src/diff/patience.js +22 -11
- package/src/dom.js +2 -2
- package/src/encoding.js +1 -1
- package/src/environment.js +15 -0
- package/src/hash/fnv1a.js +82 -0
- package/src/number.js +1 -3
- package/src/performance.node.js +3 -3
- package/src/schema.js +231 -177
- package/src/string.js +3 -5
- package/src/testing.js +1 -1
- package/src/trait/equality.js +1 -1
- package/src/trait/fingerprint.js +1 -1
- package/src/ts.js +11 -0
- package/src/bin/gendocs.js +0 -117
- package/src/component.js +0 -414
- package/src/delta/binding.js +0 -372
- package/src/tree.js +0 -546
- package/types/bin/gendocs.d.ts +0 -3
- package/types/component.d.ts +0 -86
- package/types/delta/binding.d.ts +0 -106
- package/types/diff/patience.d.ts +0 -16
- package/types/tree.d.ts +0 -96
- package/types/webcrypto.d.ts +0 -3
- /package/{types → dist}/array.d.ts +0 -0
- /package/{types → dist}/bin/0ecdsa-generate-keypair.d.ts +0 -0
- /package/{types → dist}/bin/0serve.d.ts +0 -0
- /package/{types → dist}/bin/gentesthtml.d.ts +0 -0
- /package/{types → dist}/binary.d.ts +0 -0
- /package/{types → dist}/broadcastchannel.d.ts +0 -0
- /package/{types → dist}/cache.d.ts +0 -0
- /package/{types → dist}/conditions.d.ts +0 -0
- /package/{types → dist}/crypto/aes-gcm.d.ts +0 -0
- /package/{types → dist}/crypto/common.d.ts +0 -0
- /package/{types → dist}/crypto/ecdsa.d.ts +0 -0
- /package/{types → dist}/crypto/jwt.d.ts +0 -0
- /package/{types → dist}/crypto/rsa-oaep.d.ts +0 -0
- /package/{types → dist}/decoding.d.ts +0 -0
- /package/{types → dist}/diff.d.ts +0 -0
- /package/{types → dist}/dom.d.ts +0 -0
- /package/{types → dist}/encoding.d.ts +0 -0
- /package/{types → dist}/error.d.ts +0 -0
- /package/{types → dist}/eventloop.d.ts +0 -0
- /package/{types → dist}/function.d.ts +0 -0
- /package/{types → dist}/hash/rabin-gf2-polynomial.d.ts +0 -0
- /package/{types → dist}/hash/rabin-uncached.d.ts +0 -0
- /package/{types → dist}/hash/rabin.d.ts +0 -0
- /package/{types → dist}/hash/sha256.d.ts +0 -0
- /package/{types → dist}/hash/sha256.node.d.ts +0 -0
- /package/{types → dist}/indexeddb.d.ts +0 -0
- /package/{types → dist}/indexeddbV2.d.ts +0 -0
- /package/{types → dist}/iterator.d.ts +0 -0
- /package/{types → dist}/json.d.ts +0 -0
- /package/{types → dist}/list.d.ts +0 -0
- /package/{types → dist}/logging.common.d.ts +0 -0
- /package/{types → dist}/logging.d.ts +0 -0
- /package/{types → dist}/logging.node.d.ts +0 -0
- /package/{types → dist}/map.d.ts +0 -0
- /package/{types → dist}/math.d.ts +0 -0
- /package/{types → dist}/metric.d.ts +0 -0
- /package/{types → dist}/mutex.d.ts +0 -0
- /package/{types → dist}/number.d.ts +0 -0
- /package/{types → dist}/object.d.ts +0 -0
- /package/{types → dist}/observable.d.ts +0 -0
- /package/{types → dist}/pair.d.ts +0 -0
- /package/{types → dist}/performance.d.ts +0 -0
- /package/{types → dist}/performance.node.d.ts +0 -0
- /package/{types → dist}/pledge.d.ts +0 -0
- /package/{types → dist}/prng/Mt19937.d.ts +0 -0
- /package/{types → dist}/prng/Xoroshiro128plus.d.ts +0 -0
- /package/{types → dist}/prng/Xorshift32.d.ts +0 -0
- /package/{types → dist}/prng.d.ts +0 -0
- /package/{types → dist}/promise.d.ts +0 -0
- /package/{types → dist}/queue.d.ts +0 -0
- /package/{types → dist}/random.d.ts +0 -0
- /package/{types → dist}/set.d.ts +0 -0
- /package/{types → dist}/sort.d.ts +0 -0
- /package/{types → dist}/statistics.d.ts +0 -0
- /package/{types → dist}/storage.d.ts +0 -0
- /package/{types → dist}/string.d.ts +0 -0
- /package/{types → dist}/symbol.d.ts +0 -0
- /package/{types → dist}/testing.d.ts +0 -0
- /package/{types → dist}/time.d.ts +0 -0
- /package/{types → dist}/trait/equality.d.ts +0 -0
- /package/{types → dist}/trait/fingerprint.d.ts +0 -0
- /package/{types → dist}/trait/traits.d.ts +0 -0
- /package/{types → dist}/url.d.ts +0 -0
- /package/{types → dist}/webcrypto.deno.d.ts +0 -0
- /package/{types → dist}/webcrypto.node.d.ts +0 -0
- /package/{types → dist}/webcrypto.react-native.d.ts +0 -0
- /package/{types → dist}/websocket.d.ts +0 -0
package/src/string.js
CHANGED
|
@@ -84,8 +84,7 @@ export const _encodeUtf8Native = str => utf8TextEncoder.encode(str)
|
|
|
84
84
|
* @param {string} str
|
|
85
85
|
* @return {Uint8Array}
|
|
86
86
|
*/
|
|
87
|
-
/* c8 ignore next
|
|
88
|
-
/* @__NO_SIDE_EFFECTS__ */
|
|
87
|
+
/* c8 ignore next 1 */
|
|
89
88
|
export const encodeUtf8 = /* @__PURE__ */(() => utf8TextEncoder ? _encodeUtf8Native : _encodeUtf8Polyfill)()
|
|
90
89
|
|
|
91
90
|
/**
|
|
@@ -135,9 +134,8 @@ export const _decodeUtf8Native = buf => /** @type {TextDecoder} */ (utf8TextDeco
|
|
|
135
134
|
* @param {Uint8Array} buf
|
|
136
135
|
* @return {string}
|
|
137
136
|
*/
|
|
138
|
-
/* c8 ignore next
|
|
139
|
-
/* @
|
|
140
|
-
export const decodeUtf8 = utf8TextDecoder ? _decodeUtf8Native : _decodeUtf8Polyfill
|
|
137
|
+
/* c8 ignore next 1 */
|
|
138
|
+
export const decodeUtf8 = /* @__PURE__ */ (() => utf8TextDecoder ? _decodeUtf8Native : _decodeUtf8Polyfill)()
|
|
141
139
|
|
|
142
140
|
/**
|
|
143
141
|
* @param {string} str The initial string
|
package/src/testing.js
CHANGED
|
@@ -63,7 +63,7 @@ export { production } from './environment.js'
|
|
|
63
63
|
export const extensive = env.hasConf('extensive')
|
|
64
64
|
|
|
65
65
|
/* c8 ignore next */
|
|
66
|
-
export const envSeed = env.hasParam('--seed') ? Number.parseInt(env.getParam('--seed', '0')) : null
|
|
66
|
+
export const envSeed = /* @__PURE__ */(() => env.hasParam('--seed') ? Number.parseInt(env.getParam('--seed', '0')) : null)()
|
|
67
67
|
|
|
68
68
|
export class TestCase {
|
|
69
69
|
/**
|
package/src/trait/equality.js
CHANGED
package/src/trait/fingerprint.js
CHANGED
|
@@ -2,7 +2,7 @@ import * as encoding from '../encoding.js'
|
|
|
2
2
|
import * as rabin from '../hash/rabin.js'
|
|
3
3
|
import * as buffer from '../buffer.js'
|
|
4
4
|
|
|
5
|
-
export const FingerprintTraitSymbol = Symbol('
|
|
5
|
+
export const FingerprintTraitSymbol = Symbol.for('0:fingerprint')
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* When implementing this trait, it is recommended to write some sort of "magic number" first to
|
package/src/ts.js
CHANGED
|
@@ -45,6 +45,17 @@
|
|
|
45
45
|
* @typedef {{[K in keyof OBJ as K extends keyof Renames ? Renames[K] : K extends Renames[keyof Renames] ? never : K]: OBJ[K]}} PropsRename
|
|
46
46
|
*/
|
|
47
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Convert a union type to an intersection type.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* // { a: string } & { b: number }
|
|
53
|
+
* type R = UnionToIntersection<{ a: string } | { b: number }>
|
|
54
|
+
*
|
|
55
|
+
* @template U
|
|
56
|
+
* @typedef {(U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never} UnionToIntersection
|
|
57
|
+
*/
|
|
58
|
+
|
|
48
59
|
/**
|
|
49
60
|
* Merge two objects, keeping only keys present in both with intersected value types.
|
|
50
61
|
* e.g. IntersectObjects<{a:string,b:string|number},{b:string,c:number}> = {b:string}
|
package/src/bin/gendocs.js
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// @ts-ignore
|
|
3
|
-
import jsdoc from 'jsdoc-api'
|
|
4
|
-
import * as fs from 'fs'
|
|
5
|
-
|
|
6
|
-
const firstTagContentRegex = /<\w>([^<]+)<\/\w>([^]*)/
|
|
7
|
-
const jsdocReturnRegex = /\* @return {(.*)}/
|
|
8
|
-
const jsdocTypeRegex = /\* @type {(.*)}/
|
|
9
|
-
|
|
10
|
-
const files = fs.readdirSync('./').filter(file => /(?<!(test|config))\.js$/.test(file))
|
|
11
|
-
|
|
12
|
-
const _ltregex = /</g
|
|
13
|
-
const _rtregex = />/g
|
|
14
|
-
/**
|
|
15
|
-
* @param {string} s
|
|
16
|
-
*/
|
|
17
|
-
const toSafeHtml = s => s.replace(_ltregex, '<').replace(_rtregex, '>')
|
|
18
|
-
|
|
19
|
-
const READMEcontent = fs.readFileSync('./README.md', 'utf8')
|
|
20
|
-
|
|
21
|
-
jsdoc.explain({
|
|
22
|
-
files,
|
|
23
|
-
configure: '.jsdoc.json'
|
|
24
|
-
}).then(/** @param {Array<any>} json */ json => {
|
|
25
|
-
const strBuilder = []
|
|
26
|
-
/**
|
|
27
|
-
* @type {Object<string, { items: Array<any>, name: string, description: string }>}
|
|
28
|
-
*/
|
|
29
|
-
const modules = {}
|
|
30
|
-
json.forEach(item => {
|
|
31
|
-
if (item.meta && item.meta.filename) {
|
|
32
|
-
const mod = modules[item.meta.filename] || (modules[item.meta.filename] = { items: [], name: item.meta.filename.slice(0, -3), description: '' })
|
|
33
|
-
if (item.kind === 'module') {
|
|
34
|
-
mod.name = item.name
|
|
35
|
-
mod.description = item.description || ''
|
|
36
|
-
} else {
|
|
37
|
-
mod.items.push(item)
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
})
|
|
41
|
-
/**
|
|
42
|
-
* @type {Object<string,string>}
|
|
43
|
-
*/
|
|
44
|
-
const classDescriptions = {}
|
|
45
|
-
for (const fileName in modules) {
|
|
46
|
-
const mod = modules[fileName]
|
|
47
|
-
const items = mod.items
|
|
48
|
-
const desc = firstTagContentRegex.exec(mod.description)
|
|
49
|
-
const descHead = desc ? desc[1] : ''
|
|
50
|
-
const descRest = desc ? desc[2] : ''
|
|
51
|
-
strBuilder.push(`<details><summary><b>[lib0/${mod.name}]</b> ${descHead}</summary>`)
|
|
52
|
-
strBuilder.push(`<pre>import * as ${mod.name} from 'lib0/${fileName.slice(0, -3)}'</pre>`)
|
|
53
|
-
if (descRest.length > 0) {
|
|
54
|
-
strBuilder.push(descRest)
|
|
55
|
-
}
|
|
56
|
-
strBuilder.push('<dl>')
|
|
57
|
-
for (let i = 0; i < items.length; i++) {
|
|
58
|
-
const item = items[i]
|
|
59
|
-
if (!item.ignore && item.scope !== 'inner' && item.name[0] !== '_' && item.longname.indexOf('~') < 0) {
|
|
60
|
-
// strBuilder.push(JSON.stringify(item)) // output json for debugging
|
|
61
|
-
switch (item.kind) {
|
|
62
|
-
case 'class': {
|
|
63
|
-
if (item.params == null) {
|
|
64
|
-
classDescriptions[item.longname] = item.classdesc
|
|
65
|
-
break
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
// eslint-disable-next-line
|
|
69
|
-
case 'constant': {
|
|
70
|
-
if (item.params == null && item.returns == null) {
|
|
71
|
-
const typeEval = jsdocTypeRegex.exec(item.comment)
|
|
72
|
-
strBuilder.push(`<b><code>${item.longname.slice(7)}${typeEval ? (': ' + toSafeHtml(typeEval[1])) : ''}</code></b><br>`)
|
|
73
|
-
if (item.description) {
|
|
74
|
-
strBuilder.push(`<dd>${item.description}</dd>`)
|
|
75
|
-
}
|
|
76
|
-
break
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
// eslint-disable-next-line
|
|
80
|
-
case 'function': {
|
|
81
|
-
/**
|
|
82
|
-
* @param {string} name
|
|
83
|
-
*/
|
|
84
|
-
const getOriginalParamTypeDecl = name => {
|
|
85
|
-
const regval = new RegExp('@param {(.*)} \\[?' + name + '\\]?[^\\w]*').exec(item.comment)
|
|
86
|
-
return regval ? regval[1] : null
|
|
87
|
-
}
|
|
88
|
-
if (item.params == null && item.returns == null) {
|
|
89
|
-
break
|
|
90
|
-
}
|
|
91
|
-
const paramVal = (item.params || []).map(/** @param {any} ret */ ret => `${ret.name}: ${getOriginalParamTypeDecl(ret.name) || ret.type.names.join('|')}`).join(', ')
|
|
92
|
-
const evalReturnRegex = jsdocReturnRegex.exec(item.comment)
|
|
93
|
-
const returnVal = evalReturnRegex ? `: ${evalReturnRegex[1]}` : (item.returns ? item.returns.map(/** @param {any} r */ r => r.type.names.join('|')).join('|') : '')
|
|
94
|
-
strBuilder.push(`<b><code>${item.kind === 'class' ? 'new ' : ''}${item.longname.slice(7)}(${toSafeHtml(paramVal)})${toSafeHtml(returnVal)}</code></b><br>`)
|
|
95
|
-
const desc = item.description || item.classdesc || classDescriptions[item.longname] || null
|
|
96
|
-
if (desc) {
|
|
97
|
-
strBuilder.push(`<dd>${desc}</dd>`)
|
|
98
|
-
}
|
|
99
|
-
break
|
|
100
|
-
}
|
|
101
|
-
case 'member': {
|
|
102
|
-
if (item.type) {
|
|
103
|
-
strBuilder.push(`<b><code>${item.longname.slice(7)}: ${toSafeHtml(/** @type {RegExpExecArray} */ (jsdocTypeRegex.exec(item.comment))[1])}</code></b><br>`)
|
|
104
|
-
if (item.description) {
|
|
105
|
-
strBuilder.push(`<dd>${item.description}</dd>`)
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
strBuilder.push('</dl>')
|
|
113
|
-
strBuilder.push('</details>')
|
|
114
|
-
}
|
|
115
|
-
const replaceReadme = READMEcontent.replace(/<details>[^]*<\/details>/, strBuilder.join('\n'))
|
|
116
|
-
fs.writeFileSync('./README.md', replaceReadme)
|
|
117
|
-
})
|
package/src/component.js
DELETED
|
@@ -1,414 +0,0 @@
|
|
|
1
|
-
/* eslint-env browser */
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Web components.
|
|
5
|
-
*
|
|
6
|
-
* @module component
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import * as dom from './dom.js'
|
|
10
|
-
import * as diff from './diff.js'
|
|
11
|
-
import * as object from './object.js'
|
|
12
|
-
import * as json from './json.js'
|
|
13
|
-
import * as string from './string.js'
|
|
14
|
-
import * as array from './array.js'
|
|
15
|
-
import * as number from './number.js'
|
|
16
|
-
import * as func from './function.js'
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* @type {CustomElementRegistry}
|
|
20
|
-
*/
|
|
21
|
-
export const registry = customElements
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* @param {string} name
|
|
25
|
-
* @param {any} constr
|
|
26
|
-
* @param {ElementDefinitionOptions} [opts]
|
|
27
|
-
*/
|
|
28
|
-
export const define = (name, constr, opts) => registry.define(name, constr, opts)
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* @param {string} name
|
|
32
|
-
* @return {Promise<CustomElementConstructor>}
|
|
33
|
-
*/
|
|
34
|
-
export const whenDefined = name => registry.whenDefined(name)
|
|
35
|
-
|
|
36
|
-
const upgradedEventName = 'upgraded'
|
|
37
|
-
const connectedEventName = 'connected'
|
|
38
|
-
const disconnectedEventName = 'disconnected'
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* @template S
|
|
42
|
-
*/
|
|
43
|
-
export class Lib0Component extends HTMLElement {
|
|
44
|
-
/**
|
|
45
|
-
* @param {S} [state]
|
|
46
|
-
*/
|
|
47
|
-
constructor (state) {
|
|
48
|
-
super()
|
|
49
|
-
/**
|
|
50
|
-
* @type {S|null}
|
|
51
|
-
*/
|
|
52
|
-
this.state = /** @type {any} */ (state)
|
|
53
|
-
/**
|
|
54
|
-
* @type {any}
|
|
55
|
-
*/
|
|
56
|
-
this._internal = {}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* @param {S} _state
|
|
61
|
-
* @param {boolean} [_forceStateUpdate] Force that the state is rerendered even if state didn't change
|
|
62
|
-
*/
|
|
63
|
-
setState (_state, _forceStateUpdate = true) {}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* @param {any} _stateUpdate
|
|
67
|
-
*/
|
|
68
|
-
updateState (_stateUpdate) { }
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* @param {any} val
|
|
73
|
-
* @param {"json"|"string"|"number"} type
|
|
74
|
-
* @return {string}
|
|
75
|
-
*/
|
|
76
|
-
const encodeAttrVal = (val, type) => {
|
|
77
|
-
if (type === 'json') {
|
|
78
|
-
val = json.stringify(val)
|
|
79
|
-
}
|
|
80
|
-
return val + ''
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* @param {any} val
|
|
85
|
-
* @param {"json"|"string"|"number"|"bool"} type
|
|
86
|
-
* @return {any}
|
|
87
|
-
*/
|
|
88
|
-
const parseAttrVal = (val, type) => {
|
|
89
|
-
switch (type) {
|
|
90
|
-
case 'json':
|
|
91
|
-
return json.parse(val)
|
|
92
|
-
case 'number':
|
|
93
|
-
return Number.parseFloat(val)
|
|
94
|
-
case 'string':
|
|
95
|
-
return val
|
|
96
|
-
case 'bool':
|
|
97
|
-
return val != null
|
|
98
|
-
default:
|
|
99
|
-
return null
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* @template S
|
|
105
|
-
* @typedef {Object} CONF
|
|
106
|
-
* @property {string?} [CONF.template] Template for the shadow dom.
|
|
107
|
-
* @property {string} [CONF.style] shadow dom style. Is only used when
|
|
108
|
-
* `CONF.template` is defined
|
|
109
|
-
* @property {S} [CONF.state] Initial component state.
|
|
110
|
-
* @property {function(S,S|null,Lib0Component<S>):void} [CONF.onStateChange] Called when
|
|
111
|
-
* the state changes.
|
|
112
|
-
* @property {Object<string,function(any, any):Object>} [CONF.childStates] maps from
|
|
113
|
-
* CSS-selector to transformer function. The first element that matches the
|
|
114
|
-
* CSS-selector receives state updates via the transformer function.
|
|
115
|
-
* @property {Object<string,"json"|"number"|"string"|"bool">} [CONF.attrs]
|
|
116
|
-
* attrs-keys and state-keys should be camelCase, but the DOM uses kebap-case. I.e.
|
|
117
|
-
* `attrs = { myAttr: 4 }` is represeted as `<my-elem my-attr="4" />` in the DOM
|
|
118
|
-
* @property {Object<string, function(CustomEvent, Lib0Component<any>):boolean|void>} [CONF.listeners] Maps from dom-event-name
|
|
119
|
-
* to event listener.
|
|
120
|
-
* @property {function(S, S, Lib0Component<S>):Object<string,string>} [CONF.slots] Fill slots
|
|
121
|
-
* automatically when state changes. Maps from slot-name to slot-html.
|
|
122
|
-
*/
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* @template T
|
|
126
|
-
* @param {string} name
|
|
127
|
-
* @param {CONF<T>} cnf
|
|
128
|
-
* @return {typeof Lib0Component}
|
|
129
|
-
*/
|
|
130
|
-
export const createComponent = (name, { template, style = '', state: defaultState, onStateChange = () => {}, childStates = { }, attrs = {}, listeners = {}, slots = () => ({}) }) => {
|
|
131
|
-
/**
|
|
132
|
-
* Maps from camelCase attribute name to kebap-case attribute name.
|
|
133
|
-
* @type {Object<string,string>}
|
|
134
|
-
*/
|
|
135
|
-
const normalizedAttrs = {}
|
|
136
|
-
for (const key in attrs) {
|
|
137
|
-
normalizedAttrs[string.fromCamelCase(key, '-')] = key
|
|
138
|
-
}
|
|
139
|
-
const templateElement = template
|
|
140
|
-
? /** @type {HTMLTemplateElement} */ (dom.parseElement(`
|
|
141
|
-
<template>
|
|
142
|
-
<style>${style}</style>
|
|
143
|
-
${template}
|
|
144
|
-
</template>
|
|
145
|
-
`))
|
|
146
|
-
: null
|
|
147
|
-
|
|
148
|
-
class _Lib0Component extends HTMLElement {
|
|
149
|
-
/**
|
|
150
|
-
* @param {T} [state]
|
|
151
|
-
*/
|
|
152
|
-
constructor (state) {
|
|
153
|
-
super()
|
|
154
|
-
/**
|
|
155
|
-
* @type {Array<{d:Lib0Component<T>, s:function(any, any):Object}>}
|
|
156
|
-
*/
|
|
157
|
-
this._childStates = []
|
|
158
|
-
/**
|
|
159
|
-
* @type {Object<string,string>}
|
|
160
|
-
*/
|
|
161
|
-
this._slots = {}
|
|
162
|
-
this._init = false
|
|
163
|
-
/**
|
|
164
|
-
* @type {any}
|
|
165
|
-
*/
|
|
166
|
-
this._internal = {}
|
|
167
|
-
/**
|
|
168
|
-
* @type {any}
|
|
169
|
-
*/
|
|
170
|
-
this.state = state || null
|
|
171
|
-
this.connected = false
|
|
172
|
-
// init shadow dom
|
|
173
|
-
if (templateElement) {
|
|
174
|
-
const shadow = /** @type {ShadowRoot} */ (this.attachShadow({ mode: 'open' }))
|
|
175
|
-
shadow.appendChild(templateElement.content.cloneNode(true))
|
|
176
|
-
// fill child states
|
|
177
|
-
for (const key in childStates) {
|
|
178
|
-
this._childStates.push({
|
|
179
|
-
d: /** @type {Lib0Component<T>} */ (dom.querySelector(/** @type {any} */ (shadow), key)),
|
|
180
|
-
s: childStates[key]
|
|
181
|
-
})
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
dom.emitCustomEvent(this, upgradedEventName, { bubbles: true })
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
connectedCallback () {
|
|
188
|
-
this.connected = true
|
|
189
|
-
if (!this._init) {
|
|
190
|
-
this._init = true
|
|
191
|
-
const shadow = this.shadowRoot
|
|
192
|
-
if (shadow) {
|
|
193
|
-
dom.addEventListener(shadow, upgradedEventName, event => {
|
|
194
|
-
this.setState(this.state, true)
|
|
195
|
-
event.stopPropagation()
|
|
196
|
-
})
|
|
197
|
-
}
|
|
198
|
-
/**
|
|
199
|
-
* @type {Object<string, any>}
|
|
200
|
-
*/
|
|
201
|
-
const startState = this.state || object.assign({}, defaultState)
|
|
202
|
-
if (attrs) {
|
|
203
|
-
for (const key in attrs) {
|
|
204
|
-
const normalizedKey = string.fromCamelCase(key, '-')
|
|
205
|
-
const val = parseAttrVal(this.getAttribute(normalizedKey), attrs[key])
|
|
206
|
-
if (val) {
|
|
207
|
-
startState[key] = val
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
// add event listeners
|
|
212
|
-
for (const key in listeners) {
|
|
213
|
-
dom.addEventListener(shadow || this, key, event => {
|
|
214
|
-
if (listeners[key](/** @type {CustomEvent} */ (event), this) !== false) {
|
|
215
|
-
event.stopPropagation()
|
|
216
|
-
event.preventDefault()
|
|
217
|
-
return false
|
|
218
|
-
}
|
|
219
|
-
})
|
|
220
|
-
}
|
|
221
|
-
// first setState call
|
|
222
|
-
this.state = null
|
|
223
|
-
this.setState(startState)
|
|
224
|
-
}
|
|
225
|
-
dom.emitCustomEvent(/** @type {any} */ (this.shadowRoot || this), connectedEventName, { bubbles: true })
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
disconnectedCallback () {
|
|
229
|
-
this.connected = false
|
|
230
|
-
dom.emitCustomEvent(/** @type {any} */ (this.shadowRoot || this), disconnectedEventName, { bubbles: true })
|
|
231
|
-
this.setState(null)
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
static get observedAttributes () {
|
|
235
|
-
return object.keys(normalizedAttrs)
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* @param {string} name
|
|
240
|
-
* @param {string} oldVal
|
|
241
|
-
* @param {string} newVal
|
|
242
|
-
*
|
|
243
|
-
* @private
|
|
244
|
-
*/
|
|
245
|
-
attributeChangedCallback (name, oldVal, newVal) {
|
|
246
|
-
const curState = /** @type {any} */ (this.state)
|
|
247
|
-
const camelAttrName = normalizedAttrs[name]
|
|
248
|
-
const type = attrs[camelAttrName]
|
|
249
|
-
const parsedVal = parseAttrVal(newVal, type)
|
|
250
|
-
if (curState && (type !== 'json' || json.stringify(curState[camelAttrName]) !== newVal) && curState[camelAttrName] !== parsedVal && !number.isNaN(parsedVal)) {
|
|
251
|
-
this.updateState({ [camelAttrName]: parsedVal })
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* @param {any} stateUpdate
|
|
257
|
-
*/
|
|
258
|
-
updateState (stateUpdate) {
|
|
259
|
-
this.setState(object.assign({}, this.state, stateUpdate))
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* @param {any} state
|
|
264
|
-
*/
|
|
265
|
-
setState (state, forceStateUpdates = false) {
|
|
266
|
-
const prevState = this.state
|
|
267
|
-
this.state = state
|
|
268
|
-
if (this._init && (!func.equalityFlat(state, prevState) || forceStateUpdates)) {
|
|
269
|
-
// fill slots
|
|
270
|
-
if (state) {
|
|
271
|
-
const slotElems = slots(state, prevState, this)
|
|
272
|
-
for (const key in slotElems) {
|
|
273
|
-
const slotContent = slotElems[key]
|
|
274
|
-
if (this._slots[key] !== slotContent) {
|
|
275
|
-
this._slots[key] = slotContent
|
|
276
|
-
const currentSlots = /** @type {Array<any>} */ (key !== 'default' ? array.from(dom.querySelectorAll(this, `[slot="${key}"]`)) : array.from(this.childNodes).filter(/** @param {any} child */ child => !dom.checkNodeType(child, dom.ELEMENT_NODE) || !child.hasAttribute('slot')))
|
|
277
|
-
currentSlots.slice(1).map(dom.remove)
|
|
278
|
-
const nextSlot = dom.parseFragment(slotContent)
|
|
279
|
-
if (key !== 'default') {
|
|
280
|
-
array.from(nextSlot.children).forEach(c => c.setAttribute('slot', key))
|
|
281
|
-
}
|
|
282
|
-
if (currentSlots.length > 0) {
|
|
283
|
-
dom.replaceWith(currentSlots[0], nextSlot)
|
|
284
|
-
} else {
|
|
285
|
-
dom.appendChild(this, nextSlot)
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
onStateChange(state, prevState, this)
|
|
291
|
-
if (state != null) {
|
|
292
|
-
this._childStates.forEach(cnf => {
|
|
293
|
-
const d = cnf.d
|
|
294
|
-
if (d.updateState) {
|
|
295
|
-
d.updateState(cnf.s(state, this))
|
|
296
|
-
}
|
|
297
|
-
})
|
|
298
|
-
}
|
|
299
|
-
for (const key in attrs) {
|
|
300
|
-
const normalizedKey = string.fromCamelCase(key, '-')
|
|
301
|
-
if (state == null) {
|
|
302
|
-
this.removeAttribute(normalizedKey)
|
|
303
|
-
} else {
|
|
304
|
-
const stateVal = state[key]
|
|
305
|
-
const attrsType = attrs[key]
|
|
306
|
-
if (!prevState || prevState[key] !== stateVal) {
|
|
307
|
-
if (attrsType === 'bool') {
|
|
308
|
-
if (stateVal) {
|
|
309
|
-
this.setAttribute(normalizedKey, '')
|
|
310
|
-
} else {
|
|
311
|
-
this.removeAttribute(normalizedKey)
|
|
312
|
-
}
|
|
313
|
-
} else if (stateVal == null && (attrsType === 'string' || attrsType === 'number')) {
|
|
314
|
-
this.removeAttribute(normalizedKey)
|
|
315
|
-
} else {
|
|
316
|
-
this.setAttribute(normalizedKey, encodeAttrVal(stateVal, attrsType))
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
define(name, _Lib0Component)
|
|
325
|
-
// @ts-ignore
|
|
326
|
-
return _Lib0Component
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* @param {function} definer function that defines a component when executed
|
|
331
|
-
*/
|
|
332
|
-
export const createComponentDefiner = definer => {
|
|
333
|
-
/**
|
|
334
|
-
* @type {any}
|
|
335
|
-
*/
|
|
336
|
-
let defined = null
|
|
337
|
-
return () => {
|
|
338
|
-
if (!defined) {
|
|
339
|
-
defined = definer()
|
|
340
|
-
}
|
|
341
|
-
return defined
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
export const defineListComponent = createComponentDefiner(() => {
|
|
346
|
-
const ListItem = createComponent('lib0-list-item', {
|
|
347
|
-
template: '<slot name="content"></slot>',
|
|
348
|
-
slots: state => ({
|
|
349
|
-
content: `<div>${state}</div>`
|
|
350
|
-
})
|
|
351
|
-
})
|
|
352
|
-
return createComponent('lib0-list', {
|
|
353
|
-
state: { list: /** @type {Array<string>} */ ([]), Item: ListItem },
|
|
354
|
-
onStateChange: (state, prevState, component) => {
|
|
355
|
-
if (state == null) {
|
|
356
|
-
return
|
|
357
|
-
}
|
|
358
|
-
const { list = /** @type {Array<any>} */ ([]), Item = ListItem } = state
|
|
359
|
-
// @todo deep compare here by providing another parameter to simpleDiffArray
|
|
360
|
-
let { index, remove, insert } = diff.simpleDiffArray(prevState ? prevState.list : [], list, func.equalityFlat)
|
|
361
|
-
if (remove === 0 && insert.length === 0) {
|
|
362
|
-
return
|
|
363
|
-
}
|
|
364
|
-
let child = /** @type {Lib0Component<any>} */ (component.firstChild)
|
|
365
|
-
while (index-- > 0) {
|
|
366
|
-
child = /** @type {Lib0Component<any>} */ (child.nextElementSibling)
|
|
367
|
-
}
|
|
368
|
-
let insertStart = 0
|
|
369
|
-
while (insertStart < insert.length && remove-- > 0) {
|
|
370
|
-
// update existing state
|
|
371
|
-
child.setState(insert[insertStart++])
|
|
372
|
-
child = /** @type {Lib0Component<any>} */ (child.nextElementSibling)
|
|
373
|
-
}
|
|
374
|
-
while (remove-- > 0) {
|
|
375
|
-
// remove remaining
|
|
376
|
-
const prevChild = child
|
|
377
|
-
child = /** @type {Lib0Component<any>} */ (child.nextElementSibling)
|
|
378
|
-
component.removeChild(prevChild)
|
|
379
|
-
}
|
|
380
|
-
// insert remaining
|
|
381
|
-
component.insertBefore(dom.fragment(insert.slice(insertStart).map(/** @param {any} insState */ insState => {
|
|
382
|
-
const el = new Item()
|
|
383
|
-
el.setState(insState)
|
|
384
|
-
return el
|
|
385
|
-
})), child)
|
|
386
|
-
}
|
|
387
|
-
})
|
|
388
|
-
})
|
|
389
|
-
|
|
390
|
-
export const defineLazyLoadingComponent = createComponentDefiner(() => createComponent('lib0-lazy', {
|
|
391
|
-
state: /** @type {{component:null|String,import:null|function():Promise<any>,state:null|object}} */ ({
|
|
392
|
-
component: null, import: null, state: null
|
|
393
|
-
}),
|
|
394
|
-
attrs: {
|
|
395
|
-
component: 'string'
|
|
396
|
-
},
|
|
397
|
-
onStateChange: ({ component, state, import: getImport }, prevState, componentEl) => {
|
|
398
|
-
if (component !== null) {
|
|
399
|
-
if (getImport) {
|
|
400
|
-
getImport()
|
|
401
|
-
}
|
|
402
|
-
if (!prevState || component !== prevState.component) {
|
|
403
|
-
const el = /** @type {any} */ (dom.createElement(component))
|
|
404
|
-
componentEl.innerHTML = ''
|
|
405
|
-
componentEl.insertBefore(el, null)
|
|
406
|
-
}
|
|
407
|
-
const el = /** @type {any} */ (componentEl.firstElementChild)
|
|
408
|
-
// @todo generalize setting state and check if setState is defined
|
|
409
|
-
if (el.setState) {
|
|
410
|
-
el.setState(state)
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
}))
|