@aduptive/strapi-blockscene 2.0.0-alpha.10 → 2.0.0-alpha.11

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 CHANGED
@@ -96,8 +96,9 @@ The modal is a block browser (80vw wide):
96
96
  remembered per browser). Active values show as removable chips with "Clear
97
97
  all"; the empty state offers a reset.
98
98
  - **Cards**, grouped by typology: facet badges (IMAGE, VIDEO, GALLERY, RICH
99
- TEXT, LIST, DYNAMIC, FORM), a star and a hover "+" that inserts at once.
100
- - A click **magnifies** the block: the card grows into a large panel over the
99
+ TEXT, LIST, DYNAMIC, FORM), a star and an always-visible magnifying glass.
100
+ Clicking the card inserts it immediately; the magnifying glass opens its preview.
101
+ - The preview **magnifies** the block: the card grows into a large panel over the
101
102
  grid (sidebar and top bar stay usable, the grid dims underneath) and shrinks
102
103
  back into its card on close (close button, Esc or a click on the dimmed
103
104
  grid). The animation is transform/opacity only, about 250 ms, and is skipped
@@ -105,7 +106,7 @@ The modal is a block browser (80vw wide):
105
106
  with Fit / Mobile / Tablet / Desktop widths (scaled to fit, as in the page
106
107
  preview) and a compact strip: label, uid, description, badges, fields (on
107
108
  demand), the variant choice (see [Insert variants](#insert-variants)), star
108
- and Insert. Double click, "+" or Enter on a focused card inserts without it
109
+ and Insert. Clicking or pressing Enter on a focused card inserts without it
109
110
  (the first variant, when the block has variants).
110
111
 
111
112
  The magnified preview shows the card's thumbnail at once and fades the live
package/dist/admin.cjs CHANGED
@@ -48,6 +48,10 @@ var require_groups = __commonJS({
48
48
  return Object.keys(out).length ? out : null;
49
49
  }
50
50
  var uidOf = (row) => typeof row?.__component === "string" ? row.__component : "";
51
+ var invalidComponents = (rows, allowed) => {
52
+ const uids = new Set(Array.isArray(allowed) ? allowed : []);
53
+ return (Array.isArray(rows) ? rows : []).flatMap((row, index) => uidOf(row) && !uids.has(uidOf(row)) ? [{ index, uid: uidOf(row) }] : []);
54
+ };
51
55
  var isOpener2 = (row, g) => Boolean(g) && Object.prototype.hasOwnProperty.call(g, uidOf(row));
52
56
  var isClose2 = (row, g) => Boolean(g) && Object.values(g).includes(uidOf(row));
53
57
  var blockKey3 = (row) => row && row.id != null ? `${row.__component}#${row.id}` : row && row.__temp_key__ != null ? String(row.__temp_key__) : null;
@@ -154,6 +158,7 @@ var require_groups = __commonJS({
154
158
  module2.exports = {
155
159
  safeGroups: safeGroups2,
156
160
  validateGroups: validateGroups3,
161
+ invalidComponents,
157
162
  groupRange: groupRange3,
158
163
  groupRows: groupRows2,
159
164
  topLevelRanges: topLevelRanges2,
@@ -1044,7 +1049,7 @@ var en_default = {
1044
1049
  blockPreview: "Live block preview",
1045
1050
  livePreviewFailed: "Live preview unavailable, showing the image.",
1046
1051
  blockPreviewUrl: "Block preview URL",
1047
- blockPreviewUrlHelp: "Optional. A page of your frontend that renders one block on its own, shown large when a gallery card is clicked, e.g. http://localhost:3026/pt-BR/block-preview/'{name}'/'{variant}'. Placeholders: '{uid}', '{name}' (after the dot), '{category}' (before the dot), '{variant}' (default), '{locale}'. Empty uses the page preview route when there is one, else the image.",
1052
+ blockPreviewUrlHelp: "Optional. A page of your frontend that renders one block on its own, shown large from a gallery card's magnifying glass, e.g. http://localhost:3026/pt-BR/block-preview/'{name}'/'{variant}'. Placeholders: '{uid}', '{name}' (after the dot), '{category}' (before the dot), '{variant}' (default), '{locale}'. Empty uses the page preview route when there is one, else the image.",
1048
1053
  rowActions: "Actions for {label}",
1049
1054
  selectBlock: "Select {label}",
1050
1055
  hiddenBadge: "Hidden",
@@ -1465,7 +1470,7 @@ var pt_BR_default = {
1465
1470
  blockPreview: "Preview ao vivo do bloco",
1466
1471
  livePreviewFailed: "Preview ao vivo indispon\xEDvel, mostrando a imagem.",
1467
1472
  blockPreviewUrl: "URL de preview do bloco",
1468
- blockPreviewUrlHelp: "Opcional. Uma p\xE1gina do seu frontend que renderiza um bloco sozinho, mostrada em tamanho grande ao clicar num card da galeria, por exemplo http://localhost:3026/pt-BR/block-preview/'{name}'/'{variant}'. Marcadores: '{uid}', '{name}' (depois do ponto), '{category}' (antes do ponto), '{variant}' (default), '{locale}'. Vazio usa a rota de preview de p\xE1gina quando existir, sen\xE3o a imagem.",
1473
+ blockPreviewUrlHelp: "Opcional. Uma p\xE1gina do seu frontend que renderiza um bloco sozinho, mostrada em tamanho grande pela lupa do card da galeria, por exemplo http://localhost:3026/pt-BR/block-preview/'{name}'/'{variant}'. Marcadores: '{uid}', '{name}' (depois do ponto), '{category}' (antes do ponto), '{variant}' (default), '{locale}'. Vazio usa a rota de preview de p\xE1gina quando existir, sen\xE3o a imagem.",
1469
1474
  rowActions: "A\xE7\xF5es de {label}",
1470
1475
  selectBlock: "Selecionar {label}",
1471
1476
  hiddenBadge: "Oculto",
@@ -1886,7 +1891,7 @@ var pt_default = {
1886
1891
  blockPreview: "Pr\xE9-visualiza\xE7\xE3o ao vivo do bloco",
1887
1892
  livePreviewFailed: "Pr\xE9-visualiza\xE7\xE3o ao vivo indispon\xEDvel, a mostrar a imagem.",
1888
1893
  blockPreviewUrl: "URL de pr\xE9-visualiza\xE7\xE3o do bloco",
1889
- blockPreviewUrlHelp: "Opcional. Uma p\xE1gina do seu frontend que renderiza um bloco isolado, mostrada em grande ao clicar num cart\xE3o da galeria, por exemplo http://localhost:3026/pt-PT/block-preview/'{name}'/'{variant}'. Marcadores: '{uid}', '{name}' (depois do ponto), '{category}' (antes do ponto), '{variant}' (default), '{locale}'. Vazio usa a rota de pr\xE9-visualiza\xE7\xE3o de p\xE1gina quando existir, caso contr\xE1rio a imagem.",
1894
+ blockPreviewUrlHelp: "Opcional. Uma p\xE1gina do seu frontend que renderiza um bloco isolado, mostrada em grande pela lupa do cart\xE3o da galeria, por exemplo http://localhost:3026/pt-PT/block-preview/'{name}'/'{variant}'. Marcadores: '{uid}', '{name}' (depois do ponto), '{category}' (antes do ponto), '{variant}' (default), '{locale}'. Vazio usa a rota de pr\xE9-visualiza\xE7\xE3o de p\xE1gina quando existir, caso contr\xE1rio a imagem.",
1890
1895
  rowActions: "A\xE7\xF5es de {label}",
1891
1896
  selectBlock: "Selecionar {label}",
1892
1897
  hiddenBadge: "Oculto",
@@ -2307,7 +2312,7 @@ var fr_default = {
2307
2312
  blockPreview: "Aper\xE7u en direct du bloc",
2308
2313
  livePreviewFailed: "Aper\xE7u en direct indisponible, l'image est affich\xE9e.",
2309
2314
  blockPreviewUrl: "URL d'aper\xE7u de bloc",
2310
- blockPreviewUrlHelp: "Facultatif. Une page de votre frontend qui affiche un seul bloc, montr\xE9e en grand quand on clique sur une carte de la galerie, par ex. http://localhost:3026/fr/block-preview/'{name}'/'{variant}'. Variables : '{uid}', '{name}' (apr\xE8s le point), '{category}' (avant le point), '{variant}' (default), '{locale}'. Vide : la route d'aper\xE7u de page si elle existe, sinon l'image.",
2315
+ blockPreviewUrlHelp: "Facultatif. Une page de votre frontend qui affiche un seul bloc, montr\xE9e en grand depuis la loupe d'une carte de la galerie, par ex. http://localhost:3026/fr/block-preview/'{name}'/'{variant}'. Variables : '{uid}', '{name}' (apr\xE8s le point), '{category}' (avant le point), '{variant}' (default), '{locale}'. Vide : la route d'aper\xE7u de page si elle existe, sinon l'image.",
2311
2316
  rowActions: "Actions pour {label}",
2312
2317
  selectBlock: "S\xE9lectionner {label}",
2313
2318
  hiddenBadge: "Masqu\xE9",
@@ -2728,7 +2733,7 @@ var es_default = {
2728
2733
  blockPreview: "Vista previa en vivo del bloque",
2729
2734
  livePreviewFailed: "Vista previa en vivo no disponible, se muestra la imagen.",
2730
2735
  blockPreviewUrl: "URL de vista previa del bloque",
2731
- blockPreviewUrlHelp: "Opcional. Una p\xE1gina de su frontend que renderiza un solo bloque, mostrada en grande al hacer clic en una tarjeta de la galer\xEDa, p. ej. http://localhost:3026/es/block-preview/'{name}'/'{variant}'. Marcadores: '{uid}', '{name}' (tras el punto), '{category}' (antes del punto), '{variant}' (default), '{locale}'. Vac\xEDa usa la ruta de vista previa de p\xE1gina si existe; si no, la imagen.",
2736
+ blockPreviewUrlHelp: "Opcional. Una p\xE1gina de su frontend que renderiza un solo bloque, mostrada en grande desde la lupa de una tarjeta de la galer\xEDa, p. ej. http://localhost:3026/es/block-preview/'{name}'/'{variant}'. Marcadores: '{uid}', '{name}' (tras el punto), '{category}' (antes del punto), '{variant}' (default), '{locale}'. Vac\xEDa usa la ruta de vista previa de p\xE1gina si existe; si no, la imagen.",
2732
2737
  rowActions: "Acciones de {label}",
2733
2738
  selectBlock: "Seleccionar {label}",
2734
2739
  hiddenBadge: "Oculto",
@@ -3149,7 +3154,7 @@ var de_default = {
3149
3154
  blockPreview: "Live-Vorschau des Blocks",
3150
3155
  livePreviewFailed: "Live-Vorschau nicht verf\xFCgbar, das Bild wird angezeigt.",
3151
3156
  blockPreviewUrl: "Block-Vorschau-URL",
3152
- blockPreviewUrlHelp: "Optional. Eine Seite Ihres Frontends, die einen einzelnen Block rendert und beim Klick auf eine Galeriekarte gro\xDF angezeigt wird, z. B. http://localhost:3026/de/block-preview/'{name}'/'{variant}'. Platzhalter: '{uid}', '{name}' (nach dem Punkt), '{category}' (vor dem Punkt), '{variant}' (default), '{locale}'. Leer verwendet die Seitenvorschau-Route, falls vorhanden, sonst das Bild.",
3157
+ blockPreviewUrlHelp: "Optional. Eine Seite Ihres Frontends, die einen einzelnen Block rendert und \xFCber die Lupe einer Galeriekarte gro\xDF angezeigt wird, z. B. http://localhost:3026/de/block-preview/'{name}'/'{variant}'. Platzhalter: '{uid}', '{name}' (nach dem Punkt), '{category}' (vor dem Punkt), '{variant}' (default), '{locale}'. Leer verwendet die Seitenvorschau-Route, falls vorhanden, sonst das Bild.",
3153
3158
  rowActions: "Aktionen f\xFCr {label}",
3154
3159
  selectBlock: "{label} ausw\xE4hlen",
3155
3160
  hiddenBadge: "Ausgeblendet",
@@ -3570,7 +3575,7 @@ var it_default = {
3570
3575
  blockPreview: "Anteprima dal vivo del blocco",
3571
3576
  livePreviewFailed: "Anteprima dal vivo non disponibile, viene mostrata l'immagine.",
3572
3577
  blockPreviewUrl: "URL di anteprima del blocco",
3573
- blockPreviewUrlHelp: "Facoltativo. Una pagina del tuo frontend che renderizza un singolo blocco, mostrata in grande quando si clicca una scheda della galleria, ad es. http://localhost:3026/it/block-preview/'{name}'/'{variant}'. Segnaposto: '{uid}', '{name}' (dopo il punto), '{category}' (prima del punto), '{variant}' (default), '{locale}'. Vuoto usa la route di anteprima pagina se esiste, altrimenti l'immagine.",
3578
+ blockPreviewUrlHelp: "Facoltativo. Una pagina del tuo frontend che renderizza un singolo blocco, mostrata in grande dalla lente di ingrandimento di una scheda della galleria, ad es. http://localhost:3026/it/block-preview/'{name}'/'{variant}'. Segnaposto: '{uid}', '{name}' (dopo il punto), '{category}' (prima del punto), '{variant}' (default), '{locale}'. Vuoto usa la route di anteprima pagina se esiste, altrimenti l'immagine.",
3574
3579
  rowActions: "Azioni per {label}",
3575
3580
  selectBlock: "Seleziona {label}",
3576
3581
  hiddenBadge: "Nascosto",
@@ -3991,7 +3996,7 @@ var nl_default = {
3991
3996
  blockPreview: "Livevoorbeeld van het blok",
3992
3997
  livePreviewFailed: "Livevoorbeeld niet beschikbaar, de afbeelding wordt getoond.",
3993
3998
  blockPreviewUrl: "URL van blokvoorbeeld",
3994
- blockPreviewUrlHelp: "Optioneel. Een pagina van uw frontend die \xE9\xE9n blok rendert, groot getoond wanneer op een galerijkaart wordt geklikt, bijv. http://localhost:3026/nl/block-preview/'{name}'/'{variant}'. Plaatshouders: '{uid}', '{name}' (na de punt), '{category}' (voor de punt), '{variant}' (default), '{locale}'. Leeg gebruikt de paginavoorbeeldroute als die er is, anders de afbeelding.",
3999
+ blockPreviewUrlHelp: "Optioneel. Een pagina van uw frontend die \xE9\xE9n blok rendert, groot getoond via het vergrootglas van een galerijkaart, bijv. http://localhost:3026/nl/block-preview/'{name}'/'{variant}'. Plaatshouders: '{uid}', '{name}' (na de punt), '{category}' (voor de punt), '{variant}' (default), '{locale}'. Leeg gebruikt de paginavoorbeeldroute als die er is, anders de afbeelding.",
3995
4000
  rowActions: "Acties voor {label}",
3996
4001
  selectBlock: "{label} selecteren",
3997
4002
  hiddenBadge: "Verborgen",
@@ -5836,10 +5841,6 @@ var Badge = import_styled_components5.default.span`
5836
5841
  `;
5837
5842
  var CardBox = import_styled_components5.default.div`
5838
5843
  position: relative;
5839
- &:hover .bs-quick,
5840
- &:focus-within .bs-quick {
5841
- opacity: 1;
5842
- }
5843
5844
  `;
5844
5845
  var CardBadges = import_styled_components5.default.div`
5845
5846
  position: absolute;
@@ -5862,17 +5863,13 @@ var Round = import_styled_components5.default.button`
5862
5863
  padding: 0;
5863
5864
  border-radius: 50%;
5864
5865
  cursor: pointer;
5865
- border: 1px solid ${({ theme, $primary }) => $primary ? theme.colors.primary600 : theme.colors.neutral200};
5866
- color: ${({ theme, $on, $primary }) => $primary ? theme.colors.neutral0 : $on ? theme.colors.warning500 : theme.colors.neutral600};
5867
- background: ${({ theme, $primary }) => $primary ? theme.colors.primary600 : theme.colors.neutral0};
5866
+ border: 1px solid ${({ theme }) => theme.colors.neutral200};
5867
+ color: ${({ theme, $on }) => $on ? theme.colors.warning500 : theme.colors.neutral600};
5868
+ background: ${({ theme }) => theme.colors.neutral0};
5868
5869
  &:focus-visible {
5869
5870
  outline: 2px solid ${({ theme }) => theme.colors.primary600};
5870
5871
  outline-offset: 1px;
5871
5872
  }
5872
- &.bs-quick {
5873
- opacity: 0;
5874
- transition: opacity 120ms;
5875
- }
5876
5873
  `;
5877
5874
  var Mono = import_styled_components5.default.code`
5878
5875
  font-size: 12px;
@@ -5947,25 +5944,13 @@ function Badges({ entry, t, tags }) {
5947
5944
  }
5948
5945
  function Card({ entry, palette, showFields, starred, active, onOpen, onInsert, onStar }) {
5949
5946
  const t = useMessages();
5950
- const timer = React6.useRef(null);
5951
- React6.useEffect(() => () => clearTimeout(timer.current), []);
5952
5947
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(CardBox, { title: entry.uid, "data-testid": `blockscene-${entry.uid}`, children: [
5953
5948
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
5954
5949
  Tile,
5955
5950
  {
5956
5951
  type: "button",
5957
5952
  $active: active,
5958
- onClick: (event) => {
5959
- clearTimeout(timer.current);
5960
- if (event.detail === 2) onInsert();
5961
- else if (event.detail === 1) timer.current = setTimeout(onOpen, 220);
5962
- else onOpen();
5963
- },
5964
- onKeyDown: (event) => {
5965
- if (event.key !== "Enter") return;
5966
- event.preventDefault();
5967
- onInsert();
5968
- },
5953
+ onClick: onInsert,
5969
5954
  children: [
5970
5955
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
5971
5956
  Thumb,
@@ -6010,14 +5995,12 @@ function Card({ entry, palette, showFields, starred, active, onOpen, onInsert, o
6010
5995
  Round,
6011
5996
  {
6012
5997
  type: "button",
6013
- className: "bs-quick",
6014
5998
  style: { top: 46 },
6015
- $primary: true,
6016
- "aria-label": t.f("quickInsert", { label: entry.label }),
6017
- title: t.f("quickInsert", { label: entry.label }),
6018
- onClick: onInsert,
6019
- "data-testid": `gallery-quick-${entry.uid}`,
6020
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Icon, { name: "plus", size: 16 })
5999
+ "aria-label": t.f("rowPreview", { label: entry.label }),
6000
+ title: t.f("rowPreview", { label: entry.label }),
6001
+ onClick: onOpen,
6002
+ "data-testid": `gallery-preview-${entry.uid}`,
6003
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Icon, { name: "seo", size: 16 })
6021
6004
  }
6022
6005
  )
6023
6006
  ] });
package/dist/admin.mjs CHANGED
@@ -43,6 +43,10 @@ var require_groups = __commonJS({
43
43
  return Object.keys(out).length ? out : null;
44
44
  }
45
45
  var uidOf = (row) => typeof row?.__component === "string" ? row.__component : "";
46
+ var invalidComponents = (rows, allowed) => {
47
+ const uids = new Set(Array.isArray(allowed) ? allowed : []);
48
+ return (Array.isArray(rows) ? rows : []).flatMap((row, index) => uidOf(row) && !uids.has(uidOf(row)) ? [{ index, uid: uidOf(row) }] : []);
49
+ };
46
50
  var isOpener2 = (row, g) => Boolean(g) && Object.prototype.hasOwnProperty.call(g, uidOf(row));
47
51
  var isClose2 = (row, g) => Boolean(g) && Object.values(g).includes(uidOf(row));
48
52
  var blockKey3 = (row) => row && row.id != null ? `${row.__component}#${row.id}` : row && row.__temp_key__ != null ? String(row.__temp_key__) : null;
@@ -149,6 +153,7 @@ var require_groups = __commonJS({
149
153
  module.exports = {
150
154
  safeGroups: safeGroups2,
151
155
  validateGroups: validateGroups3,
156
+ invalidComponents,
152
157
  groupRange: groupRange3,
153
158
  groupRows: groupRows2,
154
159
  topLevelRanges: topLevelRanges2,
@@ -1063,7 +1068,7 @@ var en_default = {
1063
1068
  blockPreview: "Live block preview",
1064
1069
  livePreviewFailed: "Live preview unavailable, showing the image.",
1065
1070
  blockPreviewUrl: "Block preview URL",
1066
- blockPreviewUrlHelp: "Optional. A page of your frontend that renders one block on its own, shown large when a gallery card is clicked, e.g. http://localhost:3026/pt-BR/block-preview/'{name}'/'{variant}'. Placeholders: '{uid}', '{name}' (after the dot), '{category}' (before the dot), '{variant}' (default), '{locale}'. Empty uses the page preview route when there is one, else the image.",
1071
+ blockPreviewUrlHelp: "Optional. A page of your frontend that renders one block on its own, shown large from a gallery card's magnifying glass, e.g. http://localhost:3026/pt-BR/block-preview/'{name}'/'{variant}'. Placeholders: '{uid}', '{name}' (after the dot), '{category}' (before the dot), '{variant}' (default), '{locale}'. Empty uses the page preview route when there is one, else the image.",
1067
1072
  rowActions: "Actions for {label}",
1068
1073
  selectBlock: "Select {label}",
1069
1074
  hiddenBadge: "Hidden",
@@ -1484,7 +1489,7 @@ var pt_BR_default = {
1484
1489
  blockPreview: "Preview ao vivo do bloco",
1485
1490
  livePreviewFailed: "Preview ao vivo indispon\xEDvel, mostrando a imagem.",
1486
1491
  blockPreviewUrl: "URL de preview do bloco",
1487
- blockPreviewUrlHelp: "Opcional. Uma p\xE1gina do seu frontend que renderiza um bloco sozinho, mostrada em tamanho grande ao clicar num card da galeria, por exemplo http://localhost:3026/pt-BR/block-preview/'{name}'/'{variant}'. Marcadores: '{uid}', '{name}' (depois do ponto), '{category}' (antes do ponto), '{variant}' (default), '{locale}'. Vazio usa a rota de preview de p\xE1gina quando existir, sen\xE3o a imagem.",
1492
+ blockPreviewUrlHelp: "Opcional. Uma p\xE1gina do seu frontend que renderiza um bloco sozinho, mostrada em tamanho grande pela lupa do card da galeria, por exemplo http://localhost:3026/pt-BR/block-preview/'{name}'/'{variant}'. Marcadores: '{uid}', '{name}' (depois do ponto), '{category}' (antes do ponto), '{variant}' (default), '{locale}'. Vazio usa a rota de preview de p\xE1gina quando existir, sen\xE3o a imagem.",
1488
1493
  rowActions: "A\xE7\xF5es de {label}",
1489
1494
  selectBlock: "Selecionar {label}",
1490
1495
  hiddenBadge: "Oculto",
@@ -1905,7 +1910,7 @@ var pt_default = {
1905
1910
  blockPreview: "Pr\xE9-visualiza\xE7\xE3o ao vivo do bloco",
1906
1911
  livePreviewFailed: "Pr\xE9-visualiza\xE7\xE3o ao vivo indispon\xEDvel, a mostrar a imagem.",
1907
1912
  blockPreviewUrl: "URL de pr\xE9-visualiza\xE7\xE3o do bloco",
1908
- blockPreviewUrlHelp: "Opcional. Uma p\xE1gina do seu frontend que renderiza um bloco isolado, mostrada em grande ao clicar num cart\xE3o da galeria, por exemplo http://localhost:3026/pt-PT/block-preview/'{name}'/'{variant}'. Marcadores: '{uid}', '{name}' (depois do ponto), '{category}' (antes do ponto), '{variant}' (default), '{locale}'. Vazio usa a rota de pr\xE9-visualiza\xE7\xE3o de p\xE1gina quando existir, caso contr\xE1rio a imagem.",
1913
+ blockPreviewUrlHelp: "Opcional. Uma p\xE1gina do seu frontend que renderiza um bloco isolado, mostrada em grande pela lupa do cart\xE3o da galeria, por exemplo http://localhost:3026/pt-PT/block-preview/'{name}'/'{variant}'. Marcadores: '{uid}', '{name}' (depois do ponto), '{category}' (antes do ponto), '{variant}' (default), '{locale}'. Vazio usa a rota de pr\xE9-visualiza\xE7\xE3o de p\xE1gina quando existir, caso contr\xE1rio a imagem.",
1909
1914
  rowActions: "A\xE7\xF5es de {label}",
1910
1915
  selectBlock: "Selecionar {label}",
1911
1916
  hiddenBadge: "Oculto",
@@ -2326,7 +2331,7 @@ var fr_default = {
2326
2331
  blockPreview: "Aper\xE7u en direct du bloc",
2327
2332
  livePreviewFailed: "Aper\xE7u en direct indisponible, l'image est affich\xE9e.",
2328
2333
  blockPreviewUrl: "URL d'aper\xE7u de bloc",
2329
- blockPreviewUrlHelp: "Facultatif. Une page de votre frontend qui affiche un seul bloc, montr\xE9e en grand quand on clique sur une carte de la galerie, par ex. http://localhost:3026/fr/block-preview/'{name}'/'{variant}'. Variables : '{uid}', '{name}' (apr\xE8s le point), '{category}' (avant le point), '{variant}' (default), '{locale}'. Vide : la route d'aper\xE7u de page si elle existe, sinon l'image.",
2334
+ blockPreviewUrlHelp: "Facultatif. Une page de votre frontend qui affiche un seul bloc, montr\xE9e en grand depuis la loupe d'une carte de la galerie, par ex. http://localhost:3026/fr/block-preview/'{name}'/'{variant}'. Variables : '{uid}', '{name}' (apr\xE8s le point), '{category}' (avant le point), '{variant}' (default), '{locale}'. Vide : la route d'aper\xE7u de page si elle existe, sinon l'image.",
2330
2335
  rowActions: "Actions pour {label}",
2331
2336
  selectBlock: "S\xE9lectionner {label}",
2332
2337
  hiddenBadge: "Masqu\xE9",
@@ -2747,7 +2752,7 @@ var es_default = {
2747
2752
  blockPreview: "Vista previa en vivo del bloque",
2748
2753
  livePreviewFailed: "Vista previa en vivo no disponible, se muestra la imagen.",
2749
2754
  blockPreviewUrl: "URL de vista previa del bloque",
2750
- blockPreviewUrlHelp: "Opcional. Una p\xE1gina de su frontend que renderiza un solo bloque, mostrada en grande al hacer clic en una tarjeta de la galer\xEDa, p. ej. http://localhost:3026/es/block-preview/'{name}'/'{variant}'. Marcadores: '{uid}', '{name}' (tras el punto), '{category}' (antes del punto), '{variant}' (default), '{locale}'. Vac\xEDa usa la ruta de vista previa de p\xE1gina si existe; si no, la imagen.",
2755
+ blockPreviewUrlHelp: "Opcional. Una p\xE1gina de su frontend que renderiza un solo bloque, mostrada en grande desde la lupa de una tarjeta de la galer\xEDa, p. ej. http://localhost:3026/es/block-preview/'{name}'/'{variant}'. Marcadores: '{uid}', '{name}' (tras el punto), '{category}' (antes del punto), '{variant}' (default), '{locale}'. Vac\xEDa usa la ruta de vista previa de p\xE1gina si existe; si no, la imagen.",
2751
2756
  rowActions: "Acciones de {label}",
2752
2757
  selectBlock: "Seleccionar {label}",
2753
2758
  hiddenBadge: "Oculto",
@@ -3168,7 +3173,7 @@ var de_default = {
3168
3173
  blockPreview: "Live-Vorschau des Blocks",
3169
3174
  livePreviewFailed: "Live-Vorschau nicht verf\xFCgbar, das Bild wird angezeigt.",
3170
3175
  blockPreviewUrl: "Block-Vorschau-URL",
3171
- blockPreviewUrlHelp: "Optional. Eine Seite Ihres Frontends, die einen einzelnen Block rendert und beim Klick auf eine Galeriekarte gro\xDF angezeigt wird, z. B. http://localhost:3026/de/block-preview/'{name}'/'{variant}'. Platzhalter: '{uid}', '{name}' (nach dem Punkt), '{category}' (vor dem Punkt), '{variant}' (default), '{locale}'. Leer verwendet die Seitenvorschau-Route, falls vorhanden, sonst das Bild.",
3176
+ blockPreviewUrlHelp: "Optional. Eine Seite Ihres Frontends, die einen einzelnen Block rendert und \xFCber die Lupe einer Galeriekarte gro\xDF angezeigt wird, z. B. http://localhost:3026/de/block-preview/'{name}'/'{variant}'. Platzhalter: '{uid}', '{name}' (nach dem Punkt), '{category}' (vor dem Punkt), '{variant}' (default), '{locale}'. Leer verwendet die Seitenvorschau-Route, falls vorhanden, sonst das Bild.",
3172
3177
  rowActions: "Aktionen f\xFCr {label}",
3173
3178
  selectBlock: "{label} ausw\xE4hlen",
3174
3179
  hiddenBadge: "Ausgeblendet",
@@ -3589,7 +3594,7 @@ var it_default = {
3589
3594
  blockPreview: "Anteprima dal vivo del blocco",
3590
3595
  livePreviewFailed: "Anteprima dal vivo non disponibile, viene mostrata l'immagine.",
3591
3596
  blockPreviewUrl: "URL di anteprima del blocco",
3592
- blockPreviewUrlHelp: "Facoltativo. Una pagina del tuo frontend che renderizza un singolo blocco, mostrata in grande quando si clicca una scheda della galleria, ad es. http://localhost:3026/it/block-preview/'{name}'/'{variant}'. Segnaposto: '{uid}', '{name}' (dopo il punto), '{category}' (prima del punto), '{variant}' (default), '{locale}'. Vuoto usa la route di anteprima pagina se esiste, altrimenti l'immagine.",
3597
+ blockPreviewUrlHelp: "Facoltativo. Una pagina del tuo frontend che renderizza un singolo blocco, mostrata in grande dalla lente di ingrandimento di una scheda della galleria, ad es. http://localhost:3026/it/block-preview/'{name}'/'{variant}'. Segnaposto: '{uid}', '{name}' (dopo il punto), '{category}' (prima del punto), '{variant}' (default), '{locale}'. Vuoto usa la route di anteprima pagina se esiste, altrimenti l'immagine.",
3593
3598
  rowActions: "Azioni per {label}",
3594
3599
  selectBlock: "Seleziona {label}",
3595
3600
  hiddenBadge: "Nascosto",
@@ -4010,7 +4015,7 @@ var nl_default = {
4010
4015
  blockPreview: "Livevoorbeeld van het blok",
4011
4016
  livePreviewFailed: "Livevoorbeeld niet beschikbaar, de afbeelding wordt getoond.",
4012
4017
  blockPreviewUrl: "URL van blokvoorbeeld",
4013
- blockPreviewUrlHelp: "Optioneel. Een pagina van uw frontend die \xE9\xE9n blok rendert, groot getoond wanneer op een galerijkaart wordt geklikt, bijv. http://localhost:3026/nl/block-preview/'{name}'/'{variant}'. Plaatshouders: '{uid}', '{name}' (na de punt), '{category}' (voor de punt), '{variant}' (default), '{locale}'. Leeg gebruikt de paginavoorbeeldroute als die er is, anders de afbeelding.",
4018
+ blockPreviewUrlHelp: "Optioneel. Een pagina van uw frontend die \xE9\xE9n blok rendert, groot getoond via het vergrootglas van een galerijkaart, bijv. http://localhost:3026/nl/block-preview/'{name}'/'{variant}'. Plaatshouders: '{uid}', '{name}' (na de punt), '{category}' (voor de punt), '{variant}' (default), '{locale}'. Leeg gebruikt de paginavoorbeeldroute als die er is, anders de afbeelding.",
4014
4019
  rowActions: "Acties voor {label}",
4015
4020
  selectBlock: "{label} selecteren",
4016
4021
  hiddenBadge: "Verborgen",
@@ -5855,10 +5860,6 @@ var Badge = styled5.span`
5855
5860
  `;
5856
5861
  var CardBox = styled5.div`
5857
5862
  position: relative;
5858
- &:hover .bs-quick,
5859
- &:focus-within .bs-quick {
5860
- opacity: 1;
5861
- }
5862
5863
  `;
5863
5864
  var CardBadges = styled5.div`
5864
5865
  position: absolute;
@@ -5881,17 +5882,13 @@ var Round = styled5.button`
5881
5882
  padding: 0;
5882
5883
  border-radius: 50%;
5883
5884
  cursor: pointer;
5884
- border: 1px solid ${({ theme, $primary }) => $primary ? theme.colors.primary600 : theme.colors.neutral200};
5885
- color: ${({ theme, $on, $primary }) => $primary ? theme.colors.neutral0 : $on ? theme.colors.warning500 : theme.colors.neutral600};
5886
- background: ${({ theme, $primary }) => $primary ? theme.colors.primary600 : theme.colors.neutral0};
5885
+ border: 1px solid ${({ theme }) => theme.colors.neutral200};
5886
+ color: ${({ theme, $on }) => $on ? theme.colors.warning500 : theme.colors.neutral600};
5887
+ background: ${({ theme }) => theme.colors.neutral0};
5887
5888
  &:focus-visible {
5888
5889
  outline: 2px solid ${({ theme }) => theme.colors.primary600};
5889
5890
  outline-offset: 1px;
5890
5891
  }
5891
- &.bs-quick {
5892
- opacity: 0;
5893
- transition: opacity 120ms;
5894
- }
5895
5892
  `;
5896
5893
  var Mono = styled5.code`
5897
5894
  font-size: 12px;
@@ -5966,25 +5963,13 @@ function Badges({ entry, t, tags }) {
5966
5963
  }
5967
5964
  function Card({ entry, palette, showFields, starred, active, onOpen, onInsert, onStar }) {
5968
5965
  const t = useMessages();
5969
- const timer = React6.useRef(null);
5970
- React6.useEffect(() => () => clearTimeout(timer.current), []);
5971
5966
  return /* @__PURE__ */ jsxs5(CardBox, { title: entry.uid, "data-testid": `blockscene-${entry.uid}`, children: [
5972
5967
  /* @__PURE__ */ jsxs5(
5973
5968
  Tile,
5974
5969
  {
5975
5970
  type: "button",
5976
5971
  $active: active,
5977
- onClick: (event) => {
5978
- clearTimeout(timer.current);
5979
- if (event.detail === 2) onInsert();
5980
- else if (event.detail === 1) timer.current = setTimeout(onOpen, 220);
5981
- else onOpen();
5982
- },
5983
- onKeyDown: (event) => {
5984
- if (event.key !== "Enter") return;
5985
- event.preventDefault();
5986
- onInsert();
5987
- },
5972
+ onClick: onInsert,
5988
5973
  children: [
5989
5974
  /* @__PURE__ */ jsx7(
5990
5975
  Thumb,
@@ -6029,14 +6014,12 @@ function Card({ entry, palette, showFields, starred, active, onOpen, onInsert, o
6029
6014
  Round,
6030
6015
  {
6031
6016
  type: "button",
6032
- className: "bs-quick",
6033
6017
  style: { top: 46 },
6034
- $primary: true,
6035
- "aria-label": t.f("quickInsert", { label: entry.label }),
6036
- title: t.f("quickInsert", { label: entry.label }),
6037
- onClick: onInsert,
6038
- "data-testid": `gallery-quick-${entry.uid}`,
6039
- children: /* @__PURE__ */ jsx7(Icon, { name: "plus", size: 16 })
6018
+ "aria-label": t.f("rowPreview", { label: entry.label }),
6019
+ title: t.f("rowPreview", { label: entry.label }),
6020
+ onClick: onOpen,
6021
+ "data-testid": `gallery-preview-${entry.uid}`,
6022
+ children: /* @__PURE__ */ jsx7(Icon, { name: "seo", size: 16 })
6040
6023
  }
6041
6024
  )
6042
6025
  ] });
package/dist/server.cjs CHANGED
@@ -21,6 +21,10 @@ var require_groups = __commonJS({
21
21
  return Object.keys(out).length ? out : null;
22
22
  }
23
23
  var uidOf = (row) => typeof row?.__component === "string" ? row.__component : "";
24
+ var invalidComponents2 = (rows, allowed) => {
25
+ const uids = new Set(Array.isArray(allowed) ? allowed : []);
26
+ return (Array.isArray(rows) ? rows : []).flatMap((row, index) => uidOf(row) && !uids.has(uidOf(row)) ? [{ index, uid: uidOf(row) }] : []);
27
+ };
24
28
  var isOpener = (row, g) => Boolean(g) && Object.prototype.hasOwnProperty.call(g, uidOf(row));
25
29
  var isClose = (row, g) => Boolean(g) && Object.values(g).includes(uidOf(row));
26
30
  var blockKey = (row) => row && row.id != null ? `${row.__component}#${row.id}` : row && row.__temp_key__ != null ? String(row.__temp_key__) : null;
@@ -127,6 +131,7 @@ var require_groups = __commonJS({
127
131
  module2.exports = {
128
132
  safeGroups: safeGroups2,
129
133
  validateGroups: validateGroups2,
134
+ invalidComponents: invalidComponents2,
130
135
  groupRange,
131
136
  groupRows,
132
137
  topLevelRanges,
@@ -1686,7 +1691,7 @@ var require_history = __commonJS({
1686
1691
 
1687
1692
  // server/index.js
1688
1693
  var { PLUGIN, TEMPLATES, TYPOLOGIES, DEFAULTS, catalog, validateSettings, validateFields, validateVariants, schemaMetadata, mergeSaved, layer, overrides, validatePrefs, mergePrefs, safeUrl, fail } = require_settings();
1689
- var { safeGroups, validateGroups, layoutFields } = require_groups();
1694
+ var { safeGroups, validateGroups, invalidComponents, layoutFields } = require_groups();
1690
1695
  var { hiddenName, injectHidden, hideFromBuilder, registerHiddenStrip } = require_hidden();
1691
1696
  var { CRON, covers, eventContentType, historyService, historyController, registerHistory, registerPurge } = require_history();
1692
1697
  var { trashContentType, trashService, trashController } = require_trash();
@@ -1719,6 +1724,18 @@ var contentTypeUids = (strapi) => Object.fromEntries(Object.entries(strapi.conte
1719
1724
  var apiUids = (strapi) => Object.keys(strapi.contentTypes || {}).filter((uid) => uid.startsWith("api::"));
1720
1725
  var zonesOf = (schema) => Object.entries(schema?.attributes || {}).filter(([, attr]) => attr?.type === "dynamiczone").map(([name]) => name);
1721
1726
  var label = (error) => `row ${error.index + 1}: ${error.code === "closeBeforeOpen" ? `close marker ${error.uid} has no open marker before it` : error.code === "mismatch" ? `close marker ${error.uid} does not match the open group ${error.open} (expected ${error.expected})` : `group ${error.uid} is not closed (expected ${error.expected})`}`;
1727
+ var componentLabel = (error) => `row ${error.index + 1}: component "${error.uid}" is not allowed in this Dynamic Zone`;
1728
+ function checkComponents(strapi, uid, entry) {
1729
+ const schema = strapi.getModel(uid);
1730
+ for (const name of zonesOf(schema)) {
1731
+ if (!Array.isArray(entry?.[name])) continue;
1732
+ const errors = invalidComponents(entry[name], schema.attributes[name].components);
1733
+ if (errors.length) fail(
1734
+ `Block components in "${name}" are not allowed by its schema (${errors.map(componentLabel).join("; ")}).`,
1735
+ { plugin: PLUGIN, field: name, errors: errors.map((error) => ({ path: [name, error.index], message: componentLabel(error), name: "ValidationError", code: "componentNotAllowed", uid: error.uid })) }
1736
+ );
1737
+ }
1738
+ }
1722
1739
  function checkZones(strapi, uid, entry, groups) {
1723
1740
  for (const name of zonesOf(strapi.getModel(uid))) {
1724
1741
  const errors = validateGroups(Array.isArray(entry?.[name]) ? entry[name] : [], groups);
@@ -1728,11 +1745,10 @@ function checkZones(strapi, uid, entry, groups) {
1728
1745
  );
1729
1746
  }
1730
1747
  }
1731
- function registerPublishGuard(strapi) {
1748
+ function registerZoneGuards(strapi) {
1732
1749
  const plugin = strapi.plugin(PLUGIN);
1733
1750
  const groups = safeGroups(plugin.config("groups"), componentUids(strapi));
1734
1751
  if (plugin.config("groups") && !groups) strapi.log.warn(`[${PLUGIN}] "groups" config ignored: expected { "<open component uid>": "<close component uid>" } with existing components; every block is treated as ordinary.`);
1735
- if (!groups) return;
1736
1752
  const active = async (uid) => {
1737
1753
  if (plugin.config("disabled") === true) return false;
1738
1754
  const settings = await plugin.service("settings").get();
@@ -1749,12 +1765,14 @@ function registerPublishGuard(strapi) {
1749
1765
  strapi.documents.use(async (ctx, next) => {
1750
1766
  const { action, params = {} } = ctx;
1751
1767
  const publishing = action === "publish" || (action === "create" || action === "update") && params.status === "published";
1752
- if (!publishing || !ctx.uid) return next();
1768
+ if (!ctx.uid || !["create", "update", "publish"].includes(action)) return next();
1753
1769
  const model = strapi.getModel(ctx.uid);
1754
1770
  const zones = zonesOf(model);
1755
1771
  if (!zones.length || !await active(ctx.uid)) return next();
1772
+ if (action === "create" || action === "update") checkComponents(strapi, ctx.uid, params.data || {});
1773
+ if (!publishing) return next();
1756
1774
  if (action === "create") {
1757
- checkZones(strapi, ctx.uid, params.data || {}, groups);
1775
+ if (groups) checkZones(strapi, ctx.uid, params.data || {}, groups);
1758
1776
  return next();
1759
1777
  }
1760
1778
  const populate = Object.fromEntries(zones.map((name) => [name, true]));
@@ -1769,7 +1787,8 @@ function registerPublishGuard(strapi) {
1769
1787
  }
1770
1788
  for (const draft of drafts) {
1771
1789
  const entry = action === "update" ? { ...draft || {}, ...Object.fromEntries(zones.filter((name) => Array.isArray(params.data?.[name])).map((name) => [name, params.data[name]])) } : draft;
1772
- checkZones(strapi, ctx.uid, entry, groups);
1790
+ checkComponents(strapi, ctx.uid, entry);
1791
+ if (groups) checkZones(strapi, ctx.uid, entry, groups);
1773
1792
  }
1774
1793
  return next();
1775
1794
  });
@@ -1778,20 +1797,31 @@ function registerPublishGuard(strapi) {
1778
1797
  const published = (data) => Boolean(data && data.publishedAt);
1779
1798
  strapi.db.lifecycles.subscribe({
1780
1799
  async beforeCreate(event) {
1781
- if (published(event.params?.data) && await active(event.model.uid)) checkZones(strapi, event.model.uid, event.params.data, groups);
1800
+ const data = event.params?.data, uid = event.model.uid;
1801
+ if (!zonesOf(strapi.getModel(uid)).length || !await active(uid)) return;
1802
+ checkComponents(strapi, uid, data || {});
1803
+ if (groups && published(data)) checkZones(strapi, uid, data, groups);
1782
1804
  },
1783
1805
  async beforeUpdate(event) {
1784
1806
  const { data, where } = event.params || {};
1785
1807
  const zones = zonesOf(strapi.getModel(event.model.uid));
1786
- if (!published(data) || !zones.length || !await active(event.model.uid)) return;
1808
+ if (!zones.length || !await active(event.model.uid)) return;
1809
+ checkComponents(strapi, event.model.uid, data || {});
1810
+ if (!published(data)) return;
1787
1811
  const entry = zones.every((name) => Array.isArray(data[name])) ? data : { ...await strapi.entityService.findOne(event.model.uid, where?.id, { populate: zones }), ...data };
1788
- checkZones(strapi, event.model.uid, entry, groups);
1812
+ checkComponents(strapi, event.model.uid, entry);
1813
+ if (groups) checkZones(strapi, event.model.uid, entry, groups);
1789
1814
  },
1790
1815
  async beforeUpdateMany(event) {
1791
1816
  const { data, where } = event.params || {};
1792
1817
  const zones = zonesOf(strapi.getModel(event.model.uid));
1793
- if (!published(data) || !zones.length || !await active(event.model.uid)) return;
1794
- for (const entry of await strapi.entityService.findMany(event.model.uid, { filters: where, populate: zones })) checkZones(strapi, event.model.uid, entry, groups);
1818
+ if (!zones.length || !await active(event.model.uid)) return;
1819
+ checkComponents(strapi, event.model.uid, data || {});
1820
+ if (!published(data)) return;
1821
+ for (const entry of await strapi.entityService.findMany(event.model.uid, { filters: where, populate: zones })) {
1822
+ checkComponents(strapi, event.model.uid, entry);
1823
+ if (groups) checkZones(strapi, event.model.uid, entry, groups);
1824
+ }
1795
1825
  }
1796
1826
  });
1797
1827
  }
@@ -1846,7 +1876,7 @@ module.exports = {
1846
1876
  strapi.plugin(PLUGIN).service("settings").projectDefaults();
1847
1877
  strapi.plugin(PLUGIN).service("settings").fields();
1848
1878
  strapi.plugin(PLUGIN).service("settings").variants();
1849
- registerPublishGuard(strapi);
1879
+ registerZoneGuards(strapi);
1850
1880
  registerHiddenStrip(strapi, hiddenName(strapi.plugin(PLUGIN).config("hiddenAttribute")), strapi.plugin(PLUGIN));
1851
1881
  if (withHidden.length) hideFromBuilder(typeof strapi.get === "function" ? strapi.get("services") : strapi.container.get("services"), withHidden, hiddenName(strapi.plugin(PLUGIN).config("hiddenAttribute")));
1852
1882
  if (STRAPI5) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aduptive/strapi-blockscene",
3
- "version": "2.0.0-alpha.10",
3
+ "version": "2.0.0-alpha.11",
4
4
  "description": "Visual component gallery for native Strapi dynamic zones.",
5
5
  "license": "MIT",
6
6
  "author": "Andrea Scarpello <aduptive@gmail.com>",