Add grouping feature
This commit is contained in:
52
README.md
52
README.md
@@ -23,6 +23,7 @@ luxtools provides DokuWiki syntax that:
|
|||||||
|
|
||||||
- Lists a directory's direct children (files + folders) or files matching a glob pattern
|
- Lists a directory's direct children (files + folders) or files matching a glob pattern
|
||||||
- Renders an image thumbnail gallery (with lightbox)
|
- Renders an image thumbnail gallery (with lightbox)
|
||||||
|
- Groups multiple `{{image>...}}` blocks in compact grid/flex layouts
|
||||||
- Provides "open this folder/path" links for local workflows
|
- Provides "open this folder/path" links for local workflows
|
||||||
- Embeds file-backed scratchpads with a minimal inline editor (no wiki revisions)
|
- Embeds file-backed scratchpads with a minimal inline editor (no wiki revisions)
|
||||||
- Links a page to a media folder via a UUID (.pagelink), enabling a `blobs/` alias
|
- Links a page to a media folder via a UUID (.pagelink), enabling a `blobs/` alias
|
||||||
@@ -310,7 +311,52 @@ The image links to the full-size version when clicked.
|
|||||||
|
|
||||||
Remote images (HTTP/HTTPS URLs) are linked directly without proxying or thumbnailing.
|
Remote images (HTTP/HTTPS URLs) are linked directly without proxying or thumbnailing.
|
||||||
|
|
||||||
### 5) Open a local path/folder (best-effort)
|
### 5) Group multiple image boxes compactly
|
||||||
|
|
||||||
|
Use `<grouping> ... </grouping>` to arrange multiple `{{image>...}}` entries in less vertical space.
|
||||||
|
|
||||||
|
```text
|
||||||
|
<grouping>
|
||||||
|
{{image>/Scape/photos/1.jpg|One|300}}
|
||||||
|
{{image>/Scape/photos/2.jpg|Two|300}}
|
||||||
|
{{image>/Scape/photos/3.jpg|Three|300}}
|
||||||
|
{{image>/Scape/photos/4.jpg|Four|300}}
|
||||||
|
</grouping>
|
||||||
|
|
||||||
|
<grouping layout="flex" gap="0" justify="start" align="start">
|
||||||
|
{{image>/Scape/photos/1.jpg|One|220}}
|
||||||
|
{{image>/Scape/photos/2.jpg|Two|220}}
|
||||||
|
{{image>/Scape/photos/3.jpg|Three|220}}
|
||||||
|
</grouping>
|
||||||
|
|
||||||
|
<grouping layout="grid" cols="3" gap="0.4rem">
|
||||||
|
{{image>/Scape/photos/1.jpg|One|260}}
|
||||||
|
{{image>/Scape/photos/2.jpg|Two|260}}
|
||||||
|
{{image>/Scape/photos/3.jpg|Three|260}}
|
||||||
|
</grouping>
|
||||||
|
|
||||||
|
<grouping layout="flex" gap="0.5rem" justify="space-between" align="center">
|
||||||
|
{{image>/Scape/photos/1.jpg|One|220}}
|
||||||
|
{{image>/Scape/photos/2.jpg|Two|220}}
|
||||||
|
{{image>/Scape/photos/3.jpg|Three|220}}
|
||||||
|
</grouping>
|
||||||
|
```
|
||||||
|
|
||||||
|
Supported attributes on the opening tag:
|
||||||
|
|
||||||
|
- `layout`: `flex` (default) or `grid`
|
||||||
|
- `cols`: integer >= 1 (default `2`, used by `grid`)
|
||||||
|
- `gap`: CSS length token such as `0`, `0.6rem`, `8px` (default `0`)
|
||||||
|
- `justify`: `start`, `center`, `end`, `space-between`, `space-around`, `space-evenly` (default `start`)
|
||||||
|
- `align`: `start`, `center`, `end`, `stretch`, `baseline` (default `start`)
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- The wrapper only controls layout. It adds no own border/background/frame.
|
||||||
|
- Invalid values silently fall back to defaults.
|
||||||
|
- Unknown attributes render a small warning string, e.g. `[grouping: unknown option(s): gpa]`.
|
||||||
|
- Existing standalone `{{image>...}}` behavior is unchanged outside `<grouping>`.
|
||||||
|
|
||||||
|
### 6) Open a local path/folder (best-effort)
|
||||||
|
|
||||||
```
|
```
|
||||||
{{open>/Scape/projects|Open projects folder}}
|
{{open>/Scape/projects|Open projects folder}}
|
||||||
@@ -322,7 +368,7 @@ Behaviour:
|
|||||||
- Prefer calling the configured local client service (open_service_url).
|
- Prefer calling the configured local client service (open_service_url).
|
||||||
- Fall back to opening a file:// URL in a new tab (often blocked by browsers).
|
- Fall back to opening a file:// URL in a new tab (often blocked by browsers).
|
||||||
|
|
||||||
### 6) Scratchpads (shared, file-backed, no page revisions)
|
### 7) Scratchpads (shared, file-backed, no page revisions)
|
||||||
|
|
||||||
```
|
```
|
||||||
{{scratchpad>start}}
|
{{scratchpad>start}}
|
||||||
@@ -330,7 +376,7 @@ Behaviour:
|
|||||||
|
|
||||||
Scratchpads render the referenced file as wikitext and (when you have edit rights on the host page) provide an inline editor that saves directly to the backing file.
|
Scratchpads render the referenced file as wikitext and (when you have edit rights on the host page) provide an inline editor that saves directly to the backing file.
|
||||||
|
|
||||||
### 7) Link Favicons (automatic)
|
### 8) Link Favicons (automatic)
|
||||||
|
|
||||||
External links automatically display the favicon of the linked website. This feature:
|
External links automatically display the favicon of the linked website. This feature:
|
||||||
|
|
||||||
|
|||||||
@@ -239,6 +239,101 @@ class plugin_luxtools_test extends DokuWikiTest
|
|||||||
$this->assertStringContainsString('height="150"', $xhtml);
|
$this->assertStringContainsString('height="150"', $xhtml);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grouping wrapper should use default flex mode with zero gap.
|
||||||
|
*/
|
||||||
|
public function test_grouping_default_flex()
|
||||||
|
{
|
||||||
|
$imagePath = TMP_DIR . '/filelistdata/exampleimage.png';
|
||||||
|
$syntax = '<grouping>'
|
||||||
|
. '{{image>' . $imagePath . '|One|120}}'
|
||||||
|
. '{{image>' . $imagePath . '|Two|120}}'
|
||||||
|
. '</grouping>';
|
||||||
|
|
||||||
|
$instructions = p_get_instructions($syntax);
|
||||||
|
$xhtml = p_render('xhtml', $instructions, $info);
|
||||||
|
|
||||||
|
$doc = new Document();
|
||||||
|
$doc->html($xhtml);
|
||||||
|
|
||||||
|
$structure = [
|
||||||
|
'div.luxtools-grouping.luxtools-grouping--flex' => 1,
|
||||||
|
'div.luxtools-grouping .luxtools-imagebox' => 2,
|
||||||
|
];
|
||||||
|
$this->structureCheck($doc, $structure);
|
||||||
|
|
||||||
|
$style = (string)$doc->find('div.luxtools-grouping')->first()->attr('style');
|
||||||
|
$this->assertStringContainsString('--luxtools-grouping-cols: 2', $style);
|
||||||
|
$this->assertStringContainsString('--luxtools-grouping-gap: 0', $style);
|
||||||
|
$this->assertStringContainsString('--luxtools-grouping-justify: start', $style);
|
||||||
|
$this->assertStringContainsString('--luxtools-grouping-align: start', $style);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grouping wrapper should accept custom flex layout and gap.
|
||||||
|
*/
|
||||||
|
public function test_grouping_custom_flex()
|
||||||
|
{
|
||||||
|
$imagePath = TMP_DIR . '/filelistdata/exampleimage.png';
|
||||||
|
$syntax = '<grouping layout="flex" gap="8px">'
|
||||||
|
. '{{image>' . $imagePath . '|One|120}}'
|
||||||
|
. '{{image>' . $imagePath . '|Two|120}}'
|
||||||
|
. '</grouping>';
|
||||||
|
|
||||||
|
$instructions = p_get_instructions($syntax);
|
||||||
|
$xhtml = p_render('xhtml', $instructions, $info);
|
||||||
|
|
||||||
|
$doc = new Document();
|
||||||
|
$doc->html($xhtml);
|
||||||
|
|
||||||
|
$structure = [
|
||||||
|
'div.luxtools-grouping.luxtools-grouping--flex' => 1,
|
||||||
|
'div.luxtools-grouping .luxtools-imagebox' => 2,
|
||||||
|
];
|
||||||
|
$this->structureCheck($doc, $structure);
|
||||||
|
|
||||||
|
$style = (string)$doc->find('div.luxtools-grouping')->first()->attr('style');
|
||||||
|
$this->assertStringContainsString('--luxtools-grouping-gap: 8px', $style);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grouping wrapper should accept justify and align controls.
|
||||||
|
*/
|
||||||
|
public function test_grouping_justify_and_align()
|
||||||
|
{
|
||||||
|
$imagePath = TMP_DIR . '/filelistdata/exampleimage.png';
|
||||||
|
$syntax = '<grouping layout="flex" justify="space-between" align="center">'
|
||||||
|
. '{{image>' . $imagePath . '|One|120}}'
|
||||||
|
. '{{image>' . $imagePath . '|Two|120}}'
|
||||||
|
. '</grouping>';
|
||||||
|
|
||||||
|
$instructions = p_get_instructions($syntax);
|
||||||
|
$xhtml = p_render('xhtml', $instructions, $info);
|
||||||
|
|
||||||
|
$doc = new Document();
|
||||||
|
$doc->html($xhtml);
|
||||||
|
|
||||||
|
$style = (string)$doc->find('div.luxtools-grouping')->first()->attr('style');
|
||||||
|
$this->assertStringContainsString('--luxtools-grouping-justify: space-between', $style);
|
||||||
|
$this->assertStringContainsString('--luxtools-grouping-align: center', $style);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unknown grouping attributes should render a warning string.
|
||||||
|
*/
|
||||||
|
public function test_grouping_unknown_option_warning()
|
||||||
|
{
|
||||||
|
$imagePath = TMP_DIR . '/filelistdata/exampleimage.png';
|
||||||
|
$syntax = '<grouping gpa="0.5rem">'
|
||||||
|
. '{{image>' . $imagePath . '|One|120}}'
|
||||||
|
. '</grouping>';
|
||||||
|
|
||||||
|
$instructions = p_get_instructions($syntax);
|
||||||
|
$xhtml = p_render('xhtml', $instructions, $info);
|
||||||
|
|
||||||
|
$this->assertStringContainsString('[grouping: unknown option(s): gpa]', $xhtml);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure the built-in file endpoint includes the host page id so file.php can
|
* Ensure the built-in file endpoint includes the host page id so file.php can
|
||||||
* enforce per-page ACL.
|
* enforce per-page ACL.
|
||||||
|
|||||||
25
style.css
25
style.css
@@ -422,6 +422,31 @@ html.luxtools-noscroll body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
* Grouping wrapper (compact image layout container)
|
||||||
|
* ======================================================================== */
|
||||||
|
|
||||||
|
.luxtools-grouping {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(var(--luxtools-grouping-cols, 2), minmax(0, 1fr));
|
||||||
|
gap: var(--luxtools-grouping-gap, 0);
|
||||||
|
justify-content: var(--luxtools-grouping-justify, start);
|
||||||
|
align-items: var(--luxtools-grouping-align, start);
|
||||||
|
}
|
||||||
|
|
||||||
|
.luxtools-grouping.luxtools-grouping--flex {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: var(--luxtools-grouping-gap, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Let the grouping layout fully control item placement. */
|
||||||
|
.luxtools-grouping .luxtools-imagebox {
|
||||||
|
float: none;
|
||||||
|
clear: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* ========================================================================
|
/* ========================================================================
|
||||||
* Imagebox (Wikipedia-style image with caption)
|
* Imagebox (Wikipedia-style image with caption)
|
||||||
* ======================================================================== */
|
* ======================================================================== */
|
||||||
|
|||||||
282
syntax/grouping.php
Normal file
282
syntax/grouping.php
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use dokuwiki\Extension\SyntaxPlugin;
|
||||||
|
|
||||||
|
require_once(__DIR__ . '/../autoload.php');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* luxtools Plugin: Grouping wrapper syntax.
|
||||||
|
*
|
||||||
|
* Wraps multiple blocks (typically {{image>...}}) and applies compact layout
|
||||||
|
* without adding visual box styling of its own.
|
||||||
|
*
|
||||||
|
* Syntax:
|
||||||
|
* <grouping layout="flex" gap="0" justify="start" align="start">
|
||||||
|
* {{image>...}}
|
||||||
|
* {{image>...}}
|
||||||
|
* </grouping>
|
||||||
|
*/
|
||||||
|
class syntax_plugin_luxtools_grouping extends SyntaxPlugin
|
||||||
|
{
|
||||||
|
/** @inheritdoc */
|
||||||
|
public function getType()
|
||||||
|
{
|
||||||
|
return 'container';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
public function getPType()
|
||||||
|
{
|
||||||
|
return 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
public function getSort()
|
||||||
|
{
|
||||||
|
// Slightly after image syntax
|
||||||
|
return 316;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
public function getAllowedTypes()
|
||||||
|
{
|
||||||
|
return ['container', 'substition', 'protected', 'disabled', 'formatting'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
public function connectTo($mode)
|
||||||
|
{
|
||||||
|
$this->Lexer->addEntryPattern('<grouping(?:\s+[^>]*)?>(?=.*</grouping>)', $mode, 'plugin_luxtools_grouping');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
public function postConnect()
|
||||||
|
{
|
||||||
|
$this->Lexer->addExitPattern('</grouping>', 'plugin_luxtools_grouping');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
public function handle($match, $state, $pos, Doku_Handler $handler)
|
||||||
|
{
|
||||||
|
if ($state === DOKU_LEXER_ENTER) {
|
||||||
|
$parsed = $this->parseOpeningTag($match);
|
||||||
|
return [
|
||||||
|
'state' => $state,
|
||||||
|
'params' => $parsed['params'],
|
||||||
|
'unknown' => $parsed['unknown'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($state === DOKU_LEXER_UNMATCHED) {
|
||||||
|
return [
|
||||||
|
'state' => $state,
|
||||||
|
'text' => $match,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'state' => $state,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
public function render($format, Doku_Renderer $renderer, $data)
|
||||||
|
{
|
||||||
|
if (!is_array($data) || !isset($data['state'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$state = (int)$data['state'];
|
||||||
|
|
||||||
|
if ($format !== 'xhtml') {
|
||||||
|
if ($state === DOKU_LEXER_UNMATCHED && isset($data['text'])) {
|
||||||
|
$renderer->cdata((string)$data['text']);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!($renderer instanceof Doku_Renderer_xhtml)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($state === DOKU_LEXER_ENTER) {
|
||||||
|
$params = isset($data['params']) && is_array($data['params']) ? $data['params'] : $this->getDefaultParams();
|
||||||
|
$unknown = isset($data['unknown']) && is_array($data['unknown']) ? $data['unknown'] : [];
|
||||||
|
|
||||||
|
$layout = ($params['layout'] === 'flex') ? 'flex' : 'grid';
|
||||||
|
$cols = (int)$params['cols'];
|
||||||
|
if ($cols < 1) {
|
||||||
|
$cols = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
$gap = (string)$params['gap'];
|
||||||
|
if (!$this->isValidCssLength($gap)) {
|
||||||
|
$gap = '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
$justify = (string)$params['justify'];
|
||||||
|
if (!$this->isValidJustify($justify)) {
|
||||||
|
$justify = 'start';
|
||||||
|
}
|
||||||
|
|
||||||
|
$align = (string)$params['align'];
|
||||||
|
if (!$this->isValidAlign($align)) {
|
||||||
|
$align = 'start';
|
||||||
|
}
|
||||||
|
|
||||||
|
$renderer->doc .= '<div class="luxtools-grouping luxtools-grouping--' . hsc($layout) . '"'
|
||||||
|
. ' style="--luxtools-grouping-cols: ' . $cols
|
||||||
|
. '; --luxtools-grouping-gap: ' . hsc($gap)
|
||||||
|
. '; --luxtools-grouping-justify: ' . hsc($justify)
|
||||||
|
. '; --luxtools-grouping-align: ' . hsc($align)
|
||||||
|
. ';">';
|
||||||
|
|
||||||
|
if ($unknown !== []) {
|
||||||
|
$renderer->doc .= '<span class="luxtools-grouping-warning">'
|
||||||
|
. hsc('[grouping: unknown option(s): ' . implode(', ', $unknown) . ']')
|
||||||
|
. '</span>';
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($state === DOKU_LEXER_UNMATCHED) {
|
||||||
|
if (isset($data['text'])) {
|
||||||
|
$renderer->cdata((string)$data['text']);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($state === DOKU_LEXER_EXIT) {
|
||||||
|
$renderer->doc .= '</div>';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse opening <grouping ...> tag attributes.
|
||||||
|
*
|
||||||
|
* Supports attribute style only, e.g.
|
||||||
|
* layout="grid" cols="3" gap="8px" justify="center" align="stretch".
|
||||||
|
* Unknown or invalid values are ignored and defaults are used.
|
||||||
|
*
|
||||||
|
* @param string $match
|
||||||
|
* @return array{params:array{layout:string,cols:int,gap:string,justify:string,align:string},unknown:array<int,string>}
|
||||||
|
*/
|
||||||
|
protected function parseOpeningTag(string $match): array
|
||||||
|
{
|
||||||
|
$params = $this->getDefaultParams();
|
||||||
|
$unknown = [];
|
||||||
|
|
||||||
|
if (!preg_match('/^<grouping\b(.*?)>$/is', $match, $tagMatch)) {
|
||||||
|
return ['params' => $params, 'unknown' => $unknown];
|
||||||
|
}
|
||||||
|
|
||||||
|
$attrPart = (string)$tagMatch[1];
|
||||||
|
if ($attrPart === '') {
|
||||||
|
return ['params' => $params, 'unknown' => $unknown];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match_all('/([a-zA-Z_:][a-zA-Z0-9:._-]*)\s*=\s*(["\'])(.*?)\2/s', $attrPart, $attrMatches, PREG_SET_ORDER)) {
|
||||||
|
foreach ($attrMatches as $item) {
|
||||||
|
$name = strtolower(trim((string)$item[1]));
|
||||||
|
$value = trim((string)$item[3]);
|
||||||
|
|
||||||
|
if ($name === 'layout') {
|
||||||
|
$value = strtolower($value);
|
||||||
|
if (in_array($value, ['grid', 'flex'], true)) {
|
||||||
|
$params['layout'] = $value;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($name === 'cols') {
|
||||||
|
if (preg_match('/^\d+$/', $value)) {
|
||||||
|
$cols = (int)$value;
|
||||||
|
if ($cols > 0) {
|
||||||
|
$params['cols'] = min($cols, 12);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($name === 'gap') {
|
||||||
|
if ($this->isValidCssLength($value)) {
|
||||||
|
$params['gap'] = $value;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($name === 'justify') {
|
||||||
|
$value = strtolower($value);
|
||||||
|
if ($this->isValidJustify($value)) {
|
||||||
|
$params['justify'] = $value;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($name === 'align') {
|
||||||
|
$value = strtolower($value);
|
||||||
|
if ($this->isValidAlign($value)) {
|
||||||
|
$params['align'] = $value;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$unknown[] = $name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($unknown !== []) {
|
||||||
|
$unknown = array_values(array_unique($unknown));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['params' => $params, 'unknown' => $unknown];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{layout:string,cols:int,gap:string,justify:string,align:string}
|
||||||
|
*/
|
||||||
|
protected function getDefaultParams(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'layout' => 'flex',
|
||||||
|
'cols' => 2,
|
||||||
|
'gap' => '0',
|
||||||
|
'justify' => 'start',
|
||||||
|
'align' => 'start',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate a simple CSS length token.
|
||||||
|
*
|
||||||
|
* Allows "0" and common explicit units used in docs/examples.
|
||||||
|
*/
|
||||||
|
protected function isValidCssLength(string $value): bool
|
||||||
|
{
|
||||||
|
$value = trim($value);
|
||||||
|
if ($value === '0') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (bool)preg_match('/^(?:\d+(?:\.\d+)?|\.\d+)(?:px|em|rem|%|vw|vh)$/', $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate justify-content compatible values.
|
||||||
|
*/
|
||||||
|
protected function isValidJustify(string $value): bool
|
||||||
|
{
|
||||||
|
return in_array($value, ['start', 'center', 'end', 'space-between', 'space-around', 'space-evenly'], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate align-items compatible values.
|
||||||
|
*/
|
||||||
|
protected function isValidAlign(string $value): bool
|
||||||
|
{
|
||||||
|
return in_array($value, ['start', 'center', 'end', 'stretch', 'baseline'], true);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user