@shelamkoff/rector 1.0.1 → 1.0.3
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 +25 -24
- package/dist/core/clipboard/Clipboard.d.ts +2 -0
- package/dist/core/clipboard/Clipboard.js +52 -24
- package/dist/core/index.js +195 -194
- package/dist/core/locale/en.d.ts +1 -0
- package/dist/core/locale/en.js +1 -0
- package/dist/core/locale/ru.d.ts +1 -0
- package/dist/core/locale/ru.js +1 -0
- package/dist/core/themes/light.css +1 -1
- package/dist/core/themes/variables.css +72 -10
- package/dist/locale/en.d.ts +41 -0
- package/dist/locale/ru.d.ts +41 -0
- package/dist/plugins/attaches/attaches.css +16 -14
- package/dist/plugins/attaches/index.js +997 -920
- package/dist/plugins/attaches/locale/en.d.ts +9 -0
- package/dist/plugins/attaches/locale/en.js +11 -2
- package/dist/plugins/attaches/locale/ru.d.ts +9 -0
- package/dist/plugins/attaches/locale/ru.js +11 -2
- package/dist/plugins/carousel/carousel.css +213 -49
- package/dist/plugins/carousel/index.js +918 -858
- package/dist/plugins/carousel/locale/en.d.ts +15 -0
- package/dist/plugins/carousel/locale/en.js +18 -3
- package/dist/plugins/carousel/locale/ru.d.ts +15 -0
- package/dist/plugins/carousel/locale/ru.js +18 -3
- package/dist/plugins/gallery/README.md +92 -92
- package/dist/plugins/gallery/README.ru.md +17 -17
- package/dist/plugins/gallery/gallery.css +44 -19
- package/dist/plugins/gallery/index.js +375 -354
- package/dist/plugins/gallery/locale/en.d.ts +8 -0
- package/dist/plugins/gallery/locale/en.js +11 -3
- package/dist/plugins/gallery/locale/ru.d.ts +8 -0
- package/dist/plugins/gallery/locale/ru.js +11 -3
- package/dist/plugins/gallery/settings.js +15 -10
- package/dist/plugins/gallery/view-empty.d.ts +2 -0
- package/dist/plugins/gallery/view-empty.js +21 -15
- package/dist/plugins/gallery/view-filled.d.ts +2 -2
- package/dist/plugins/gallery/view-filled.js +84 -72
- package/dist/plugins/image/image.css +7 -2
- package/dist/plugins/image/index.js +365 -345
- package/dist/plugins/image/locale/en.d.ts +8 -0
- package/dist/plugins/image/locale/en.js +11 -3
- package/dist/plugins/image/locale/ru.d.ts +8 -0
- package/dist/plugins/image/locale/ru.js +11 -3
- package/dist/plugins/image/view-empty.d.ts +2 -0
- package/dist/plugins/image/view-empty.js +21 -15
- package/dist/plugins/image/view-filled.d.ts +2 -2
- package/dist/plugins/image/view-filled.js +77 -72
- package/dist/plugins/shared/dropzone.d.ts +6 -0
- package/dist/plugins/shared/dropzone.js +47 -36
- package/dist/plugins/shared/layer.d.ts +13 -0
- package/dist/plugins/shared/layer.js +45 -0
- package/dist/plugins/shared/sourceEditor.css +165 -0
- package/dist/plugins/shared/sourceEditor.d.ts +97 -0
- package/dist/plugins/shared/sourceEditor.js +260 -0
- package/dist/renderer/renderers/carousel/index.js +24 -23
- package/dist/renderer/renderers/carousel/styles.css +8 -2
- package/dist/renderer/renderers/gallery/README.md +41 -41
- package/dist/renderer/renderers/gallery/README.ru.md +41 -41
- package/dist/renderer/renderers/gallery/index.js +100 -85
- package/dist/renderer/renderers/gallery/styles.css +417 -401
- package/dist/shared/galleryMasonry.d.ts +34 -0
- package/dist/shared/galleryMasonry.js +152 -0
- package/package.json +29 -28
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
.oe-source-editor {
|
|
2
|
+
position: absolute;
|
|
3
|
+
inset: 0;
|
|
4
|
+
z-index: var(--oe-plugin-dialog-z-index, 1200);
|
|
5
|
+
box-sizing: border-box;
|
|
6
|
+
display: grid;
|
|
7
|
+
place-items: center;
|
|
8
|
+
min-height: 220px;
|
|
9
|
+
padding: 16px;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.oe-source-editor--preloaded {
|
|
13
|
+
visibility: hidden;
|
|
14
|
+
opacity: 0;
|
|
15
|
+
pointer-events: none;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.oe-source-editor[data-oe-source-editor='html'] {
|
|
19
|
+
min-height: min(26rem, calc(100vh - 2rem));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.oe-source-editor__backdrop {
|
|
23
|
+
position: absolute;
|
|
24
|
+
inset: 0;
|
|
25
|
+
width: 100%;
|
|
26
|
+
height: 100%;
|
|
27
|
+
padding: 0;
|
|
28
|
+
background: var(--oe-overlay-dark, rgba(0, 0, 0, 0.7));
|
|
29
|
+
border: 0;
|
|
30
|
+
border-radius: inherit;
|
|
31
|
+
cursor: default;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.oe-source-editor__panel {
|
|
35
|
+
position: relative;
|
|
36
|
+
display: grid;
|
|
37
|
+
gap: 12px;
|
|
38
|
+
box-sizing: border-box;
|
|
39
|
+
width: min(100%, 32rem);
|
|
40
|
+
max-height: calc(100vh - 2rem);
|
|
41
|
+
padding: 16px;
|
|
42
|
+
overflow-y: auto;
|
|
43
|
+
background: var(--oe-surface-elevated, #1e1e24);
|
|
44
|
+
border: 1px solid var(--oe-border-hover, #2a2a2a);
|
|
45
|
+
border-radius: var(--oe-radius, 8px);
|
|
46
|
+
box-shadow: var(--oe-toolbar-shadow, 0 8px 24px rgba(0, 0, 0, 0.4));
|
|
47
|
+
text-align: left;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.oe-source-editor[data-oe-source-editor='url'] .oe-source-editor__panel {
|
|
51
|
+
width: min(100%, 28rem);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.oe-source-editor__title {
|
|
55
|
+
margin: 0;
|
|
56
|
+
color: var(--oe-text-1, #f5f5f5);
|
|
57
|
+
font: inherit;
|
|
58
|
+
font-size: 15px;
|
|
59
|
+
font-weight: 650;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.oe-source-editor__label {
|
|
63
|
+
display: grid;
|
|
64
|
+
gap: 8px;
|
|
65
|
+
color: var(--oe-text-2, #c4c4c4);
|
|
66
|
+
font-size: 13px;
|
|
67
|
+
font-weight: 600;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.oe-source-editor__field {
|
|
71
|
+
box-sizing: border-box;
|
|
72
|
+
width: 100%;
|
|
73
|
+
min-height: 42px;
|
|
74
|
+
padding: 10px 12px;
|
|
75
|
+
resize: vertical;
|
|
76
|
+
color: var(--oe-text-1, #f5f5f5);
|
|
77
|
+
background: var(--oe-input-bg, #2c2c33);
|
|
78
|
+
border: 1px solid var(--oe-border-hover, #2a2a2a);
|
|
79
|
+
border-radius: var(--oe-radius-sm, 4px);
|
|
80
|
+
outline: none;
|
|
81
|
+
font: inherit;
|
|
82
|
+
font-size: 14px;
|
|
83
|
+
line-height: 1.5;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.oe-source-editor textarea.oe-source-editor__field {
|
|
87
|
+
min-height: 9rem;
|
|
88
|
+
max-height: min(40vh, 20rem);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.oe-source-editor__field:hover {
|
|
92
|
+
background: var(--oe-input-bg-hover, #353540);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.oe-source-editor .oe-source-editor__field:focus,
|
|
96
|
+
.oe-source-editor .oe-source-editor__field:focus-visible {
|
|
97
|
+
border-color: var(--oe-border-hover, #2a2a2a);
|
|
98
|
+
outline: none;
|
|
99
|
+
outline-offset: 0;
|
|
100
|
+
box-shadow: none;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.oe-source-editor__field[aria-invalid='true'] {
|
|
104
|
+
border-color: var(--oe-danger, #f87171);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.oe-source-editor__error {
|
|
108
|
+
color: var(--oe-danger-light, #fca5a5);
|
|
109
|
+
font-size: 13px;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.oe-source-editor__actions {
|
|
113
|
+
display: flex;
|
|
114
|
+
justify-content: flex-end;
|
|
115
|
+
gap: 8px;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.oe-source-editor__button {
|
|
119
|
+
min-height: 36px;
|
|
120
|
+
padding: 7px 14px;
|
|
121
|
+
border: 1px solid transparent;
|
|
122
|
+
border-radius: var(--oe-radius-sm, 4px);
|
|
123
|
+
font: inherit;
|
|
124
|
+
font-size: 13px;
|
|
125
|
+
font-weight: 650;
|
|
126
|
+
cursor: pointer;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.oe-source-editor__button--secondary {
|
|
130
|
+
color: var(--oe-text-1, #f5f5f5);
|
|
131
|
+
background: var(--oe-card, #111);
|
|
132
|
+
border-color: var(--oe-border-hover, #2a2a2a);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.oe-source-editor__button--secondary:hover {
|
|
136
|
+
background: var(--oe-card-hover, #191919);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.oe-source-editor__button--primary {
|
|
140
|
+
color: var(--oe-text-on-accent, #fff);
|
|
141
|
+
background: var(--oe-accent, #4357b4);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.oe-source-editor__button--primary:hover {
|
|
145
|
+
filter: brightness(1.08);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
@media (max-width: 520px) {
|
|
149
|
+
.oe-source-editor {
|
|
150
|
+
padding: 10px;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.oe-source-editor__panel {
|
|
154
|
+
max-height: calc(100vh - 1.25rem);
|
|
155
|
+
padding: 14px;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.oe-source-editor__actions {
|
|
159
|
+
flex-direction: column-reverse;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.oe-source-editor__button {
|
|
163
|
+
width: 100%;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} SourceEditorConfig
|
|
3
|
+
* @property {HTMLElement} wrapper Plugin wrapper that owns the editor.
|
|
4
|
+
* @property {AbortSignal} signal Plugin render lifecycle signal.
|
|
5
|
+
* @property {'url' | 'html'} kind Source value kind.
|
|
6
|
+
* @property {string} title Dialog title.
|
|
7
|
+
* @property {string} label Accessible field label.
|
|
8
|
+
* @property {string} placeholder Field placeholder.
|
|
9
|
+
* @property {string} submitText Submit button label.
|
|
10
|
+
* @property {string} cancelText Cancel button label.
|
|
11
|
+
* @property {string} invalidText Validation error.
|
|
12
|
+
* @property {(value: string) => string} normalize Sanitizes and validates input.
|
|
13
|
+
* @property {(value: string) => void} onSubmit Receives the normalized value.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Build URL/HTML forms while the owning plugin view is rendered so their
|
|
17
|
+
* first style and layout pass does not happen in the user's click handler.
|
|
18
|
+
*
|
|
19
|
+
* @param {HTMLElement} wrapper Plugin wrapper that owns the editor.
|
|
20
|
+
* @param {AbortSignal} signal Plugin render lifecycle signal.
|
|
21
|
+
* @param {Array<'url' | 'html'>} kinds Forms used by the current plugin view.
|
|
22
|
+
* @returns {void}
|
|
23
|
+
*/
|
|
24
|
+
export function preloadSourceEditor(wrapper: HTMLElement, signal: AbortSignal, kinds: Array<"url" | "html">): void;
|
|
25
|
+
/**
|
|
26
|
+
* Open an editor-owned URL or HTML input surface without relying on native
|
|
27
|
+
* browser prompts.
|
|
28
|
+
*
|
|
29
|
+
* @param {SourceEditorConfig} config Source editor configuration.
|
|
30
|
+
* @returns {{ close: () => void }}
|
|
31
|
+
*/
|
|
32
|
+
export function openSourceEditor(config: SourceEditorConfig): {
|
|
33
|
+
close: () => void;
|
|
34
|
+
};
|
|
35
|
+
export type SourceEditorConfig = {
|
|
36
|
+
/**
|
|
37
|
+
* Plugin wrapper that owns the editor.
|
|
38
|
+
*/
|
|
39
|
+
wrapper: HTMLElement;
|
|
40
|
+
/**
|
|
41
|
+
* Plugin render lifecycle signal.
|
|
42
|
+
*/
|
|
43
|
+
signal: AbortSignal;
|
|
44
|
+
/**
|
|
45
|
+
* Source value kind.
|
|
46
|
+
*/
|
|
47
|
+
kind: "url" | "html";
|
|
48
|
+
/**
|
|
49
|
+
* Dialog title.
|
|
50
|
+
*/
|
|
51
|
+
title: string;
|
|
52
|
+
/**
|
|
53
|
+
* Accessible field label.
|
|
54
|
+
*/
|
|
55
|
+
label: string;
|
|
56
|
+
/**
|
|
57
|
+
* Field placeholder.
|
|
58
|
+
*/
|
|
59
|
+
placeholder: string;
|
|
60
|
+
/**
|
|
61
|
+
* Submit button label.
|
|
62
|
+
*/
|
|
63
|
+
submitText: string;
|
|
64
|
+
/**
|
|
65
|
+
* Cancel button label.
|
|
66
|
+
*/
|
|
67
|
+
cancelText: string;
|
|
68
|
+
/**
|
|
69
|
+
* Validation error.
|
|
70
|
+
*/
|
|
71
|
+
invalidText: string;
|
|
72
|
+
/**
|
|
73
|
+
* Sanitizes and validates input.
|
|
74
|
+
*/
|
|
75
|
+
normalize: (value: string) => string;
|
|
76
|
+
/**
|
|
77
|
+
* Receives the normalized value.
|
|
78
|
+
*/
|
|
79
|
+
onSubmit: (value: string) => void;
|
|
80
|
+
};
|
|
81
|
+
export type SourceEditorSurface = {
|
|
82
|
+
root: HTMLElement;
|
|
83
|
+
backdrop: HTMLButtonElement;
|
|
84
|
+
panel: HTMLFormElement;
|
|
85
|
+
title: HTMLHeadingElement;
|
|
86
|
+
labelText: HTMLSpanElement;
|
|
87
|
+
field: HTMLInputElement | HTMLTextAreaElement;
|
|
88
|
+
error: HTMLDivElement;
|
|
89
|
+
cancel: HTMLButtonElement;
|
|
90
|
+
submit: HTMLButtonElement;
|
|
91
|
+
signal: AbortSignal;
|
|
92
|
+
layer: {
|
|
93
|
+
open: () => void;
|
|
94
|
+
close: () => void;
|
|
95
|
+
};
|
|
96
|
+
destroy: () => void;
|
|
97
|
+
};
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { createPluginLayer } from './layer.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {Object} SourceEditorSurface
|
|
5
|
+
* @property {HTMLElement} root
|
|
6
|
+
* @property {HTMLButtonElement} backdrop
|
|
7
|
+
* @property {HTMLFormElement} panel
|
|
8
|
+
* @property {HTMLHeadingElement} title
|
|
9
|
+
* @property {HTMLSpanElement} labelText
|
|
10
|
+
* @property {HTMLInputElement | HTMLTextAreaElement} field
|
|
11
|
+
* @property {HTMLDivElement} error
|
|
12
|
+
* @property {HTMLButtonElement} cancel
|
|
13
|
+
* @property {HTMLButtonElement} submit
|
|
14
|
+
* @property {AbortSignal} signal
|
|
15
|
+
* @property {{ open: () => void, close: () => void }} layer
|
|
16
|
+
* @property {() => void} destroy
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** @type {WeakMap<HTMLElement, { close: () => void, surface: SourceEditorSurface }>} */
|
|
20
|
+
const activeEditors = new WeakMap()
|
|
21
|
+
|
|
22
|
+
/** @type {WeakMap<HTMLElement, Map<'url' | 'html', SourceEditorSurface>>} */
|
|
23
|
+
const surfaces = new WeakMap()
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @typedef {Object} SourceEditorConfig
|
|
27
|
+
* @property {HTMLElement} wrapper Plugin wrapper that owns the editor.
|
|
28
|
+
* @property {AbortSignal} signal Plugin render lifecycle signal.
|
|
29
|
+
* @property {'url' | 'html'} kind Source value kind.
|
|
30
|
+
* @property {string} title Dialog title.
|
|
31
|
+
* @property {string} label Accessible field label.
|
|
32
|
+
* @property {string} placeholder Field placeholder.
|
|
33
|
+
* @property {string} submitText Submit button label.
|
|
34
|
+
* @property {string} cancelText Cancel button label.
|
|
35
|
+
* @property {string} invalidText Validation error.
|
|
36
|
+
* @property {(value: string) => string} normalize Sanitizes and validates input.
|
|
37
|
+
* @property {(value: string) => void} onSubmit Receives the normalized value.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Build URL/HTML forms while the owning plugin view is rendered so their
|
|
42
|
+
* first style and layout pass does not happen in the user's click handler.
|
|
43
|
+
*
|
|
44
|
+
* @param {HTMLElement} wrapper Plugin wrapper that owns the editor.
|
|
45
|
+
* @param {AbortSignal} signal Plugin render lifecycle signal.
|
|
46
|
+
* @param {Array<'url' | 'html'>} kinds Forms used by the current plugin view.
|
|
47
|
+
* @returns {void}
|
|
48
|
+
*/
|
|
49
|
+
export function preloadSourceEditor(wrapper, signal, kinds) {
|
|
50
|
+
if (signal.aborted) return
|
|
51
|
+
for (const kind of new Set(kinds)) ensureSurface(wrapper, signal, kind)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Open an editor-owned URL or HTML input surface without relying on native
|
|
56
|
+
* browser prompts.
|
|
57
|
+
*
|
|
58
|
+
* @param {SourceEditorConfig} config Source editor configuration.
|
|
59
|
+
* @returns {{ close: () => void }}
|
|
60
|
+
*/
|
|
61
|
+
export function openSourceEditor(config) {
|
|
62
|
+
if (config.signal.aborted) return { close() {} }
|
|
63
|
+
activeEditors.get(config.wrapper)?.close()
|
|
64
|
+
|
|
65
|
+
const surface = ensureSurface(config.wrapper, config.signal, config.kind)
|
|
66
|
+
if (!surface) return { close() {} }
|
|
67
|
+
const controller = new AbortController()
|
|
68
|
+
const { root, backdrop, panel, title, labelText, field, error, cancel, submit } = surface
|
|
69
|
+
backdrop.setAttribute('aria-label', config.cancelText)
|
|
70
|
+
panel.setAttribute('aria-label', config.title)
|
|
71
|
+
title.textContent = config.title
|
|
72
|
+
labelText.textContent = config.label
|
|
73
|
+
field.value = ''
|
|
74
|
+
field.placeholder = config.placeholder
|
|
75
|
+
field.removeAttribute('aria-invalid')
|
|
76
|
+
error.textContent = ''
|
|
77
|
+
error.hidden = true
|
|
78
|
+
cancel.textContent = config.cancelText
|
|
79
|
+
submit.textContent = config.submitText
|
|
80
|
+
setSurfaceVisible(surface, true)
|
|
81
|
+
surface.layer.open()
|
|
82
|
+
|
|
83
|
+
let closed = false
|
|
84
|
+
const close = () => {
|
|
85
|
+
if (closed) return
|
|
86
|
+
closed = true
|
|
87
|
+
if (activeEditors.get(config.wrapper)?.close === close) activeEditors.delete(config.wrapper)
|
|
88
|
+
controller.abort()
|
|
89
|
+
surface.layer.close()
|
|
90
|
+
setSurfaceVisible(surface, false)
|
|
91
|
+
}
|
|
92
|
+
activeEditors.set(config.wrapper, { close, surface })
|
|
93
|
+
|
|
94
|
+
config.signal.addEventListener('abort', close, { once: true, signal: controller.signal })
|
|
95
|
+
backdrop.addEventListener('click', close, { signal: controller.signal })
|
|
96
|
+
cancel.addEventListener('click', close, { signal: controller.signal })
|
|
97
|
+
root.addEventListener('keydown', (event) => {
|
|
98
|
+
if (event.key === 'Escape') {
|
|
99
|
+
event.preventDefault()
|
|
100
|
+
event.stopPropagation()
|
|
101
|
+
close()
|
|
102
|
+
}
|
|
103
|
+
}, { signal: controller.signal })
|
|
104
|
+
panel.addEventListener('submit', (event) => {
|
|
105
|
+
event.preventDefault()
|
|
106
|
+
let value = ''
|
|
107
|
+
try {
|
|
108
|
+
value = config.normalize(field.value)
|
|
109
|
+
} catch {
|
|
110
|
+
value = ''
|
|
111
|
+
}
|
|
112
|
+
if (!value) {
|
|
113
|
+
error.textContent = config.invalidText
|
|
114
|
+
error.hidden = false
|
|
115
|
+
field.setAttribute('aria-invalid', 'true')
|
|
116
|
+
field.focus()
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
close()
|
|
120
|
+
config.onSubmit(value)
|
|
121
|
+
}, { signal: controller.signal })
|
|
122
|
+
field.addEventListener('input', () => {
|
|
123
|
+
error.hidden = true
|
|
124
|
+
field.removeAttribute('aria-invalid')
|
|
125
|
+
}, { signal: controller.signal })
|
|
126
|
+
|
|
127
|
+
queueMicrotask(() => {
|
|
128
|
+
if (!closed) field.focus({ preventScroll: true })
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
return { close }
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* @param {HTMLElement} wrapper
|
|
136
|
+
* @param {AbortSignal} signal
|
|
137
|
+
* @param {'url' | 'html'} kind
|
|
138
|
+
* @returns {SourceEditorSurface | null}
|
|
139
|
+
*/
|
|
140
|
+
function ensureSurface(wrapper, signal, kind) {
|
|
141
|
+
if (signal.aborted) return null
|
|
142
|
+
|
|
143
|
+
let owned = surfaces.get(wrapper)
|
|
144
|
+
if (!owned) {
|
|
145
|
+
owned = new Map()
|
|
146
|
+
surfaces.set(wrapper, owned)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const cached = owned.get(kind)
|
|
150
|
+
if (cached?.signal === signal && cached.root.isConnected) return cached
|
|
151
|
+
cached?.destroy()
|
|
152
|
+
|
|
153
|
+
const root = document.createElement('div')
|
|
154
|
+
root.className = 'oe-source-editor oe-source-editor--preloaded'
|
|
155
|
+
root.dataset.oeSourceEditor = kind
|
|
156
|
+
root.setAttribute('aria-hidden', 'true')
|
|
157
|
+
root.inert = true
|
|
158
|
+
|
|
159
|
+
const backdrop = document.createElement('button')
|
|
160
|
+
backdrop.type = 'button'
|
|
161
|
+
backdrop.className = 'oe-source-editor__backdrop'
|
|
162
|
+
backdrop.setAttribute('aria-label', 'Cancel')
|
|
163
|
+
backdrop.tabIndex = -1
|
|
164
|
+
|
|
165
|
+
const panel = document.createElement('form')
|
|
166
|
+
panel.className = 'oe-source-editor__panel'
|
|
167
|
+
panel.noValidate = true
|
|
168
|
+
panel.setAttribute('role', 'dialog')
|
|
169
|
+
panel.setAttribute('aria-modal', 'true')
|
|
170
|
+
panel.setAttribute('aria-label', 'Source')
|
|
171
|
+
|
|
172
|
+
const title = document.createElement('h3')
|
|
173
|
+
title.className = 'oe-source-editor__title'
|
|
174
|
+
title.textContent = 'Source'
|
|
175
|
+
|
|
176
|
+
const label = document.createElement('label')
|
|
177
|
+
label.className = 'oe-source-editor__label'
|
|
178
|
+
const labelText = document.createElement('span')
|
|
179
|
+
labelText.textContent = 'Source'
|
|
180
|
+
|
|
181
|
+
/** @type {HTMLInputElement | HTMLTextAreaElement} */
|
|
182
|
+
let field
|
|
183
|
+
if (kind === 'html') {
|
|
184
|
+
const textarea = document.createElement('textarea')
|
|
185
|
+
textarea.rows = 6
|
|
186
|
+
textarea.spellcheck = false
|
|
187
|
+
field = textarea
|
|
188
|
+
} else {
|
|
189
|
+
const input = document.createElement('input')
|
|
190
|
+
input.type = 'url'
|
|
191
|
+
input.inputMode = 'url'
|
|
192
|
+
input.setAttribute('autocomplete', 'url')
|
|
193
|
+
field = input
|
|
194
|
+
}
|
|
195
|
+
field.className = 'oe-source-editor__field'
|
|
196
|
+
field.required = true
|
|
197
|
+
label.append(labelText, field)
|
|
198
|
+
|
|
199
|
+
const error = document.createElement('div')
|
|
200
|
+
error.className = 'oe-source-editor__error'
|
|
201
|
+
error.setAttribute('role', 'alert')
|
|
202
|
+
error.hidden = true
|
|
203
|
+
|
|
204
|
+
const actions = document.createElement('div')
|
|
205
|
+
actions.className = 'oe-source-editor__actions'
|
|
206
|
+
|
|
207
|
+
const cancel = document.createElement('button')
|
|
208
|
+
cancel.type = 'button'
|
|
209
|
+
cancel.className = 'oe-source-editor__button oe-source-editor__button--secondary'
|
|
210
|
+
cancel.textContent = 'Cancel'
|
|
211
|
+
|
|
212
|
+
const submit = document.createElement('button')
|
|
213
|
+
submit.type = 'submit'
|
|
214
|
+
submit.className = 'oe-source-editor__button oe-source-editor__button--primary'
|
|
215
|
+
submit.textContent = 'Insert'
|
|
216
|
+
|
|
217
|
+
actions.append(cancel, submit)
|
|
218
|
+
panel.append(title, label, error, actions)
|
|
219
|
+
root.append(backdrop, panel)
|
|
220
|
+
wrapper.appendChild(root)
|
|
221
|
+
|
|
222
|
+
const layer = createPluginLayer(wrapper, signal)
|
|
223
|
+
/** @type {SourceEditorSurface} */
|
|
224
|
+
let surface
|
|
225
|
+
const destroy = () => {
|
|
226
|
+
const active = activeEditors.get(wrapper)
|
|
227
|
+
if (active?.surface === surface) active.close()
|
|
228
|
+
root.remove()
|
|
229
|
+
if (owned?.get(kind) === surface) owned.delete(kind)
|
|
230
|
+
if (owned?.size === 0) surfaces.delete(wrapper)
|
|
231
|
+
}
|
|
232
|
+
surface = {
|
|
233
|
+
root,
|
|
234
|
+
backdrop,
|
|
235
|
+
panel,
|
|
236
|
+
title,
|
|
237
|
+
labelText,
|
|
238
|
+
field,
|
|
239
|
+
error,
|
|
240
|
+
cancel,
|
|
241
|
+
submit,
|
|
242
|
+
signal,
|
|
243
|
+
layer,
|
|
244
|
+
destroy,
|
|
245
|
+
}
|
|
246
|
+
owned.set(kind, surface)
|
|
247
|
+
signal.addEventListener('abort', destroy, { once: true })
|
|
248
|
+
return surface
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* @param {SourceEditorSurface} surface
|
|
253
|
+
* @param {boolean} visible
|
|
254
|
+
* @returns {void}
|
|
255
|
+
*/
|
|
256
|
+
function setSurfaceVisible(surface, visible) {
|
|
257
|
+
surface.root.classList.toggle('oe-source-editor--preloaded', !visible)
|
|
258
|
+
surface.root.setAttribute('aria-hidden', String(!visible))
|
|
259
|
+
surface.root.inert = !visible
|
|
260
|
+
}
|
|
@@ -13,23 +13,23 @@ import { normalizeCarouselData } from '../../../shared/carouselData.js'
|
|
|
13
13
|
import { setSanitizedRawHtml } from '../../../shared/sanitize/sanitizeRawHtml.js'
|
|
14
14
|
import { setSafeUrlAttribute } from '../../../shared/sanitize/sanitizeUrl.js'
|
|
15
15
|
|
|
16
|
-
const styles = new URL('./styles.css', import.meta.url).href
|
|
16
|
+
const styles = new URL('./styles.css', import.meta.url).href
|
|
17
17
|
|
|
18
|
-
/**
|
|
19
|
-
* Create the mixed-media carousel renderer and own its Carousel instances.
|
|
20
|
-
* @param {string} classPrefix
|
|
21
|
-
* @param {Record<string, import('../../../shared/localeTypes').LocaleValue>} locale
|
|
22
|
-
* @returns {import('../../types').BlockRenderer<import('../../types').CarouselBlock>}
|
|
23
|
-
*/
|
|
24
|
-
export function createCarouselRenderer(classPrefix, locale) {
|
|
18
|
+
/**
|
|
19
|
+
* Create the mixed-media carousel renderer and own its Carousel instances.
|
|
20
|
+
* @param {string} classPrefix
|
|
21
|
+
* @param {Record<string, import('../../../shared/localeTypes').LocaleValue>} locale
|
|
22
|
+
* @returns {import('../../types').BlockRenderer<import('../../types').CarouselBlock>}
|
|
23
|
+
*/
|
|
24
|
+
export function createCarouselRenderer(classPrefix, locale) {
|
|
25
25
|
const p = `${classPrefix}-carousel-block`
|
|
26
26
|
/** @type {WeakMap<HTMLElement, Carousel>} */
|
|
27
|
-
const instances = new WeakMap()
|
|
28
|
-
/** @param {string} key @param {string} fallback */
|
|
29
|
-
const t = (key, fallback) => {
|
|
30
|
-
const value = locale[`renderer.carousel.${key}`]
|
|
31
|
-
return typeof value === 'string' ? value : fallback
|
|
32
|
-
}
|
|
27
|
+
const instances = new WeakMap()
|
|
28
|
+
/** @param {string} key @param {string} fallback */
|
|
29
|
+
const t = (key, fallback) => {
|
|
30
|
+
const value = locale[`renderer.carousel.${key}`]
|
|
31
|
+
return typeof value === 'string' ? value : fallback
|
|
32
|
+
}
|
|
33
33
|
|
|
34
34
|
return {
|
|
35
35
|
type: 'carousel',
|
|
@@ -41,10 +41,11 @@ export function createCarouselRenderer(classPrefix, locale) {
|
|
|
41
41
|
root.className = p
|
|
42
42
|
root.setAttribute('aria-label', t('label', 'Content carousel'))
|
|
43
43
|
if (data.options.aspectRatio && data.options.aspectRatio !== 'auto') {
|
|
44
|
+
root.classList.add(`${p}--fixed-ratio`)
|
|
44
45
|
root.style.aspectRatio = data.options.aspectRatio
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
const slides = data.slides.map(slide => {
|
|
48
|
+
const slides = data.slides.map(slide => {
|
|
48
49
|
const content = () => {
|
|
49
50
|
const figure = document.createElement('figure')
|
|
50
51
|
figure.className = `${p}__slide`
|
|
@@ -80,14 +81,14 @@ export function createCarouselRenderer(classPrefix, locale) {
|
|
|
80
81
|
...(slide.type === 'image' && slide.src ? { thumb: slide.src } : {}),
|
|
81
82
|
...(slide.type === 'video' && slide.poster ? { thumb: slide.poster } : {}),
|
|
82
83
|
meta: { id: slide.id, type: slide.type },
|
|
83
|
-
}
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
// Preserve mode may intentionally pass a normalized empty carousel.
|
|
87
|
-
// Avoid constructing a runtime instance with no navigable slides.
|
|
88
|
-
if (!slides.length) return root
|
|
89
|
-
|
|
90
|
-
const plugins = [createKeyboard({ scope: 'container' }), createSwipe({ mouse: true })]
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
// Preserve mode may intentionally pass a normalized empty carousel.
|
|
88
|
+
// Avoid constructing a runtime instance with no navigable slides.
|
|
89
|
+
if (!slides.length) return root
|
|
90
|
+
|
|
91
|
+
const plugins = [createKeyboard({ scope: 'container' }), createSwipe({ mouse: true })]
|
|
91
92
|
if (data.options.navigation) plugins.push(createArrows())
|
|
92
93
|
if (data.options.pagination) plugins.push(createDots())
|
|
93
94
|
if (data.options.thumbnails) plugins.push(createThumbnails())
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
.editor-carousel-block { position: relative; width: 100%; }
|
|
2
|
-
.editor-carousel-block .carousel__viewport { border-radius: var(--er-radius);
|
|
2
|
+
.editor-carousel-block .carousel__viewport { border-radius: var(--er-radius); }
|
|
3
3
|
.editor-carousel-block__slide { display: grid; place-items: center; box-sizing: border-box; width: 100%; min-height: 16rem; margin: 0; }
|
|
4
|
-
.editor-carousel-block__slide img, .editor-carousel-block__slide video { display: block; width: 100%; height:
|
|
4
|
+
.editor-carousel-block__slide img, .editor-carousel-block__slide video { display: block; width: 100%; height: auto; max-height: none; object-fit: contain; }
|
|
5
|
+
.editor-carousel-block--fixed-ratio .carousel__viewport { height: 100%; background: var(--er-surface); }
|
|
6
|
+
.editor-carousel-block--fixed-ratio .carousel,
|
|
7
|
+
.editor-carousel-block--fixed-ratio .carousel__track,
|
|
8
|
+
.editor-carousel-block--fixed-ratio .editor-carousel-block__slide { height: 100%; min-height: 0; }
|
|
9
|
+
.editor-carousel-block--fixed-ratio .editor-carousel-block__slide img,
|
|
10
|
+
.editor-carousel-block--fixed-ratio .editor-carousel-block__slide video { height: 100%; max-height: 100%; object-fit: contain; }
|
|
5
11
|
.editor-carousel-block__html { box-sizing: border-box; width: 100%; padding: 1rem; }
|
|
6
12
|
.editor-carousel-block figcaption { box-sizing: border-box; width: 100%; padding: .625rem .875rem; color: var(--er-text-2); font-size: .8125rem; text-align: center; }
|
|
7
13
|
.editor-carousel-block .carousel__arrow:focus-visible, .editor-carousel-block .carousel__dot:focus-visible, .editor-carousel-block .carousel__thumb:focus-visible { outline: 2px solid var(--er-accent); outline-offset: 2px; }
|