@shelamkoff/rector 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/inline-plugins/color/README.md +1 -2
- package/dist/inline-plugins/color/README.ru.md +1 -2
- package/dist/inline-plugins/color.js +13 -1
- package/dist/plugins/gallery/README.md +92 -92
- package/dist/plugins/gallery/README.ru.md +17 -17
- package/dist/plugins/gallery/gallery.css +38 -18
- package/dist/plugins/gallery/settings.js +15 -10
- package/dist/plugins/gallery/view-filled.js +26 -19
- 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 +7 -6
|
@@ -13,7 +13,6 @@ import { createEditor } from '@shelamkoff/rector'
|
|
|
13
13
|
import { Paragraph } from '@shelamkoff/rector/plugins/paragraph'
|
|
14
14
|
import { createColorSwatchPlugin } from '@shelamkoff/rector/inline-plugins/color'
|
|
15
15
|
import '@shelamkoff/rector/styles/editor.css'
|
|
16
|
-
import '@shelamkoff/color-picker/styles.css'
|
|
17
16
|
|
|
18
17
|
const editor = createEditor({
|
|
19
18
|
holder: document.querySelector('#editor'),
|
|
@@ -54,7 +53,7 @@ In read-only mode the widget remains visible but does not open the picker. `edit
|
|
|
54
53
|
|
|
55
54
|
## Styles and document output
|
|
56
55
|
|
|
57
|
-
|
|
56
|
+
The plugin acquires the color-picker stylesheet through Rector's reference-counted style registry while its editor is mounted, so applications must not import it separately. The widget root uses `.oe-ip.oe-ip--color`; its dot and label use `.oe-ip__dot` and `.oe-ip__label`. Scope host overrides under the editor or renderer container.
|
|
58
57
|
|
|
59
58
|
For document output, pass `createColorSwatchPlugin()` in `EditorRenderer`'s `inlinePlugins` array. The renderer uses its `createWidget()` and `getData()` subset; the picker is not mounted by the renderer.
|
|
60
59
|
|
|
@@ -13,7 +13,6 @@ import { createEditor } from '@shelamkoff/rector'
|
|
|
13
13
|
import { Paragraph } from '@shelamkoff/rector/plugins/paragraph'
|
|
14
14
|
import { createColorSwatchPlugin } from '@shelamkoff/rector/inline-plugins/color'
|
|
15
15
|
import '@shelamkoff/rector/styles/editor.css'
|
|
16
|
-
import '@shelamkoff/color-picker/styles.css'
|
|
17
16
|
|
|
18
17
|
const editor = createEditor({
|
|
19
18
|
holder: document.querySelector('#editor'),
|
|
@@ -54,7 +53,7 @@ const editor = createEditor({
|
|
|
54
53
|
|
|
55
54
|
## Стили и отображение документа
|
|
56
55
|
|
|
57
|
-
|
|
56
|
+
Плагин сам подключает таблицу стилей средства выбора цвета через реестр Rector с подсчётом ссылок на время жизни редактора, поэтому приложению не нужно импортировать её отдельно. Корневой элемент использует `.oe-ip.oe-ip--color`, а точка и подпись — `.oe-ip__dot` и `.oe-ip__label`. Ограничивайте переопределения контейнером редактора или рендерера.
|
|
58
57
|
|
|
59
58
|
Для отображения документа передайте `createColorSwatchPlugin()` в массив `inlinePlugins` конструктора `EditorRenderer`. Рендерер использует только `createWidget()` и `getData()`; окно выбора цвета в нём не подключается.
|
|
60
59
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { ColorPicker } from '@shelamkoff/color-picker'
|
|
1
|
+
import { ColorPicker, colorPickerStylesUrl } from '@shelamkoff/color-picker'
|
|
2
|
+
import { injectStyleUrls } from '../core/StyleInjector.js'
|
|
2
3
|
import { generateInlineId } from '../shared/inlineMarshal.js'
|
|
3
4
|
import { normalizeTextValue } from '../shared/textFormat.js'
|
|
4
5
|
|
|
@@ -12,6 +13,8 @@ import { normalizeTextValue } from '../shared/textFormat.js'
|
|
|
12
13
|
export function createColorSwatchPlugin() {
|
|
13
14
|
/** @type {import('../core/types').IScopedI18n | null} */
|
|
14
15
|
let i18n = null
|
|
16
|
+
/** @type {{ destroy(): void } | null} */
|
|
17
|
+
let styleHandle = null
|
|
15
18
|
|
|
16
19
|
return {
|
|
17
20
|
type: 'color',
|
|
@@ -19,6 +22,10 @@ export function createColorSwatchPlugin() {
|
|
|
19
22
|
icon: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 21a9 9 0 0 1 0-18c4.97 0 9 3.582 9 8c0 1.06-.474 2.078-1.318 2.828S17.938 15 16.5 15H14a2 2 0 0 0-1 3.75A1.3 1.3 0 0 1 12 21"/><circle cx="7.5" cy="10.5" r=".5" fill="currentColor"/><circle cx="12" cy="7.5" r=".5" fill="currentColor"/><circle cx="16.5" cy="10.5" r=".5" fill="currentColor"/></svg>',
|
|
20
23
|
/** @param {import('../core/types').IScopedI18n} _i18n */
|
|
21
24
|
setI18n(_i18n) { i18n = _i18n },
|
|
25
|
+
mount() {
|
|
26
|
+
if (styleHandle) throw new Error('Color swatch plugin is already mounted')
|
|
27
|
+
styleHandle = injectStyleUrls([colorPickerStylesUrl])
|
|
28
|
+
},
|
|
22
29
|
pasteConfig: {
|
|
23
30
|
patterns: [
|
|
24
31
|
/^#[0-9a-fA-F]{3}$/,
|
|
@@ -72,6 +79,11 @@ export function createColorSwatchPlugin() {
|
|
|
72
79
|
value: element.dataset.value || '#000000',
|
|
73
80
|
}
|
|
74
81
|
},
|
|
82
|
+
|
|
83
|
+
destroy() {
|
|
84
|
+
styleHandle?.destroy()
|
|
85
|
+
styleHandle = null
|
|
86
|
+
},
|
|
75
87
|
}
|
|
76
88
|
}
|
|
77
89
|
|
|
@@ -1,92 +1,92 @@
|
|
|
1
|
-
# Gallery block plugin
|
|
2
|
-
|
|
3
|
-
Multi-image gallery with layouts, captions, appearance settings, reordering, and viewer options.
|
|
4
|
-
|
|
5
|
-
## Install and register
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm install @shelamkoff/rector
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
```js
|
|
12
|
-
import { createEditor } from '@shelamkoff/rector'
|
|
13
|
-
import { Gallery } from '@shelamkoff/rector/plugins/gallery'
|
|
14
|
-
import '@shelamkoff/rector/styles/editor.css'
|
|
15
|
-
|
|
16
|
-
const editor = createEditor({
|
|
17
|
-
holder: document.querySelector('#editor'),
|
|
18
|
-
plugins: [new Gallery()],
|
|
19
|
-
})
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
The registered block type is `gallery`. The class is also exported by the complete `@shelamkoff/rector/plugins` preset and can be loaded through `@shelamkoff/rector/plugins/async`.
|
|
23
|
-
|
|
24
|
-
## Data
|
|
25
|
-
|
|
26
|
-
```json
|
|
27
|
-
{
|
|
28
|
-
"images": [{ "url": "https://cdn.example/a.jpg", "caption": "A" }],
|
|
29
|
-
"layout": "auto",
|
|
30
|
-
"styles": { "gap": "8px", "borderRadius": "8px", "height": "420px" },
|
|
31
|
-
"options": { "loop": true, "zoom": true, "navigation": true, "captions": true, "thumbnails": true, "fullscreen": true, "autoplayInterval": 3000 }
|
|
32
|
-
}
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
### Field reference
|
|
36
|
-
|
|
37
|
-
| Field | Required | Meaning and constraints |
|
|
38
|
-
| --- | --- | --- |
|
|
39
|
-
| `images` | yes | Non-empty array. Every item requires a media-policy `url`; `caption` is an optional string. |
|
|
40
|
-
| `layout` | yes | One of `auto`, `1`, `2`, `3a`, `3b`, `3c`, `4a`, `4b`, `4c`, `5a`, `5b`, `5c`, `6a`, `6b`, `6c`, `triptych`, `masonry`, `poly-5`, `poly-3arch`, `poly-5flat`, or `poly-3steps`. |
|
|
41
|
-
| `styles` | no | String-valued map. The built-in settings UI uses `gap`, `borderRadius`, and `height`. |
|
|
42
|
-
| `options.loop` | no | Wraps viewer navigation from the last image to the first. The new-block default is `true`. |
|
|
43
|
-
| `options.zoom`, `navigation`, `captions`, `fullscreen` | no | Boolean viewer controls; new-block defaults are `true`. |
|
|
44
|
-
| `options.thumbnails` | no | Shows viewer thumbnails; the new-block default is `false`. |
|
|
45
|
-
| `options.autoplayInterval` | no | Positive finite interval in milliseconds. Omit it to disable autoplay. `0` is not valid persisted data. |
|
|
46
|
-
|
|
47
|
-
Without `uploadFile`, local files become data URLs. Callback results are accepted only when `url` passes the shared media URL policy. The viewer requires `@shelamkoff/expose`.
|
|
48
|
-
|
|
49
|
-
## Configuration
|
|
50
|
-
|
|
51
|
-
Every built-in block plugin accepts two style ownership options: `injectStyles?: boolean` defaults to `true`; set it to `false` when the host bundles that plugin's CSS. `css?: string` adds one host-provided stylesheet URL after the plugin default, or acts as the replacement URL when default injection is disabled.
|
|
52
|
-
|
|
53
|
-
`uploadFile?: (file: File, context: { signal: AbortSignal }) => Promise<{ url: string; alt?: string }>` uploads each file. `actions?: Array<{ icon?; label; handler(context: { signal: AbortSignal }): Promise<Array<{ url: string; alt?: string }> | null> }>` adds application media sources. Both callbacks must stop work when the supplied signal is aborted.
|
|
54
|
-
|
|
55
|
-
## Application file sources
|
|
56
|
-
|
|
57
|
-
Use `uploadFile` for browser `File` objects and `actions` for existing assets selected from a media library, cloud drive, or another application-owned catalog. An action may return several images; the complete selection becomes one undo/redo step.
|
|
58
|
-
|
|
59
|
-
```js
|
|
60
|
-
const gallery = new Gallery({
|
|
61
|
-
actions: [{
|
|
62
|
-
label: 'Media library',
|
|
63
|
-
async handler({ signal }) {
|
|
64
|
-
const assets = await openMediaLibrary({
|
|
65
|
-
accept: ['image/*'],
|
|
66
|
-
multiple: true,
|
|
67
|
-
signal,
|
|
68
|
-
})
|
|
69
|
-
return assets?.map(asset => ({
|
|
70
|
-
url: asset.url,
|
|
71
|
-
alt: asset.alt,
|
|
72
|
-
})) ?? null
|
|
73
|
-
},
|
|
74
|
-
}],
|
|
75
|
-
})
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
Return `null` when selection is cancelled. The optional `alt` value initializes the image caption. Multiple independent sources may be added as separate `actions` entries. See [File sources and media libraries](https://shelamkoff.github.io/editor/guide/file-sources) for upload, cancellation, validation, and reusable adapter guidance.
|
|
79
|
-
|
|
80
|
-
## Capabilities
|
|
81
|
-
|
|
82
|
-
Multi-file upload; image-file paste with an async transaction; layout/style controls; drag reordering; custom media actions.
|
|
83
|
-
|
|
84
|
-
## Undo, lifecycle, and styles
|
|
85
|
-
|
|
86
|
-
User actions exposed by the plugin enter the command pipeline through the supplied `context.mutate()` capability, so each completed action is one undo/redo step. The editor reference-counts the plugin's declared stylesheet URLs. Removing a block calls its cleanup hook; removing the editor calls `destroy()` for every remaining block and then releases shared plugin resources.
|
|
87
|
-
|
|
88
|
-
Do not remove the editor holder without first calling `editor.destroy()`.
|
|
89
|
-
|
|
90
|
-
## Document output
|
|
91
|
-
|
|
92
|
-
Use the matching renderer from `@shelamkoff/rector/renderer/renderers/gallery`. The VitePress guide documents configuration, commands and history, extension contracts, document migrations, styling, security, and lifecycle in a sequential form.
|
|
1
|
+
# Gallery block plugin
|
|
2
|
+
|
|
3
|
+
Multi-image gallery with layouts, captions, appearance settings, reordering, and viewer options.
|
|
4
|
+
|
|
5
|
+
## Install and register
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @shelamkoff/rector
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
import { createEditor } from '@shelamkoff/rector'
|
|
13
|
+
import { Gallery } from '@shelamkoff/rector/plugins/gallery'
|
|
14
|
+
import '@shelamkoff/rector/styles/editor.css'
|
|
15
|
+
|
|
16
|
+
const editor = createEditor({
|
|
17
|
+
holder: document.querySelector('#editor'),
|
|
18
|
+
plugins: [new Gallery()],
|
|
19
|
+
})
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The registered block type is `gallery`. The class is also exported by the complete `@shelamkoff/rector/plugins` preset and can be loaded through `@shelamkoff/rector/plugins/async`.
|
|
23
|
+
|
|
24
|
+
## Data
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"images": [{ "url": "https://cdn.example/a.jpg", "caption": "A" }],
|
|
29
|
+
"layout": "auto",
|
|
30
|
+
"styles": { "gap": "8px", "borderRadius": "8px", "height": "420px" },
|
|
31
|
+
"options": { "loop": true, "zoom": true, "navigation": true, "captions": true, "thumbnails": true, "fullscreen": true, "autoplayInterval": 3000 }
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Field reference
|
|
36
|
+
|
|
37
|
+
| Field | Required | Meaning and constraints |
|
|
38
|
+
| --- | --- | --- |
|
|
39
|
+
| `images` | yes | Non-empty array. Every item requires a media-policy `url`; `caption` is an optional string. |
|
|
40
|
+
| `layout` | yes | One of `auto`, `1`, `2`, `3a`, `3b`, `3c`, `4a`, `4b`, `4c`, `5a`, `5b`, `5c`, `6a`, `6b`, `6c`, `triptych`, `masonry`, `poly-5`, `poly-3arch`, `poly-5flat`, or `poly-3steps`. |
|
|
41
|
+
| `styles` | no | String-valued map. The built-in settings UI uses `gap`, `borderRadius`, and `height`. |
|
|
42
|
+
| `options.loop` | no | Wraps viewer navigation from the last image to the first. The new-block default is `true`. |
|
|
43
|
+
| `options.zoom`, `navigation`, `captions`, `fullscreen` | no | Boolean viewer controls; new-block defaults are `true`. |
|
|
44
|
+
| `options.thumbnails` | no | Shows viewer thumbnails; the new-block default is `false`. |
|
|
45
|
+
| `options.autoplayInterval` | no | Positive finite interval in milliseconds. Omit it to disable autoplay. `0` is not valid persisted data. |
|
|
46
|
+
|
|
47
|
+
Without `uploadFile`, local files become data URLs. Callback results are accepted only when `url` passes the shared media URL policy. The viewer requires `@shelamkoff/expose`. The `masonry` layout is measured and positioned by `@shelamkoff/masonry`; it is not a CSS-columns approximation.
|
|
48
|
+
|
|
49
|
+
## Configuration
|
|
50
|
+
|
|
51
|
+
Every built-in block plugin accepts two style ownership options: `injectStyles?: boolean` defaults to `true`; set it to `false` when the host bundles that plugin's CSS. `css?: string` adds one host-provided stylesheet URL after the plugin default, or acts as the replacement URL when default injection is disabled.
|
|
52
|
+
|
|
53
|
+
`uploadFile?: (file: File, context: { signal: AbortSignal }) => Promise<{ url: string; alt?: string }>` uploads each file. `actions?: Array<{ icon?; label; handler(context: { signal: AbortSignal }): Promise<Array<{ url: string; alt?: string }> | null> }>` adds application media sources. Both callbacks must stop work when the supplied signal is aborted.
|
|
54
|
+
|
|
55
|
+
## Application file sources
|
|
56
|
+
|
|
57
|
+
Use `uploadFile` for browser `File` objects and `actions` for existing assets selected from a media library, cloud drive, or another application-owned catalog. An action may return several images; the complete selection becomes one undo/redo step.
|
|
58
|
+
|
|
59
|
+
```js
|
|
60
|
+
const gallery = new Gallery({
|
|
61
|
+
actions: [{
|
|
62
|
+
label: 'Media library',
|
|
63
|
+
async handler({ signal }) {
|
|
64
|
+
const assets = await openMediaLibrary({
|
|
65
|
+
accept: ['image/*'],
|
|
66
|
+
multiple: true,
|
|
67
|
+
signal,
|
|
68
|
+
})
|
|
69
|
+
return assets?.map(asset => ({
|
|
70
|
+
url: asset.url,
|
|
71
|
+
alt: asset.alt,
|
|
72
|
+
})) ?? null
|
|
73
|
+
},
|
|
74
|
+
}],
|
|
75
|
+
})
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Return `null` when selection is cancelled. The optional `alt` value initializes the image caption. Multiple independent sources may be added as separate `actions` entries. See [File sources and media libraries](https://shelamkoff.github.io/editor/guide/file-sources) for upload, cancellation, validation, and reusable adapter guidance.
|
|
79
|
+
|
|
80
|
+
## Capabilities
|
|
81
|
+
|
|
82
|
+
Multi-file upload; image-file paste with an async transaction; layout/style controls; drag reordering; custom media actions.
|
|
83
|
+
|
|
84
|
+
## Undo, lifecycle, and styles
|
|
85
|
+
|
|
86
|
+
User actions exposed by the plugin enter the command pipeline through the supplied `context.mutate()` capability, so each completed action is one undo/redo step. The editor reference-counts the plugin's declared stylesheet URLs. Removing a block calls its cleanup hook; removing the editor calls `destroy()` for every remaining block and then releases shared plugin resources.
|
|
87
|
+
|
|
88
|
+
Do not remove the editor holder without first calling `editor.destroy()`.
|
|
89
|
+
|
|
90
|
+
## Document output
|
|
91
|
+
|
|
92
|
+
Use the matching renderer from `@shelamkoff/rector/renderer/renderers/gallery`. The VitePress guide documents configuration, commands and history, extension contracts, document migrations, styling, security, and lifecycle in a sequential form.
|
|
@@ -28,23 +28,23 @@ const editor = createEditor({
|
|
|
28
28
|
"images": [{ "url": "https://cdn.example/a.jpg", "caption": "A" }],
|
|
29
29
|
"layout": "auto",
|
|
30
30
|
"styles": { "gap": "8px", "borderRadius": "8px", "height": "420px" },
|
|
31
|
-
"options": { "loop": true, "zoom": true, "navigation": true, "captions": true, "thumbnails": true, "fullscreen": true, "autoplayInterval": 3000 }
|
|
32
|
-
}
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
### Поля данных
|
|
36
|
-
|
|
37
|
-
| Поле | Обязательное | Назначение и ограничения |
|
|
38
|
-
| --- | --- | --- |
|
|
39
|
-
| `images` | да | Непустой массив. Каждому элементу нужен разрешённый политикой медиафайлов `url`; `caption` — необязательная строка. |
|
|
40
|
-
| `layout` | да | Одно из значений `auto`, `1`, `2`, `3a`, `3b`, `3c`, `4a`, `4b`, `4c`, `5a`, `5b`, `5c`, `6a`, `6b`, `6c`, `triptych`, `masonry`, `poly-5`, `poly-3arch`, `poly-5flat` или `poly-3steps`. |
|
|
41
|
-
| `styles` | нет | Карта строковых значений. Встроенная панель настроек использует `gap`, `borderRadius` и `height`. |
|
|
42
|
-
| `options.loop` | нет | После последнего изображения переходит к первому; для нового блока по умолчанию `true`. |
|
|
43
|
-
| `options.zoom`, `navigation`, `captions`, `fullscreen` | нет | Логические параметры просмотра; для нового блока по умолчанию `true`. |
|
|
44
|
-
| `options.thumbnails` | нет | Показывает миниатюры; для нового блока по умолчанию `false`. |
|
|
45
|
-
| `options.autoplayInterval` | нет | Положительный конечный интервал в миллисекундах. Отсутствие поля отключает автовоспроизведение. Значение `0` недопустимо в сохранённых данных. |
|
|
46
|
-
|
|
47
|
-
Без `uploadFile` файлы сохраняются как URL со встроенными данными. Для полноэкранного просмотра нужен `@shelamkoff/expose`.
|
|
31
|
+
"options": { "loop": true, "zoom": true, "navigation": true, "captions": true, "thumbnails": true, "fullscreen": true, "autoplayInterval": 3000 }
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Поля данных
|
|
36
|
+
|
|
37
|
+
| Поле | Обязательное | Назначение и ограничения |
|
|
38
|
+
| --- | --- | --- |
|
|
39
|
+
| `images` | да | Непустой массив. Каждому элементу нужен разрешённый политикой медиафайлов `url`; `caption` — необязательная строка. |
|
|
40
|
+
| `layout` | да | Одно из значений `auto`, `1`, `2`, `3a`, `3b`, `3c`, `4a`, `4b`, `4c`, `5a`, `5b`, `5c`, `6a`, `6b`, `6c`, `triptych`, `masonry`, `poly-5`, `poly-3arch`, `poly-5flat` или `poly-3steps`. |
|
|
41
|
+
| `styles` | нет | Карта строковых значений. Встроенная панель настроек использует `gap`, `borderRadius` и `height`. |
|
|
42
|
+
| `options.loop` | нет | После последнего изображения переходит к первому; для нового блока по умолчанию `true`. |
|
|
43
|
+
| `options.zoom`, `navigation`, `captions`, `fullscreen` | нет | Логические параметры просмотра; для нового блока по умолчанию `true`. |
|
|
44
|
+
| `options.thumbnails` | нет | Показывает миниатюры; для нового блока по умолчанию `false`. |
|
|
45
|
+
| `options.autoplayInterval` | нет | Положительный конечный интервал в миллисекундах. Отсутствие поля отключает автовоспроизведение. Значение `0` недопустимо в сохранённых данных. |
|
|
46
|
+
|
|
47
|
+
Без `uploadFile` файлы сохраняются как URL со встроенными данными. Для полноэкранного просмотра нужен `@shelamkoff/expose`. Раскладка `masonry` измеряется и позиционируется библиотекой `@shelamkoff/masonry`, а не имитацией через CSS columns.
|
|
48
48
|
|
|
49
49
|
## Конфигурация
|
|
50
50
|
|
|
@@ -434,19 +434,40 @@
|
|
|
434
434
|
aspect-ratio: 3 / 1.5;
|
|
435
435
|
}
|
|
436
436
|
|
|
437
|
-
/* Masonry:
|
|
438
|
-
.eg--masonry {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
437
|
+
/* Masonry: layout is calculated by @shelamkoff/masonry. */
|
|
438
|
+
.eg--masonry {
|
|
439
|
+
position: relative;
|
|
440
|
+
width: 100%;
|
|
441
|
+
gap: 0.25rem;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
.eg--masonry.masonry-container > .masonry-item {
|
|
445
|
+
position: absolute;
|
|
446
|
+
top: 0;
|
|
447
|
+
left: 0;
|
|
448
|
+
will-change: transform;
|
|
449
|
+
opacity: 0;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
.eg--masonry.masonry-container > .masonry-item--visible {
|
|
453
|
+
opacity: 1;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
.eg--masonry .oe-gallery__slot {
|
|
457
|
+
width: 100%;
|
|
458
|
+
margin: 0;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
.eg--masonry .oe-gallery__slot-img {
|
|
462
|
+
height: auto;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
.eg--masonry-fallback {
|
|
466
|
+
display: grid;
|
|
467
|
+
grid-template-columns: repeat(auto-fill, minmax(min(100%, 15rem), 1fr));
|
|
468
|
+
height: auto !important;
|
|
469
|
+
}
|
|
470
|
+
|
|
450
471
|
/* ── Polyptych templates ── */
|
|
451
472
|
|
|
452
473
|
/* poly-5: Classic 5-panel pyramid, center tallest */
|
|
@@ -511,11 +532,10 @@
|
|
|
511
532
|
.eg--6b {
|
|
512
533
|
grid-template-columns: repeat(3, 1fr);
|
|
513
534
|
grid-template-rows: 1fr 1fr;
|
|
514
|
-
}
|
|
515
|
-
.eg--6b .oe-gallery__slot:nth-child(1) { grid-column: 1 / -1; grid-row: auto; }
|
|
516
|
-
.eg--6b .oe-gallery__slot:nth-child(n+2) { grid-column: auto; }
|
|
517
|
-
|
|
518
|
-
}
|
|
535
|
+
}
|
|
536
|
+
.eg--6b .oe-gallery__slot:nth-child(1) { grid-column: 1 / -1; grid-row: auto; }
|
|
537
|
+
.eg--6b .oe-gallery__slot:nth-child(n+2) { grid-column: auto; }
|
|
538
|
+
}
|
|
519
539
|
|
|
520
540
|
/* ── Action bar ───────────────────────────────────────────────────────────── */
|
|
521
541
|
|
|
@@ -153,20 +153,25 @@ function buildStylesGroup(wrapper, state, deps, signal) {
|
|
|
153
153
|
const group = createGroup(deps.t('styles', 'Styles'))
|
|
154
154
|
const styles = state.data.styles
|
|
155
155
|
|
|
156
|
-
const makeStyleInput = (/** @type {string} */ key, /** @type {string} */ value) => {
|
|
157
|
-
const input = document.createElement('input')
|
|
158
|
-
input.type = 'text'
|
|
159
|
-
input.className = CSS.styleInput
|
|
160
|
-
input.value = value || ''
|
|
156
|
+
const makeStyleInput = (/** @type {string} */ key, /** @type {string} */ value) => {
|
|
157
|
+
const input = document.createElement('input')
|
|
158
|
+
input.type = 'text'
|
|
159
|
+
input.className = CSS.styleInput
|
|
160
|
+
input.value = value || ''
|
|
161
161
|
input.addEventListener('input', () => {
|
|
162
162
|
deps.mutate(() => {
|
|
163
163
|
if (input.value) styles[key] = input.value
|
|
164
164
|
else delete styles[key]
|
|
165
|
-
applyGalleryStyles(wrapper, state)
|
|
166
|
-
})
|
|
167
|
-
}, { signal })
|
|
168
|
-
|
|
169
|
-
|
|
165
|
+
applyGalleryStyles(wrapper, state)
|
|
166
|
+
})
|
|
167
|
+
}, { signal })
|
|
168
|
+
if (key === 'gap') {
|
|
169
|
+
input.addEventListener('change', () => {
|
|
170
|
+
if (state.data.layout === 'masonry') deps.reRender()
|
|
171
|
+
}, { signal })
|
|
172
|
+
}
|
|
173
|
+
return input
|
|
174
|
+
}
|
|
170
175
|
|
|
171
176
|
const makeRow = (/** @type {string} */ label, /** @type {HTMLElement} */ input) => {
|
|
172
177
|
const row = document.createElement('div')
|
|
@@ -10,10 +10,11 @@ import {
|
|
|
10
10
|
} from './layout.js'
|
|
11
11
|
import { applyGalleryStyles } from './styles.js'
|
|
12
12
|
import { buildSettingsPanel } from './settings.js'
|
|
13
|
-
import {
|
|
14
|
-
attachExternalDrop, createEmptySlot, createFilledSlot, createOverflowItem,
|
|
15
|
-
} from './slot.js'
|
|
16
|
-
import { createPluginLayer } from '../shared/layer.js'
|
|
13
|
+
import {
|
|
14
|
+
attachExternalDrop, createEmptySlot, createFilledSlot, createOverflowItem,
|
|
15
|
+
} from './slot.js'
|
|
16
|
+
import { createPluginLayer } from '../shared/layer.js'
|
|
17
|
+
import { mountGalleryMasonry } from '../../shared/galleryMasonry.js'
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* @typedef {Object} FilledViewDeps
|
|
@@ -66,21 +67,27 @@ export function renderFilledView(wrapper, state, deps) {
|
|
|
66
67
|
* @param {FilledViewDeps} deps
|
|
67
68
|
* @param {AbortSignal} signal
|
|
68
69
|
*/
|
|
69
|
-
function renderMasonry(wrapper, state, deps, signal) {
|
|
70
|
-
const grid = document.createElement('div')
|
|
71
|
-
grid.className = `${CSS.grid} eg--masonry`
|
|
72
|
-
|
|
73
|
-
const slotDeps = makeSlotDeps(deps)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
70
|
+
function renderMasonry(wrapper, state, deps, signal) {
|
|
71
|
+
const grid = document.createElement('div')
|
|
72
|
+
grid.className = `${CSS.grid} eg--masonry`
|
|
73
|
+
|
|
74
|
+
const slotDeps = makeSlotDeps(deps)
|
|
75
|
+
/** @type {HTMLElement[]} */
|
|
76
|
+
const slots = []
|
|
77
|
+
|
|
78
|
+
state.data.images.forEach((img, i) => {
|
|
79
|
+
const slot = createFilledSlot(img, i, signal, slotDeps)
|
|
80
|
+
slot.dataset.index = String(i)
|
|
81
|
+
const image = /** @type {HTMLImageElement | null} */ (slot.querySelector(`.${CSS.slotImg}`))
|
|
82
|
+
if (image) image.loading = 'eager'
|
|
83
|
+
slots.push(slot)
|
|
84
|
+
grid.appendChild(slot)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
if (!deps.readOnly) attachExternalDrop(grid, signal, deps.onFilesDropped)
|
|
88
|
+
wrapper.appendChild(grid)
|
|
89
|
+
mountGalleryMasonry(grid, slots, { signal })
|
|
90
|
+
}
|
|
84
91
|
|
|
85
92
|
/**
|
|
86
93
|
* Slot-based: fixed template + optional overflow row.
|
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
# Gallery renderer
|
|
2
|
-
|
|
3
|
-
Renderer for the `gallery` block. It converts persisted block data into renderer-owned DOM.
|
|
4
|
-
The Gallery renderer directly uses `@shelamkoff/expose` and its `exposeStylesUrl` package export.
|
|
5
|
-
|
|
6
|
-
The `@shelamkoff/rector/renderer` entry contains the synchronous built-in preset, so `@shelamkoff/carousel
|
|
7
|
-
|
|
8
|
-
## Usage
|
|
9
|
-
|
|
10
|
-
```js
|
|
11
|
-
import { createEditorRenderer } from '@shelamkoff/rector/renderer'
|
|
12
|
-
import { createGalleryRenderer } from '@shelamkoff/rector/renderer/renderers/gallery'
|
|
13
|
-
|
|
14
|
-
const renderer = createEditorRenderer({ classPrefix: 'article', blockTypes: [] })
|
|
15
|
-
renderer.registerRenderer(createGalleryRenderer('article', {}))
|
|
16
|
-
const rendererStyles = renderer.injectStyles()
|
|
17
|
-
renderer.renderTo(documentData, document.querySelector('#article'))
|
|
18
|
-
|
|
19
|
-
// When the mounted output is removed:
|
|
20
|
-
renderer.destroy()
|
|
21
|
-
rendererStyles.destroy()
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
## Typical data
|
|
25
|
-
|
|
26
|
-
```json
|
|
27
|
-
{
|
|
28
|
-
"images": [{ "url": "https://cdn.example/a.jpg", "caption": "A" }],
|
|
29
|
-
"layout": "auto",
|
|
30
|
-
"styles": { "gap": "8px", "borderRadius": "8px", "height": "420px" },
|
|
31
|
-
"options": { "loop": true, "zoom": true, "navigation": true, "captions": true, "thumbnails": true, "fullscreen": true, "autoplayInterval": 3000 }
|
|
32
|
-
}
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
The complete persisted-data contract, including layouts, defaults, and validation constraints, is defined by the matching [Gallery plugin](../../../plugins/gallery/README.md#field-reference).
|
|
36
|
-
|
|
37
|
-
Image sources use the media URL policy. The renderer owns its Expose viewers and image-readiness listeners; `destroy()` releases them. It declares gallery and Expose styles.
|
|
38
|
-
|
|
39
|
-
When styles are declared, the explicit `EditorRenderer.injectStyles()` call shown above acquires them and its returned owner releases them.
|
|
40
|
-
|
|
41
|
-
The VitePress guide documents renderer ownership, inline widget reconstruction, styles, cleanup, and security boundaries.
|
|
1
|
+
# Gallery renderer
|
|
2
|
+
|
|
3
|
+
Renderer for the `gallery` block. It converts persisted block data into renderer-owned DOM.
|
|
4
|
+
The Gallery renderer directly uses `@shelamkoff/expose` and its `exposeStylesUrl` package export. Its `masonry` layout is powered by `@shelamkoff/masonry` and is disposed together with renderer-owned output.
|
|
5
|
+
|
|
6
|
+
The `@shelamkoff/rector/renderer` entry contains the synchronous built-in preset, so `@shelamkoff/carousel`, `@shelamkoff/expose`, and `@shelamkoff/masonry` must be installed before importing it. Passing `blockTypes: []` prevents default renderer construction but does not change ESM module resolution.
|
|
7
|
+
|
|
8
|
+
## Usage
|
|
9
|
+
|
|
10
|
+
```js
|
|
11
|
+
import { createEditorRenderer } from '@shelamkoff/rector/renderer'
|
|
12
|
+
import { createGalleryRenderer } from '@shelamkoff/rector/renderer/renderers/gallery'
|
|
13
|
+
|
|
14
|
+
const renderer = createEditorRenderer({ classPrefix: 'article', blockTypes: [] })
|
|
15
|
+
renderer.registerRenderer(createGalleryRenderer('article', {}))
|
|
16
|
+
const rendererStyles = renderer.injectStyles()
|
|
17
|
+
renderer.renderTo(documentData, document.querySelector('#article'))
|
|
18
|
+
|
|
19
|
+
// When the mounted output is removed:
|
|
20
|
+
renderer.destroy()
|
|
21
|
+
rendererStyles.destroy()
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Typical data
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"images": [{ "url": "https://cdn.example/a.jpg", "caption": "A" }],
|
|
29
|
+
"layout": "auto",
|
|
30
|
+
"styles": { "gap": "8px", "borderRadius": "8px", "height": "420px" },
|
|
31
|
+
"options": { "loop": true, "zoom": true, "navigation": true, "captions": true, "thumbnails": true, "fullscreen": true, "autoplayInterval": 3000 }
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The complete persisted-data contract, including layouts, defaults, and validation constraints, is defined by the matching [Gallery plugin](../../../plugins/gallery/README.md#field-reference).
|
|
36
|
+
|
|
37
|
+
Image sources use the media URL policy. The renderer owns its Expose viewers and image-readiness listeners; `destroy()` releases them. It declares gallery and Expose styles.
|
|
38
|
+
|
|
39
|
+
When styles are declared, the explicit `EditorRenderer.injectStyles()` call shown above acquires them and its returned owner releases them.
|
|
40
|
+
|
|
41
|
+
The VitePress guide documents renderer ownership, inline widget reconstruction, styles, cleanup, and security boundaries.
|