@record-evolution/widget-form 1.0.32 → 1.0.34
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/widget-form.d.ts +9 -0
- package/dist/widget-form.js +562 -511
- package/dist/widget-form.js.map +1 -1
- package/package.json +1 -1
- package/src/widget-form.ts +104 -12
package/src/widget-form.ts
CHANGED
|
@@ -23,6 +23,84 @@ type Theme = {
|
|
|
23
23
|
theme_name: string
|
|
24
24
|
theme_object: any
|
|
25
25
|
}
|
|
26
|
+
|
|
27
|
+
// The HTML `pattern` attribute is compiled with the RegExp `v` flag, which
|
|
28
|
+
// reserves these characters inside a character class: they have to be escaped
|
|
29
|
+
// even where the older `u` flag accepted them bare.
|
|
30
|
+
const V_RESERVED_IN_CLASS = new Set(['(', ')', '[', '{', '}', '/', '|'])
|
|
31
|
+
|
|
32
|
+
// Escape the characters a `v`-flag character class reserves but that were
|
|
33
|
+
// plainly meant literally, so a regex written against the older semantics —
|
|
34
|
+
// `^[a-zA-Z0-9 _-]+$` and friends — keeps working. A hyphen is left alone
|
|
35
|
+
// where it sits between two class atoms, because there it is a range operator.
|
|
36
|
+
const escapeForVFlag = (pattern: string): string => {
|
|
37
|
+
let out = ''
|
|
38
|
+
let inClass = false
|
|
39
|
+
// Index of the first atom of the current class, i.e. past `[` and any `^`.
|
|
40
|
+
let classContentStart = 0
|
|
41
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
42
|
+
const char = pattern[i]
|
|
43
|
+
if (char === '\\') {
|
|
44
|
+
out += char + (pattern[i + 1] ?? '')
|
|
45
|
+
i++
|
|
46
|
+
} else if (!inClass) {
|
|
47
|
+
if (char === '[') {
|
|
48
|
+
inClass = true
|
|
49
|
+
classContentStart = pattern[i + 1] === '^' ? i + 2 : i + 1
|
|
50
|
+
}
|
|
51
|
+
out += char
|
|
52
|
+
} else if (char === ']') {
|
|
53
|
+
inClass = false
|
|
54
|
+
out += char
|
|
55
|
+
} else if (char === '-') {
|
|
56
|
+
const isRange = i > classContentStart && i < pattern.length - 1 && pattern[i + 1] !== ']'
|
|
57
|
+
out += isRange ? char : '\\-'
|
|
58
|
+
} else {
|
|
59
|
+
out += V_RESERVED_IN_CLASS.has(char) ? '\\' + char : char
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return out
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const compilesAsPattern = (pattern: string): boolean => {
|
|
66
|
+
try {
|
|
67
|
+
// Same shape the HTML spec compiles the `pattern` attribute with.
|
|
68
|
+
new RegExp('^(?:' + pattern + ')$', 'v')
|
|
69
|
+
return true
|
|
70
|
+
} catch {
|
|
71
|
+
return false
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const patternCache = new Map<string, string>()
|
|
76
|
+
|
|
77
|
+
// A regex the browser cannot compile makes the text field throw a SyntaxError
|
|
78
|
+
// on every validity read, which kills validation reporting and form submission
|
|
79
|
+
// for the whole form. Repair what can be repaired, and drop the rest so a
|
|
80
|
+
// mis-escaped config costs one field's validation instead of the form.
|
|
81
|
+
const browserSafePattern = (raw?: string | null): string => {
|
|
82
|
+
const pattern = raw?.trim()
|
|
83
|
+
if (!pattern) return ''
|
|
84
|
+
const cached = patternCache.get(pattern)
|
|
85
|
+
if (cached !== undefined) return cached
|
|
86
|
+
|
|
87
|
+
let safe = ''
|
|
88
|
+
if (compilesAsPattern(pattern)) {
|
|
89
|
+
safe = pattern
|
|
90
|
+
} else {
|
|
91
|
+
const escaped = escapeForVFlag(pattern)
|
|
92
|
+
if (compilesAsPattern(escaped)) {
|
|
93
|
+
safe = escaped
|
|
94
|
+
} else {
|
|
95
|
+
console.warn(
|
|
96
|
+
`widget-form: ignoring validation regex "${pattern}" — it is not a valid regular expression under the "v" flag the HTML pattern attribute uses.`
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
patternCache.set(pattern, safe)
|
|
101
|
+
return safe
|
|
102
|
+
}
|
|
103
|
+
|
|
26
104
|
@customElement('widget-form-versionplaceholder')
|
|
27
105
|
export class WidgetForm extends LitElement {
|
|
28
106
|
@property({ type: Object })
|
|
@@ -100,10 +178,8 @@ export class WidgetForm extends LitElement {
|
|
|
100
178
|
}
|
|
101
179
|
|
|
102
180
|
registerTheme(theme?: Theme) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
this.themeBgColor = cssBgColor || this.theme?.theme_object?.backgroundColor
|
|
106
|
-
this.themeTitleColor = cssTextColor || this.theme?.theme_object?.title?.textStyle?.color
|
|
181
|
+
this.themeBgColor = `var(--re-tile-background-color, ${this.theme?.theme_object?.backgroundColor || 'transparent'})`
|
|
182
|
+
this.themeTitleColor = `var(--re-text-color, ${this.theme?.theme_object?.title?.textStyle?.color || 'inherit'})`
|
|
107
183
|
}
|
|
108
184
|
|
|
109
185
|
openFormDialog() {
|
|
@@ -268,7 +344,7 @@ export class WidgetForm extends LitElement {
|
|
|
268
344
|
.type="${field.type === 'numberfield' ? 'number' : 'text'}"
|
|
269
345
|
.value="${field.preFilledValue ?? ''}"
|
|
270
346
|
.placeholder="${field.defaultValue ?? ''}"
|
|
271
|
-
.pattern="${field.validation
|
|
347
|
+
.pattern="${browserSafePattern(field.validation)}"
|
|
272
348
|
supporting-text=${field.description ?? ''}
|
|
273
349
|
validation-message="${field.validationMessage ?? 'Invalid input'}"
|
|
274
350
|
?required=${field.required && !field.defaultValue && !field.preFilledValue}
|
|
@@ -578,16 +654,32 @@ export class WidgetForm extends LitElement {
|
|
|
578
654
|
}
|
|
579
655
|
`
|
|
580
656
|
|
|
657
|
+
/**
|
|
658
|
+
* Resolved background colour, for the alpha-stripping below.
|
|
659
|
+
*
|
|
660
|
+
* themeBgColor holds a var() chain so the tile tracks the host's
|
|
661
|
+
* --re-tile-background-color live, but a chain cannot be picked apart in
|
|
662
|
+
* JS. The opaque variant therefore resolves the colour here, at render
|
|
663
|
+
* time, instead of reading a value cached at the last theme change.
|
|
664
|
+
*/
|
|
665
|
+
private resolvedBgColor(): string | undefined {
|
|
666
|
+
return (
|
|
667
|
+
getComputedStyle(this).getPropertyValue('--re-tile-background-color').trim() ||
|
|
668
|
+
this.theme?.theme_object?.backgroundColor
|
|
669
|
+
)
|
|
670
|
+
}
|
|
671
|
+
|
|
581
672
|
render() {
|
|
582
673
|
const fontColor = this.themeTitleColor
|
|
583
674
|
const bgColor = this.themeBgColor
|
|
584
|
-
const
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
675
|
+
const opaqueSource = this.resolvedBgColor()
|
|
676
|
+
const bgColorOpaque = opaqueSource?.startsWith('rgba')
|
|
677
|
+
? opaqueSource.replace(/rgba\(([^)]+),\s*[\d.]+\)/, 'rgb($1)')
|
|
678
|
+
: opaqueSource?.startsWith('#') && opaqueSource.length === 9
|
|
679
|
+
? opaqueSource.substring(0, 7) // #RRGGBBAA -> #RRGGBB
|
|
680
|
+
: opaqueSource?.startsWith('#') && opaqueSource.length === 5
|
|
681
|
+
? opaqueSource.substring(0, 4) // #RGBA -> #RGB
|
|
682
|
+
: opaqueSource
|
|
591
683
|
return html`
|
|
592
684
|
<style>
|
|
593
685
|
:host {
|