@symbo.ls/utils 3.2.3 → 3.14.1
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/LICENSE +25 -12
- package/README.md +106 -0
- package/array.js +162 -0
- package/assets.js +117 -0
- package/cache.js +7 -0
- package/cdn.js +83 -0
- package/component.js +25 -0
- package/cookie.js +78 -0
- package/{src/detectHeight.js → detectHeight.js} +3 -3
- package/dist/cjs/detectHeight.js +10 -21
- package/dist/cjs/domqlv3hacks.js +3845 -0
- package/dist/cjs/index.js +3910 -251
- package/dist/cjs/scaling.js +3 -149
- package/element.js +148 -0
- package/env.js +15 -0
- package/events.js +19 -0
- package/extends.js +519 -0
- package/function.js +145 -0
- package/globals.js +8 -0
- package/if.js +30 -0
- package/index.js +42 -0
- package/key.js +20 -0
- package/keys.js +176 -0
- package/log.js +12 -0
- package/metadata.js +392 -0
- package/methods.js +684 -0
- package/node.js +37 -0
- package/object.js +837 -0
- package/package.json +38 -22
- package/projectKeys.js +30 -0
- package/props.js +327 -0
- package/{src/scaling.js → scaling.js} +1 -1
- package/scope.js +32 -0
- package/sharedLibraries.js +184 -0
- package/state.js +203 -0
- package/{src/index.js → string-extra.js} +11 -17
- package/string.js +206 -0
- package/tags.js +176 -0
- package/triggerEvent.js +102 -0
- package/types.js +55 -0
- package/update.js +27 -0
- /package/{src/browser.js → browser.js} +0 -0
- /package/{src/date.js → date.js} +0 -0
- /package/{src/fibonacci.js → fibonacci.js} +0 -0
- /package/{src/files.js → files.js} +0 -0
- /package/{src/load.js → load.js} +0 -0
- /package/{src/style.js → style.js} +0 -0
package/LICENSE
CHANGED
|
@@ -1,21 +1,34 @@
|
|
|
1
|
-
|
|
1
|
+
Creative Commons Attribution-NonCommercial 4.0 International License
|
|
2
2
|
|
|
3
3
|
Copyright (c) 2023 symbo.ls
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
5
|
+
This work is licensed under the Creative Commons Attribution-NonCommercial
|
|
6
|
+
4.0 International License. To view a copy of this license, visit
|
|
7
|
+
https://creativecommons.org/licenses/by-nc/4.0/ or send a letter to
|
|
8
|
+
Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
copies or substantial portions of the Software.
|
|
10
|
+
You are free to:
|
|
14
11
|
|
|
15
|
-
|
|
12
|
+
Share — copy and redistribute the material in any medium or format
|
|
13
|
+
Adapt — remix, transform, and build upon the material
|
|
14
|
+
|
|
15
|
+
Under the following terms:
|
|
16
|
+
|
|
17
|
+
Attribution — You must give appropriate credit, provide a link to the
|
|
18
|
+
license, and indicate if changes were made. You may do so in any
|
|
19
|
+
reasonable manner, but not in any way that suggests the licensor endorses
|
|
20
|
+
you or your use.
|
|
21
|
+
|
|
22
|
+
NonCommercial — You may not use the material for commercial purposes.
|
|
23
|
+
|
|
24
|
+
No additional restrictions — You may not apply legal terms or
|
|
25
|
+
technological measures that legally restrict others from doing anything
|
|
26
|
+
the license permits.
|
|
27
|
+
|
|
28
|
+
THE WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
29
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
30
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
31
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
32
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE
|
|
21
|
-
|
|
33
|
+
OUT OF OR IN CONNECTION WITH THE WORK OR THE USE OR OTHER DEALINGS IN THE
|
|
34
|
+
WORK.
|
package/README.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# @domql/utils
|
|
2
|
+
|
|
3
|
+
Core utility functions for the DOMQL element system.
|
|
4
|
+
|
|
5
|
+
## Propertization (`props.js`)
|
|
6
|
+
|
|
7
|
+
The propertization system normalizes element definitions by classifying keys into child elements, framework keys, and CSS/design-token properties.
|
|
8
|
+
|
|
9
|
+
In v3.14, properties go directly on the element (no `props:` wrapper needed). The propertization system operates internally to route properties to the atomic CSS engine (`@symbo.ls/css`).
|
|
10
|
+
|
|
11
|
+
### Two-phase process
|
|
12
|
+
|
|
13
|
+
1. **`pickupPropsFromElement`** — Scans element root keys and classifies non-element, non-builtin keys for CSS processing
|
|
14
|
+
2. **`pickupElementFromProps`** — Scans classified keys and moves element-like or builtin keys back to the element root
|
|
15
|
+
|
|
16
|
+
### Key classification rules
|
|
17
|
+
|
|
18
|
+
In v3.14, properties go directly on the element (no `props:` wrapper needed). The propertization system still operates internally for the CSS engine.
|
|
19
|
+
|
|
20
|
+
| Pattern | Classification | Example |
|
|
21
|
+
|---------|---------------|---------|
|
|
22
|
+
| Starts with uppercase | Child element | `Header`, `Button`, `Nav` |
|
|
23
|
+
| Numeric key | Child element | `0`, `1`, `2` |
|
|
24
|
+
| In `DOMQ_PROPERTIES` | Framework builtin | `tag`, `extends`, `state` |
|
|
25
|
+
| In `CSS_SELECTOR_PREFIXES` | CSS-in-props | `:hover`, `@mobileS`, `$isActive` |
|
|
26
|
+
| Has define handler | Define key (stays at root) | `$router`, deprecated: `$propsCollection` |
|
|
27
|
+
| `childProps` | Always in props | `childProps` |
|
|
28
|
+
| `onXxx` + function | Event/lifecycle handler | `onClick`, `onSubmit`, `onInit`, `onRender` |
|
|
29
|
+
| Everything else | CSS/design token prop | `padding`, `theme`, `color` |
|
|
30
|
+
|
|
31
|
+
### CSS_SELECTOR_PREFIXES
|
|
32
|
+
|
|
33
|
+
```javascript
|
|
34
|
+
const CSS_SELECTOR_PREFIXES = new Set([
|
|
35
|
+
':', '@', '[', '*', '+', '~', '&', '>', '$', '-', '.', '!'
|
|
36
|
+
])
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
These single-character prefixes identify keys that should be processed by `css-in-props` via `transformersByPrefix`. When a key starts with one of these characters, it gets moved into `props`.
|
|
40
|
+
|
|
41
|
+
### Define-awareness
|
|
42
|
+
|
|
43
|
+
The `$` prefix is shared between css-in-props conditionals (`$isActive`) and define handlers (built-in `$router`). The propertization checks for define handlers before applying prefix rules to ensure define keys stay at the element root.
|
|
44
|
+
|
|
45
|
+
### childProps handling
|
|
46
|
+
|
|
47
|
+
`childProps` is a framework property that configures child element properties. Its value may contain uppercase keys that look like child elements but are not:
|
|
48
|
+
|
|
49
|
+
```javascript
|
|
50
|
+
// childProps: { Icon: { name: 'star' }, Hgroup: { ... } }
|
|
51
|
+
// The uppercase keys inside are NOT child elements — they target named children
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### ignoreChildProps
|
|
55
|
+
|
|
56
|
+
When set on `element.props`, prevents the element from inheriting `childProps` from its parent via `inheritParentProps`. Used by fragment elements to avoid double-application of childProps (since fragments explicitly forward childProps to their children).
|
|
57
|
+
|
|
58
|
+
## Scope (`scope.js`)
|
|
59
|
+
|
|
60
|
+
### `createScope(element, parent)`
|
|
61
|
+
|
|
62
|
+
Creates the scope for an element with prototype-chain inheritance:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
el.scope → parent.scope → grandparent.scope → ... → root.scope → context.globalScope
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Behavior:**
|
|
69
|
+
- **No own scope** — element inherits `parent.scope` directly (same reference)
|
|
70
|
+
- **Own scope defined** (`scope: { myVar: 1 }`) — prototype is set to parent's scope, chaining up to `globalScope`
|
|
71
|
+
- **No parent/root scope** — new scope created with `Object.create(context.globalScope)`
|
|
72
|
+
- **No context** — plain `{}`
|
|
73
|
+
|
|
74
|
+
### `globalScope`
|
|
75
|
+
|
|
76
|
+
Automatically initialized as `context.globalScope = {}` if context exists. Sits at the bottom of every scope prototype chain, making its properties accessible from any element via `el.scope.X`.
|
|
77
|
+
|
|
78
|
+
```js
|
|
79
|
+
// In context or set by the serialization pipeline:
|
|
80
|
+
context.globalScope = {
|
|
81
|
+
API_URL: 'https://api.example.com',
|
|
82
|
+
helpers: { capitalize: (s) => s[0].toUpperCase() + s.slice(1) }
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Accessible from any element — no import needed:
|
|
86
|
+
onClick: (e, el) => fetch(el.scope.API_URL)
|
|
87
|
+
|
|
88
|
+
// Also directly accessible:
|
|
89
|
+
el.context.globalScope.API_URL
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
When a component defines its own scope, properties shadow parent/global values but the chain remains walkable:
|
|
93
|
+
|
|
94
|
+
```js
|
|
95
|
+
// Parent: scope = { theme: 'dark' }
|
|
96
|
+
// Child: scope = { count: 0 }
|
|
97
|
+
// el.scope.count → 0 (own)
|
|
98
|
+
// el.scope.theme → 'dark' (parent, via prototype)
|
|
99
|
+
// el.scope.API_URL → '...' (globalScope, via prototype chain)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Key Sets (`keys.js`)
|
|
103
|
+
|
|
104
|
+
- **`DOMQ_PROPERTIES`** — Framework-level keys that stay at element root
|
|
105
|
+
- **`PROPS_METHODS`** — Keys on the props prototype (update, __element)
|
|
106
|
+
- **`STATE_METHODS`** — State management methods (update, parse, set, toggle, etc.)
|
package/array.js
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
import { deepClone, deepMerge } from './object.js'
|
|
4
|
+
import { isArray, isNumber, isString } from './types.js'
|
|
5
|
+
|
|
6
|
+
export const arrayContainsOtherArray = (arr1, arr2) => {
|
|
7
|
+
return arr2.every(val => arr1.includes(val))
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const getFrequencyInArray = (arr, value) => {
|
|
11
|
+
let count = 0
|
|
12
|
+
for (let i = 0; i < arr.length; i++) {
|
|
13
|
+
if (arr[i] === value) count++
|
|
14
|
+
}
|
|
15
|
+
return count
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const removeFromArray = (arr, index) => {
|
|
19
|
+
if (isString(index)) index = parseInt(index)
|
|
20
|
+
if (isNumber(index)) {
|
|
21
|
+
if (index < 0 || index >= arr.length || isNaN(index)) {
|
|
22
|
+
throw new Error('Invalid index')
|
|
23
|
+
}
|
|
24
|
+
arr.splice(index, 1)
|
|
25
|
+
} else {
|
|
26
|
+
throw new Error('Invalid index')
|
|
27
|
+
}
|
|
28
|
+
return arr
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const swapItemsInArray = (arr, i, j) => {
|
|
32
|
+
if (i < 0 || j < 0 || i >= arr.length || j >= arr.length) return
|
|
33
|
+
;[arr[i], arr[j]] = [arr[j], arr[i]]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const joinArrays = (...arrays) => {
|
|
37
|
+
return [].concat(...arrays)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Merges array extendtypes
|
|
42
|
+
*/
|
|
43
|
+
export const unstackArrayOfObjects = (arr, exclude = []) => {
|
|
44
|
+
return arr.reduce(
|
|
45
|
+
(a, c) => deepMerge(a, deepClone(c, { exclude }), exclude),
|
|
46
|
+
{}
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const cutArrayBeforeValue = (arr, value) => {
|
|
51
|
+
const index = arr.indexOf(value)
|
|
52
|
+
if (index !== -1) {
|
|
53
|
+
return arr.slice(0, index)
|
|
54
|
+
}
|
|
55
|
+
return arr
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export const cutArrayAfterValue = (arr, value) => {
|
|
59
|
+
if (!isArray(arr)) return
|
|
60
|
+
const index = arr.indexOf(value)
|
|
61
|
+
if (index !== -1) {
|
|
62
|
+
return arr.slice(index + 1)
|
|
63
|
+
}
|
|
64
|
+
return arr
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const removeValueFromArray = (arr, value) => {
|
|
68
|
+
const index = arr.indexOf(value)
|
|
69
|
+
if (index > -1) {
|
|
70
|
+
const newArray = [...arr]
|
|
71
|
+
newArray.splice(index, 1)
|
|
72
|
+
return newArray
|
|
73
|
+
}
|
|
74
|
+
return arr
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const removeValueFromArrayAll = (arr, value) => {
|
|
78
|
+
return arr.filter(item => item !== value)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export const addItemAfterEveryElement = (array, item) => {
|
|
82
|
+
// Create a new array to hold the result
|
|
83
|
+
const result = []
|
|
84
|
+
|
|
85
|
+
// Loop through the input array
|
|
86
|
+
for (let i = 0; i < array.length; i++) {
|
|
87
|
+
// Add the current element to the result array
|
|
88
|
+
result.push(array[i])
|
|
89
|
+
|
|
90
|
+
// If it's not the last element, add the item
|
|
91
|
+
if (i < array.length - 1) {
|
|
92
|
+
result.push(item)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return result
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export const reorderArrayByValues = (array, valueToMove, insertBeforeValue) => {
|
|
100
|
+
const newArray = [...array] // Create a copy of the original array
|
|
101
|
+
const indexToMove = newArray.indexOf(valueToMove) // Find the index of the value to move
|
|
102
|
+
const indexToInsertBefore = newArray.indexOf(insertBeforeValue) // Find the index to insert before
|
|
103
|
+
if (indexToMove !== -1 && indexToInsertBefore !== -1) {
|
|
104
|
+
const removedItem = newArray.splice(indexToMove, 1)[0] // Remove the item to move
|
|
105
|
+
const insertIndex =
|
|
106
|
+
indexToInsertBefore < indexToMove
|
|
107
|
+
? indexToInsertBefore
|
|
108
|
+
: indexToInsertBefore + 1 // Adjust insert index
|
|
109
|
+
newArray.splice(insertIndex, 0, removedItem) // Insert the removed item before the specified value
|
|
110
|
+
}
|
|
111
|
+
return newArray
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export const arraysEqual = (arr1, arr2) => {
|
|
115
|
+
if (arr1.length !== arr2.length) {
|
|
116
|
+
return false
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
for (let i = 0; i < arr1.length; i++) {
|
|
120
|
+
if (arr1[i] !== arr2[i]) {
|
|
121
|
+
return false
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return true
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Using filter and includes
|
|
129
|
+
export const filterArrays = (sourceArr, excludeArr) => {
|
|
130
|
+
return sourceArr.filter(item => !excludeArr.includes(item))
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Using Set for better performance with large arrays
|
|
134
|
+
export const filterArraysFast = (sourceArr, excludeArr) => {
|
|
135
|
+
const excludeSet = new Set(excludeArr)
|
|
136
|
+
return sourceArr.filter(item => !excludeSet.has(item))
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export const checkIfStringIsInArray = (string, arr) => {
|
|
140
|
+
if (!string) return 0
|
|
141
|
+
let count = 0
|
|
142
|
+
for (let i = 0; i < arr.length; i++) {
|
|
143
|
+
if (string.includes(arr[i])) count++
|
|
144
|
+
}
|
|
145
|
+
return count
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export const removeDuplicatesInArray = arr => {
|
|
149
|
+
if (!isArray(arr)) return arr
|
|
150
|
+
return [...new Set(arr)]
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export const addProtoToArray = (state, proto) => {
|
|
154
|
+
for (const key in proto) {
|
|
155
|
+
Object.defineProperty(state, key, {
|
|
156
|
+
value: proto[key],
|
|
157
|
+
enumerable: false, // Set this to true if you want the method to appear in for...in loops
|
|
158
|
+
configurable: true, // Set this to true if you want to allow redefining/removing the property later
|
|
159
|
+
writable: true // Set this to true if you want to allow changing the function later
|
|
160
|
+
})
|
|
161
|
+
}
|
|
162
|
+
}
|
package/assets.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Asset resolution + variant emitters shared across media components
|
|
5
|
+
* (Img, Video, Audio, AssetPicture, …) and any plugin that reads from
|
|
6
|
+
* `context.assets` / `context.files`.
|
|
7
|
+
*
|
|
8
|
+
* Components don't care WHERE an asset lives — runner-discovered file,
|
|
9
|
+
* cloud-uploaded entry, or hand-authored URL — they call `resolveAsset`
|
|
10
|
+
* with the element and get back a normalized shape (or null).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Look up `el.src` in `el.context.assets`, falling back to
|
|
15
|
+
* `el.context.files`. Normalizes the two source-side shapes:
|
|
16
|
+
*
|
|
17
|
+
* - **String URL** — cloud-resolved or hand-authored bare URLs.
|
|
18
|
+
* Returns `{ src, type: null, variants: null }`. Components emit
|
|
19
|
+
* a single-source tag with no `<source>` chain and no `srcset`.
|
|
20
|
+
*
|
|
21
|
+
* - **Manifest object** — `{ src, type, category, variants[] }`.
|
|
22
|
+
* Shape produced by the runner's filename-based asset walker
|
|
23
|
+
* (and any cloud provider that exposes variant metadata). Returned
|
|
24
|
+
* verbatim.
|
|
25
|
+
*
|
|
26
|
+
* Anything else (object without `.src`, missing key, non-string `el.src`)
|
|
27
|
+
* returns null. Components fall through to plain markup using `el.src`.
|
|
28
|
+
*/
|
|
29
|
+
export const resolveAsset = (el) => {
|
|
30
|
+
const key = el && el.src
|
|
31
|
+
if (!key || typeof key !== 'string') return null
|
|
32
|
+
const ctx = el.context
|
|
33
|
+
if (!ctx) return null
|
|
34
|
+
const found = (ctx.assets && ctx.assets[key]) || (ctx.files && ctx.files[key])
|
|
35
|
+
if (!found) return null
|
|
36
|
+
if (typeof found === 'string') {
|
|
37
|
+
return { src: found, type: null, variants: null }
|
|
38
|
+
}
|
|
39
|
+
if (typeof found === 'object' && typeof found.src === 'string') {
|
|
40
|
+
return found
|
|
41
|
+
}
|
|
42
|
+
return null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Render an `srcset` value from a list of variants.
|
|
47
|
+
*
|
|
48
|
+
* Width descriptors take precedence over scale when present. Bare 1x
|
|
49
|
+
* entries are kept or dropped based on context:
|
|
50
|
+
*
|
|
51
|
+
* - `<source srcset>` (`keepOneX: true`, default): bare 1x URLs are
|
|
52
|
+
* required because `<source>` has no `src` fallback — without a 1x
|
|
53
|
+
* entry the source can't match default-DPR clients.
|
|
54
|
+
* - `<img srcset>` (`keepOneX: false`): bare 1x URLs are redundant
|
|
55
|
+
* because the `src` attribute already covers them.
|
|
56
|
+
*/
|
|
57
|
+
export const srcsetFor = (variants, { keepOneX = true } = {}) => {
|
|
58
|
+
if (!Array.isArray(variants) || !variants.length) return ''
|
|
59
|
+
const usingWidth = variants.some((v) => typeof v.width === 'number')
|
|
60
|
+
const parts = []
|
|
61
|
+
for (const v of variants) {
|
|
62
|
+
if (usingWidth && typeof v.width === 'number') {
|
|
63
|
+
parts.push(`${v.src} ${v.width}w`)
|
|
64
|
+
} else if (!usingWidth) {
|
|
65
|
+
if (v.scale === 1 && keepOneX) parts.push(v.src)
|
|
66
|
+
else if (v.scale && v.scale !== 1) parts.push(`${v.src} ${v.scale}x`)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return parts.join(', ')
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Same-format srcset for an `<img>` tag — filtered to the asset's
|
|
74
|
+
* primary format and with `keepOneX: false`. Used by `<Img>`.
|
|
75
|
+
*/
|
|
76
|
+
export const buildImgSrcset = (asset) => {
|
|
77
|
+
if (!asset || !Array.isArray(asset.variants)) return ''
|
|
78
|
+
const sameFormat = asset.variants.filter((v) => v.format === asset.type)
|
|
79
|
+
return srcsetFor(sameFormat, { keepOneX: false })
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Build the children of an `<picture>` (or any format-negotiating tag)
|
|
84
|
+
* from an asset manifest: one `<source type="…" srcset="…">` per non-
|
|
85
|
+
* primary format, plus a trailing `<img>` for the primary format.
|
|
86
|
+
*
|
|
87
|
+
* Returns a list of DOMQL element configs. When the asset has no
|
|
88
|
+
* variants array (cloud-string URL or single-source entry), returns
|
|
89
|
+
* just the `<img>`.
|
|
90
|
+
*/
|
|
91
|
+
export const buildSourcesAndImg = (asset, alt) => {
|
|
92
|
+
if (!asset || typeof asset !== 'object' || typeof asset.src !== 'string') return []
|
|
93
|
+
if (!Array.isArray(asset.variants) || !asset.variants.length) {
|
|
94
|
+
return [{ tag: 'img', attr: { src: asset.src, alt: alt || '' } }]
|
|
95
|
+
}
|
|
96
|
+
// Group by format. Format that matches `asset.type` is the fallback
|
|
97
|
+
// and gets emitted as the trailing `<img>` (browsers fall through to
|
|
98
|
+
// it when no `<source>` matches). Other formats become `<source>`.
|
|
99
|
+
const byFormat = new Map()
|
|
100
|
+
for (const v of asset.variants) {
|
|
101
|
+
if (!byFormat.has(v.format)) byFormat.set(v.format, [])
|
|
102
|
+
byFormat.get(v.format).push(v)
|
|
103
|
+
}
|
|
104
|
+
const sources = []
|
|
105
|
+
for (const [format, list] of byFormat) {
|
|
106
|
+
if (format === asset.type) continue
|
|
107
|
+
sources.push({
|
|
108
|
+
tag: 'source',
|
|
109
|
+
attr: { type: format, srcset: srcsetFor(list, { keepOneX: true }) }
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
const sameFormat = byFormat.get(asset.type) || []
|
|
113
|
+
const fallbackSrcset = srcsetFor(sameFormat, { keepOneX: false })
|
|
114
|
+
const imgAttr = { src: asset.src, alt: alt || '' }
|
|
115
|
+
if (fallbackSrcset) imgAttr.srcset = fallbackSrcset
|
|
116
|
+
return [...sources, { tag: 'img', attr: imgAttr }]
|
|
117
|
+
}
|
package/cache.js
ADDED
package/cdn.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
function onlyDotsAndNumbers (str) {
|
|
4
|
+
return /^[0-9.]+$/.test(str) && str !== ''
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const CDN_PROVIDERS = {
|
|
8
|
+
skypack: {
|
|
9
|
+
url: 'https://cdn.skypack.dev',
|
|
10
|
+
formatUrl: (pkg, version) =>
|
|
11
|
+
`${CDN_PROVIDERS.skypack.url}/${pkg}${version !== 'latest' ? `@${version}` : ''}`
|
|
12
|
+
},
|
|
13
|
+
esmsh: {
|
|
14
|
+
url: 'https://esm.sh',
|
|
15
|
+
formatUrl: (pkg, version) =>
|
|
16
|
+
`${CDN_PROVIDERS.esmsh.url}/${pkg}${version !== 'latest' ? `@${version}` : ''}`
|
|
17
|
+
},
|
|
18
|
+
unpkg: {
|
|
19
|
+
url: 'https://unpkg.com',
|
|
20
|
+
formatUrl: (pkg, version) =>
|
|
21
|
+
`${CDN_PROVIDERS.unpkg.url}/${pkg}${version !== 'latest' ? `@${version}` : ''}?module`
|
|
22
|
+
},
|
|
23
|
+
jsdelivr: {
|
|
24
|
+
url: 'https://cdn.jsdelivr.net/npm',
|
|
25
|
+
formatUrl: (pkg, version) =>
|
|
26
|
+
`${CDN_PROVIDERS.jsdelivr.url}/${pkg}${version !== 'latest' ? `@${version}` : ''}/+esm`
|
|
27
|
+
},
|
|
28
|
+
symbols: {
|
|
29
|
+
url: 'https://pkg.symbo.ls',
|
|
30
|
+
formatUrl: (pkg, version) => {
|
|
31
|
+
if (pkg.split('/').length > 2 || !onlyDotsAndNumbers(version)) {
|
|
32
|
+
return `${CDN_PROVIDERS.symbols.url}/${pkg}`
|
|
33
|
+
}
|
|
34
|
+
return `${CDN_PROVIDERS.symbols.url}/${pkg}/${version}.js`
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Maps symbols.json packageManager values to CDN_PROVIDERS keys
|
|
40
|
+
export const PACKAGE_MANAGER_TO_CDN = {
|
|
41
|
+
'esm.sh': 'esmsh',
|
|
42
|
+
'unpkg': 'unpkg',
|
|
43
|
+
'skypack': 'skypack',
|
|
44
|
+
'jsdelivr': 'jsdelivr',
|
|
45
|
+
'pkg.symbo.ls': 'symbols'
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Derive the CDN provider key from a symbols config object.
|
|
50
|
+
* Returns null when packageManager is a local tool (npm/yarn/pnpm/bun).
|
|
51
|
+
*/
|
|
52
|
+
export const getCdnProviderFromConfig = (symbolsConfig = {}) => {
|
|
53
|
+
const { packageManager } = symbolsConfig
|
|
54
|
+
return PACKAGE_MANAGER_TO_CDN[packageManager] || null
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const getCDNUrl = (
|
|
58
|
+
packageName,
|
|
59
|
+
version = 'latest',
|
|
60
|
+
provider = 'esmsh'
|
|
61
|
+
) => {
|
|
62
|
+
const cdnConfig = CDN_PROVIDERS[provider] || CDN_PROVIDERS.esmsh
|
|
63
|
+
return cdnConfig.formatUrl(packageName, version)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Generate an HTML <script type="importmap"> tag from project dependencies.
|
|
68
|
+
*/
|
|
69
|
+
export const getImportMapScript = (data, defaultProvider = 'skypack') => {
|
|
70
|
+
const dependencies = data.dependencies || {}
|
|
71
|
+
const keys = Object.keys(dependencies)
|
|
72
|
+
if (!keys.length) return ''
|
|
73
|
+
|
|
74
|
+
const imports = {}
|
|
75
|
+
for (const pkgName of keys) {
|
|
76
|
+
const version = dependencies[pkgName] || 'latest'
|
|
77
|
+
imports[pkgName] = getCDNUrl(pkgName, version, defaultProvider)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return `<script type="importmap">{
|
|
81
|
+
"imports": ${JSON.stringify(imports, null, 2)}
|
|
82
|
+
}</script>`
|
|
83
|
+
}
|
package/component.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
import { createExtendsFromKeys } from './extends.js'
|
|
4
|
+
import { isString } from './types.js'
|
|
5
|
+
|
|
6
|
+
export const matchesComponentNaming = key => {
|
|
7
|
+
if (!isString(key) || !key.length) return false
|
|
8
|
+
const code = key.charCodeAt(0)
|
|
9
|
+
return code >= 65 && code <= 90 // A-Z
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function getCapitalCaseKeys (obj) {
|
|
13
|
+
return Object.keys(obj).filter(key => /^[A-Z]/.test(key))
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function getSpreadChildren (obj) {
|
|
17
|
+
return Object.keys(obj).filter(key => /^\d+$/.test(key))
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function isContextComponent (element, parent, passedKey) {
|
|
21
|
+
const { context } = parent || {}
|
|
22
|
+
const [extendsKey] = createExtendsFromKeys(passedKey)
|
|
23
|
+
const key = passedKey || extendsKey
|
|
24
|
+
return context?.components?.[key] || context?.pages?.[key]
|
|
25
|
+
}
|
package/cookie.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
import { isUndefined } from './types.js'
|
|
4
|
+
import { document } from './globals.js'
|
|
5
|
+
|
|
6
|
+
export const isMobile = (() =>
|
|
7
|
+
typeof navigator === 'undefined' ? false : /Mobi/.test(navigator.userAgent))()
|
|
8
|
+
|
|
9
|
+
export const setCookie = (cname, cvalue, exdays = 365) => {
|
|
10
|
+
if (isUndefined(document) || isUndefined(document.cookie)) return
|
|
11
|
+
const d = new Date()
|
|
12
|
+
d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000)
|
|
13
|
+
const expires = `expires=${d.toUTCString()}`
|
|
14
|
+
document.cookie = `${cname}=${cvalue};${expires};path=/`
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const getCookie = cname => {
|
|
18
|
+
if (isUndefined(document) || isUndefined(document.cookie)) return
|
|
19
|
+
const name = `${cname}=`
|
|
20
|
+
const decodedCookie = decodeURIComponent(document.cookie)
|
|
21
|
+
const ca = decodedCookie.split(';')
|
|
22
|
+
for (let i = 0; i < ca.length; i++) {
|
|
23
|
+
let c = ca[i]
|
|
24
|
+
while (c.charAt(0) === ' ') c = c.substring(1)
|
|
25
|
+
if (c.indexOf(name) === 0) return c.substring(name.length, c.length)
|
|
26
|
+
}
|
|
27
|
+
return ''
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const removeCookie = cname => {
|
|
31
|
+
if (isUndefined(document) || isUndefined(document.cookie)) return
|
|
32
|
+
document.cookie = cname + '=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Load item from the localStorage
|
|
36
|
+
*
|
|
37
|
+
* @param key -- string to identify the storage item
|
|
38
|
+
* @returns {*} -- parsed data or undefined
|
|
39
|
+
*/
|
|
40
|
+
export function getLocalStorage (key) {
|
|
41
|
+
// localStorage access throws in: Safari private mode (quota), sandboxed
|
|
42
|
+
// iframes without `allow-storage-access-by-user-activation`, Firefox with
|
|
43
|
+
// `dom.storage.enabled=false`. Wrap the access itself, not just the
|
|
44
|
+
// JSON.parse — the existing try only covered parse and let the read
|
|
45
|
+
// crash the host.
|
|
46
|
+
let item
|
|
47
|
+
try {
|
|
48
|
+
if (!window.localStorage) return undefined
|
|
49
|
+
item = window.localStorage.getItem(key)
|
|
50
|
+
} catch (e) {
|
|
51
|
+
return undefined
|
|
52
|
+
}
|
|
53
|
+
if (item === null) return undefined
|
|
54
|
+
try {
|
|
55
|
+
return JSON.parse(item)
|
|
56
|
+
} catch (e) {
|
|
57
|
+
return undefined
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Save the data to window.localStorage
|
|
63
|
+
*
|
|
64
|
+
* @param key - local storage key to save the data under
|
|
65
|
+
* @param data - the data to save
|
|
66
|
+
*/
|
|
67
|
+
export function setLocalStorage (key, data) {
|
|
68
|
+
if (data === undefined || data === null) return
|
|
69
|
+
try {
|
|
70
|
+
if (!window.localStorage) return
|
|
71
|
+
const value = typeof data === 'object' ? JSON.stringify(data) : data
|
|
72
|
+
window.localStorage.setItem(key, value)
|
|
73
|
+
} catch (e) {
|
|
74
|
+
// QuotaExceededError (Safari private + over quota), SecurityError
|
|
75
|
+
// (sandboxed iframe / disabled storage). Silent fail — caller doesn't
|
|
76
|
+
// expect an exception out of a "save best-effort" helper.
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -14,14 +14,14 @@ export const detectHeightOnInit = (element, state) => {
|
|
|
14
14
|
|
|
15
15
|
if (state.active) {
|
|
16
16
|
if (props.height === 'auto') return
|
|
17
|
-
element.
|
|
17
|
+
element.update(
|
|
18
18
|
{
|
|
19
19
|
height: state.clientHeight
|
|
20
20
|
},
|
|
21
21
|
{ preventBeforeUpdateListener: true, preventChildrenUpdate: true }
|
|
22
22
|
)
|
|
23
23
|
const setAutoTimeout = setTimeout(() => {
|
|
24
|
-
element.
|
|
24
|
+
element.update(
|
|
25
25
|
{
|
|
26
26
|
height: 'auto'
|
|
27
27
|
},
|
|
@@ -30,7 +30,7 @@ export const detectHeightOnInit = (element, state) => {
|
|
|
30
30
|
clearTimeout(setAutoTimeout)
|
|
31
31
|
}, 450)
|
|
32
32
|
} else {
|
|
33
|
-
element.
|
|
33
|
+
element.update(
|
|
34
34
|
{
|
|
35
35
|
height: '0'
|
|
36
36
|
},
|