Download the PHP package povly/moonshine-flexible-layouts without Composer
On this page you can find all versions of the php package povly/moonshine-flexible-layouts. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download povly/moonshine-flexible-layouts
More information about povly/moonshine-flexible-layouts
Files in povly/moonshine-flexible-layouts
Package moonshine-flexible-layouts
Short Description Flexible content blocks with unlimited nesting for MoonShine
License MIT
Informations about the package moonshine-flexible-layouts
MoonShine Flexible Layouts
Flexible content blocks field for MoonShine 4. Build page builder-style layouts with unlimited nesting, drag-to-reorder, and AJAX-powered block management.
Language: Русский
English
Flexible content blocks field for MoonShine 4. Build page builder-style layouts with unlimited nesting, drag-to-reorder, and AJAX-powered block management.
Features
- Tab-based UI — blocks displayed as reorderable tabs with optional icons
- Unlimited nesting — Flexible Layouts inside block fields just work
- Drag to reorder — powered by SortableJS via MoonShine's native
iterableAPI - AJAX add/remove — blocks are fetched on-demand from the server, no page reload
- Duplicate blocks — one click copies a block with all field values, inserted right after the source
- Limit per block type — restrict how many instances of each block can be added (enforced server-side)
- Native reindex — reuses
MoonShine.iterable.reindex()for correct form field naming at any depth - Block picker modal — Gutenberg-style modal with search and category grouping
- Layout components — use MoonShine
Flex,Column, and other layout components inside blocks for multi-column field layouts - Data safety — blocks removed from code but still stored in JSON survive save/load round-trips (unknown
_typepassthrough) - Localization — ships with English and Russian translations, extensible to any language
Requirements
- PHP 8.2+
- Laravel 12+
- MoonShine 4+
Installation
Publish assets:
Optionally publish the config and translations:
Register the cast on your model:
Quick Start
Usage
Block Registration
The block() method accepts:
| Parameter | Type | Description |
|---|---|---|
$name |
string |
Snake_case key stored in JSON as _type |
$title |
string |
Human-readable label shown in tabs and picker |
$fields |
iterable |
MoonShine fields (can include nested FlexibleLayouts) |
$limit |
?int |
Max instances of this block type (default: unlimited) |
$category |
?string |
Grouping label in the picker modal |
$description |
?string |
Short description shown in the picker card |
$icon |
?string |
MoonShine icon name, emoji, or SVG string |
Basic usage:
Registration is validated: duplicate names (compared after squish()->snake() normalization) and limit < 1 throw an InvalidArgumentException — pass null for unlimited.
With category, description, and icon (use named args):
Icons
The icon parameter accepts three types:
| Type | Example | Renders as |
|---|---|---|
| MoonShine icon name | 'photo' |
SVG from moonshine::icons.photo |
| Emoji | '📷' |
Raw text |
| Raw SVG | '<svg>...</svg>' |
Pass-through HTML |
MoonShine includes 301 Heroicons (stroke-based, 24×24). Browse them in vendor/moonshine/moonshine/src/UI/resources/views/icons/. Common examples: users, photo, document-text, rectangle-stack, bars-3, cog-6-tooth, star, bolt, globe-alt, bookmark.
Icons appear in both the tab label and the picker card.
⚠️ Security:
iconis rendered as raw HTML and must only receive developer-trusted values — never user input.
Blocks without a category are shown in an ungrouped section. When all blocks lack categories, the category pills row is hidden automatically.
Block Picker Modal
Clicking Add block opens a Gutenberg-style modal with:
- Search — type to filter by title, description, or block name
- Category tabs — click to filter by category (only shown when 2+ categories exist)
- Grid of cards — icon + title + description per block; click to add
Press Esc or click outside the modal to close.
z-index / compatibility
The picker overlay lives on the MoonShine core modal layer — z-index: var(--z-modal, 1100). It stays below core menus (--z-menu: 1200) and toasts (--z-toast: 1300), and packages with dedicated elevated layers (e.g. moonshine-media-manager dialogs at 1150/1250) always stack above it. Do not raise the overlay above the core scale — that was the original 9999 bug.
Nested Flexible Layouts
You can put a FlexibleLayouts field inside any block. Nested layouts support the same features (drag, add, remove, reindex):
Disabling Features
Custom Buttons
Duplicating Blocks
Every block header includes a duplicate button right after the remove button. It fetches a fresh server-rendered instance of the same block type (so field ids/names stay unique and per-type limits are enforced server-side), copies all field values from the source block, and inserts the copy directly after it. File fields cannot be copied (browser limitation) — they start empty in the duplicate. A flexible-layouts:block-duplicated DOM event is dispatched after each duplication. Additions and removals dispatch flexible-layouts:block-added and flexible-layouts:block-removed respectively — all events bubble on document with {name, column} in detail.
Custom Labels per Field
By default, all Flexible Layouts fields share the same UI labels from messages.php. Use ->transKey() to give a specific field its own set of labels — useful for nested layouts with different content types:
Create lang/vendor/flexible-layouts/en/refs.php:
Labels resolve in order: flexible-layouts::refs.{key} → if missing → flexible-layouts::messages.{key}. An example file ships in lang/en/refs.php and lang/ru/refs.php.
Multi-column Layouts
Use MoonShine's native Flex and Column components inside blocks for side-by-side fields:
columnSpan(6) = half width (out of 12-column grid). Use columnSpan(4) for three columns, columnSpan(3) for four, etc.
Configuration
Publish the config file:
route_prefix— URL prefix of the AJAX route:POST {prefix}/store/{pageUri}/{resourceUri?}. The route is rate-limited withthrottle:60,1.logging— enables diagnostic logs (skipped/preserved unknown block types, client-supplied limit counts). JSON encode/decode errors in the cast are always logged at error level.
The package route is registered inside
Route::moonshine()and inherits core auth/web/CSRF middleware — nomiddlewarekey exists or is needed.
Translations
The package ships with English and Russian translations. The UI adapts to the app locale automatically.
Available keys:
| Key | EN | RU |
|---|---|---|
add_block |
Add block | Добавить блок |
search_blocks |
Search blocks... | Поиск блоков... |
no_blocks_found |
No blocks found | Блоки не найдены |
all_categories |
All | Все |
duplicate_block |
Duplicate | Дублировать |
Publish translations to customize or add new languages:
To add a language, copy any file to a new locale folder:
Data Format
The field stores data as a flat JSON array. Each entry has a _type key and the block's field values:
Unknown _type handling
Block types removed from code but still present in stored JSON are not lost:
| Entry state | Render | Save | Field callbacks |
|---|---|---|---|
Known _type |
rendered | fields applied | run |
Unknown string _type |
skipped (+ warning if logging on) | preserved verbatim (+ warning if logging on) | skipped |
No _type |
skipped | dropped | skipped |
This makes block-type migrations safe: unrecognized entries keep round-tripping through save → load until you re-register or explicitly remove them. The cast caps JSON nesting depth at 64 (DoS protection).
Development
Assets are built to dist/ and published to public/vendor/flexible-layouts/. See docs/development.md for build internals and debugging.
Русский
Поле гибких контентных блоков для MoonShine 4. Стройте макеты в стиле конструктора страниц: неограниченная вложенность, drag-сортировка и AJAX-управление блоками.
Возможности
- Tab-based UI — блоки отображаются как переупорядочиваемые табы с опциональными иконками
- Неограниченная вложенность — Flexible Layouts внутри полей блоков работает рекурсивно
- Drag-сортировка — SortableJS через нативный API
iterableядра MoonShine - AJAX add/remove — блоки рендерятся по требованию сервером, без перезагрузки страницы
- Дублирование блоков — один клик: блок копируется со всеми значениями полей и вставляется сразу после исходного
- Лимит на тип блока — ограничение числа инстансов каждого типа (проверяется на сервере)
- Нативный reindex — переиспользуется
MoonShine.iterable.reindex()для корректного именования полей формы на любой глубине - Модальный пикер блоков — модал в стиле Gutenberg с поиском и группировкой по категориям
- Layout-компоненты — нативные
Flex,Columnи другие компоненты внутри блоков для мультиколоночных раскладок - Сохранность данных — блоки, удалённые из кода, но оставшиеся в JSON, переживают циклы save/load (passthrough неизвестных
_type) - Локализация — переводы EN и RU из коробки, расширяется на любой язык
Требования
- PHP 8.2+
- Laravel 12+
- MoonShine 4+
Установка
Опубликуйте собранные ассеты:
Опционально — конфиг и переводы:
Зарегистрируйте каст на модели:
Быстрый старт
Использование
Регистрация блоков
Метод block() принимает:
| Параметр | Тип | Описание |
|---|---|---|
$name |
string |
Ключ snake_case, сохраняется в JSON как _type |
$title |
string |
Человекочитаемый заголовок (табы, пикер) |
$fields |
iterable |
Поля MoonShine (в т.ч. вложенные FlexibleLayouts) |
$limit |
?int |
Максимум инстансов этого типа (по умолчанию — без ограничений) |
$category |
?string |
Группировка в пикере |
$description |
?string |
Короткое описание в карточке пикера |
$icon |
?string |
Имя иконки MoonShine, emoji или SVG-строка |
Базовое использование:
Регистрация валидируется: дубликаты имён (сравнение после нормализации squish()->snake()) и limit < 1 бросают InvalidArgumentException — для неограниченного количества передайте null.
С категорией, описанием и иконкой (именованные аргументы):
Иконки
Параметр icon принимает три типа значений:
| Тип | Пример | Рендерится как |
|---|---|---|
| Имя иконки MoonShine | 'photo' |
SVG из moonshine::icons.photo |
| Emoji | '📷' |
Как есть |
| Сырой SVG | '<svg>...</svg>' |
Pass-through HTML |
MoonShine включает 301 Heroicons (stroke, 24×24). Список — в vendor/moonshine/moonshine/src/UI/resources/views/icons/. Часто используемые: users, photo, document-text, rectangle-stack, bars-3, cog-6-tooth, star, bolt, globe-alt, bookmark.
Иконка отображается и на табе, и в карточке пикера.
⚠️ Безопасность: значение
iconрендерится как сырой HTML — допускаются только developer-trusted значения, никогда пользовательский ввод.
Блоки без категории попадают в группу «без группировки». Если категорий нет ни у одного блока — ряд пилюль категорий скрывается автоматически.
Модальный пикер
Клик по «Добавить блок» открывает модал в стиле Gutenberg:
- Поиск — фильтрация по заголовку, описанию и имени блока
- Категории — пилюли фильтрации (показываются при 2+ категориях)
- Сетка карточек — иконка + заголовок + описание; клик добавляет блок
Esc или клик вне модала — закрытие.
z-index / совместимость
Оверлей пикера живёт на слое модалов ядра MoonShine — z-index: var(--z-modal, 1100). Он остаётся ниже меню ядра (--z-menu: 1200) и тостов (--z-toast: 1300); пакеты с собственными elevated-слоями (например, диалоги moonshine-media-manager на 1150/1250) всегда лежат выше. Не поднимайте оверлей выше шкалы ядра — это вернёт исходный баг с 9999.
Вложенные Flexible Layouts
FlexibleLayouts можно положить внутрь полей любого блока. Вложенные макеты поддерживают те же возможности (drag, add, remove, reindex):
Отключение возможностей
Кастомные кнопки
Дублирование блоков
В хедере каждого блока, сразу после кнопки удаления, есть кнопка дублирования. Она запрашивает свежий серверный рендер того же типа блока (id/name полей остаются уникальными, лимиты типов проверяются на сервере), копирует все значения полей исходного блока и вставляет копию сразу после него. Файловые поля скопировать нельзя (ограничение браузера) — в дубле они пусты. После дублирования диспатчится DOM-событие flexible-layouts:block-duplicated. Добавление и удаление диспатчат flexible-layouts:block-added и flexible-layouts:block-removed соответственно — все события всплывают на document с {name, column} в detail.
Персональные подписи поля
По умолчанию все поля FlexibleLayouts берут подписи UI из messages.php. Метод ->transKey() даёт конкретному полю свой набор лейблов — полезно для вложенных макетов с другим типом контента:
Создайте lang/vendor/flexible-layouts/ru/refs.php:
Порядок разрешения: flexible-layouts::refs.{key} → при отсутствии → flexible-layouts::messages.{key}. Пример файла идёт в комплекте: lang/en/refs.php и lang/ru/refs.php.
Мультиколоночные раскладки
Внутри блоков работают нативные компоненты Flex и Column:
columnSpan(6) — половина ширины (сетка из 12 колонок). columnSpan(4) — три колонки, columnSpan(3) — четыре, и т.д.
Конфигурация
Опубликуйте конфиг:
route_prefix— префикс URL AJAX-роута:POST {prefix}/store/{pageUri}/{resourceUri?}. Роут дополнительно ограничен rate limit'омthrottle:60,1.logging— включает diagnostic-логи (пропуск/сохранение неизвестных_type, подсчёт лимитов по клиентским данным). Ошибки кодирования/декодирования JSON в касте всегда пишутся уровнемerror.
Роут пакета регистрируется внутри
Route::moonshine()и наследует auth/web/CSRF middleware ядра — ключаmiddlewareв конфиге нет и он не нужен.
Переводы
Пакет поставляется с английской и русской локализацией. UI автоматически подстраивается под локаль приложения.
Ключи:
| Ключ | EN | RU |
|---|---|---|
add_block |
Add block | Добавить блок |
search_blocks |
Search blocks... | Поиск блоков... |
no_blocks_found |
No blocks found | Блоки не найдены |
all_categories |
All | Все |
duplicate_block |
Duplicate | Дублировать |
Публикация переводов (для кастомизации или новых языков):
Добавление языка — скопируйте файл в новый каталог локали:
Формат данных
Поле хранит данные как плоский JSON-массив. Каждый элемент содержит ключ _type и значения полей блока:
Обработка неизвестных _type
Типы блоков, удалённые из кода, но оставшиеся в JSON, не теряются:
| Состояние записи | Рендер | Сохранение | Колбэки полей |
|---|---|---|---|
_type зарегистрирован |
блок рендерится | поля применяются | выполняются |
_type неизвестен |
пропуск (+ warning при включённом logging) | сохраняется verbatim (+ warning) | пропускаются |
Нет _type |
пропуск | отбрасывается | пропускаются |
Это делает миграции набора блоков безопасными: нераспознанные записи продолжают round-trip через save → load, пока вы не перерегистрируете или явно не удалите их. Каст ограничивает глубину JSON 64 уровнями (защита от DoS).
Разработка
Ассеты собираются в dist/ и публикуются в public/vendor/flexible-layouts/. Подробности сборки и отладки — в docs/development.md.
Documentation
Detailed guides (in Russian) live in the docs/ directory:
| Guide | Description |
|---|---|
| Getting Started | Installation, publishing, model cast, verification |
| Block Types | block(), icons, limits, categories, picker, z-index |
| Nested Layouts | Nesting, Flex/Column, transKey() |
| Data Format | JSON, _type, cast, unknown-type passthrough |
| Configuration | route_prefix, logging, env vars |
| Translations | Keys, locales, adding new languages |
| Development | Bun/Vite build, debugging |
License
MIT