feat: add canonical Box layout axes
This commit is contained in:
parent
fcfbeb0368
commit
9f2cdfb299
65
ebox-dsl.el
65
ebox-dsl.el
@ -14,6 +14,12 @@
|
|||||||
|
|
||||||
(declare-function ebox-create "ebox" (&rest plist))
|
(declare-function ebox-create "ebox" (&rest plist))
|
||||||
|
|
||||||
|
(defconst ebox-dsl--outer-values '(inline block)
|
||||||
|
"Valid outer participation values for the canonical Box DSL.")
|
||||||
|
|
||||||
|
(defconst ebox-dsl--layout-values '(normal row column flex grid)
|
||||||
|
"Valid child layout values for the canonical Box DSL.")
|
||||||
|
|
||||||
(defun ebox--dsl-unquote-value (value)
|
(defun ebox--dsl-unquote-value (value)
|
||||||
"Unquote one static DSL property VALUE when it is quoted data."
|
"Unquote one static DSL property VALUE when it is quoted data."
|
||||||
(if (and (consp value)
|
(if (and (consp value)
|
||||||
@ -38,6 +44,22 @@ keyword properties are normalized."
|
|||||||
(push item result))))
|
(push item result))))
|
||||||
(nreverse result)))
|
(nreverse result)))
|
||||||
|
|
||||||
|
(defun ebox-dsl--axis-value (props property default allowed)
|
||||||
|
"Return PROPS PROPERTY or DEFAULT after validating it against ALLOWED."
|
||||||
|
(let ((value (if (plist-member props property)
|
||||||
|
(plist-get props property)
|
||||||
|
default)))
|
||||||
|
(unless (memq value allowed)
|
||||||
|
(error "ebox-build: invalid %S value %S" property value))
|
||||||
|
value))
|
||||||
|
|
||||||
|
(defun ebox-dsl--with-display (node outer layout)
|
||||||
|
"Return a shallow copy of NODE with canonical OUTER and LAYOUT display."
|
||||||
|
(let ((copy (copy-sequence node)))
|
||||||
|
(plist-put copy :display
|
||||||
|
(list outer (if (eq layout 'normal) 'flow layout)))
|
||||||
|
copy))
|
||||||
|
|
||||||
(defun ebox--build-children-layout (children)
|
(defun ebox--build-children-layout (children)
|
||||||
"Build a vertical default layout from CHILDREN."
|
"Build a vertical default layout from CHILDREN."
|
||||||
(setq children (delq nil children))
|
(setq children (delq nil children))
|
||||||
@ -134,6 +156,13 @@ The first value is string content. The second value is a lazy child node."
|
|||||||
(ebox-dsl-build (car children))
|
(ebox-dsl-build (car children))
|
||||||
props)))
|
props)))
|
||||||
|
|
||||||
|
(defun ebox--build-grid (items)
|
||||||
|
"Build a grid node from DSL ITEMS."
|
||||||
|
(let* ((split (ebox-grid--split-attrs items))
|
||||||
|
(props (car split))
|
||||||
|
(children (mapcar #'ebox-dsl-build (delq nil (cdr split)))))
|
||||||
|
(apply #'ebox-grid (append props children))))
|
||||||
|
|
||||||
(defun ebox--build-spacer (items)
|
(defun ebox--build-spacer (items)
|
||||||
"Build a spacer node from DSL ITEMS."
|
"Build a spacer node from DSL ITEMS."
|
||||||
(let* ((split (ebox--build-split-attrs items))
|
(let* ((split (ebox--build-split-attrs items))
|
||||||
@ -145,6 +174,33 @@ The first value is string content. The second value is a lazy child node."
|
|||||||
(error "ebox-build: spacer cannot set :content"))
|
(error "ebox-build: spacer cannot set :content"))
|
||||||
(apply #'ebox-spacer props)))
|
(apply #'ebox-spacer props)))
|
||||||
|
|
||||||
|
(defun ebox--build-canonical-box (items)
|
||||||
|
"Build canonical Box ITEMS with orthogonal outer and layout axes."
|
||||||
|
(let* ((split (ebox--build-split-attrs items))
|
||||||
|
(props (car split))
|
||||||
|
(children (cdr split))
|
||||||
|
(outer (ebox-dsl--axis-value
|
||||||
|
props :outer 'block ebox-dsl--outer-values))
|
||||||
|
(layout (ebox-dsl--axis-value
|
||||||
|
props :layout 'normal ebox-dsl--layout-values))
|
||||||
|
(box-props (ebox--plist-remove-keys props '(:outer :layout)))
|
||||||
|
node)
|
||||||
|
(setq node
|
||||||
|
(pcase layout
|
||||||
|
('normal
|
||||||
|
(ebox--build-box (append box-props children)))
|
||||||
|
('row
|
||||||
|
(ebox--build-layout
|
||||||
|
'box #'ebox-row (append box-props children)))
|
||||||
|
('column
|
||||||
|
(ebox--build-layout
|
||||||
|
'box #'ebox-column (append box-props children)))
|
||||||
|
('flex
|
||||||
|
(ebox--build-flex (append box-props children)))
|
||||||
|
('grid
|
||||||
|
(ebox--build-grid (append box-props children)))))
|
||||||
|
(ebox-dsl--with-display node outer layout)))
|
||||||
|
|
||||||
(defun ebox-dsl-build (dsl)
|
(defun ebox-dsl-build (dsl)
|
||||||
"Build an Ebox node from an ETML-style list DSL.
|
"Build an Ebox node from an ETML-style list DSL.
|
||||||
|
|
||||||
@ -171,18 +227,15 @@ the child layout in a box using the rendered child layout as content."
|
|||||||
(let ((tag (car dsl))
|
(let ((tag (car dsl))
|
||||||
(items (ebox--dsl-unquote-properties (cdr dsl))))
|
(items (ebox--dsl-unquote-properties (cdr dsl))))
|
||||||
(pcase tag
|
(pcase tag
|
||||||
((or 'box 'ebox) (ebox--build-box items))
|
('box (ebox--build-canonical-box items))
|
||||||
|
('ebox (ebox--build-box items))
|
||||||
('row (ebox--build-layout tag #'ebox-row items))
|
('row (ebox--build-layout tag #'ebox-row items))
|
||||||
('column (ebox--build-layout tag #'ebox-column items))
|
('column (ebox--build-layout tag #'ebox-column items))
|
||||||
('flex (ebox--build-flex items))
|
('flex (ebox--build-flex items))
|
||||||
('item (ebox--build-flex-item items))
|
('item (ebox--build-flex-item items))
|
||||||
('grid-item (ebox--build-grid-item items))
|
('grid-item (ebox--build-grid-item items))
|
||||||
('spacer (ebox--build-spacer items))
|
('spacer (ebox--build-spacer items))
|
||||||
('grid
|
('grid (ebox--build-grid items))
|
||||||
(let* ((split (ebox-grid--split-attrs items))
|
|
||||||
(props (car split))
|
|
||||||
(children (mapcar #'ebox-dsl-build (delq nil (cdr split)))))
|
|
||||||
(apply #'ebox-grid (append props children))))
|
|
||||||
(_ (error "ebox-build: unknown DSL tag %S" tag)))))))
|
(_ (error "ebox-build: unknown DSL tag %S" tag)))))))
|
||||||
|
|
||||||
(provide 'ebox-dsl)
|
(provide 'ebox-dsl)
|
||||||
|
|||||||
@ -358,6 +358,51 @@
|
|||||||
(should (equal (ebox--computed-display column-node) '(block column)))
|
(should (equal (ebox--computed-display column-node) '(block column)))
|
||||||
(should (equal (ebox--computed-display flex-node) '(block flex)))))
|
(should (equal (ebox--computed-display flex-node) '(block flex)))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-build-box-uses-orthogonal-outer-and-layout-axes ()
|
||||||
|
"Canonical Box DSL should select outer participation and child layout."
|
||||||
|
(let ((normal (ebox-build '(box :outer inline :layout normal "A")))
|
||||||
|
(row (ebox-build
|
||||||
|
'(box :outer inline :layout row
|
||||||
|
(box "A")
|
||||||
|
(box "B"))))
|
||||||
|
(column (ebox-build
|
||||||
|
'(box :outer block :layout column
|
||||||
|
(box "A")
|
||||||
|
(box "B")))))
|
||||||
|
(should (equal (ebox--computed-display normal) '(inline flow)))
|
||||||
|
(should (equal (ebox--computed-display row) '(inline row)))
|
||||||
|
(should (equal (ebox--computed-display column) '(block column)))
|
||||||
|
(should (string= (ebox-dsl-test--plain row) "AB"))
|
||||||
|
(should (string= (ebox-dsl-test--plain column) "A\nB"))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-build-box-rejects-invalid-axis-values ()
|
||||||
|
"Canonical Box DSL should reject unknown outer and layout values."
|
||||||
|
(should-error (ebox-build '(box :outer floating "A")) :type 'error)
|
||||||
|
(should-error (ebox-build '(box :layout masonry "A")) :type 'error))
|
||||||
|
|
||||||
|
(ert-deftest ebox-build-box-layout-consumes-direct-flex-item-properties ()
|
||||||
|
"Canonical Flex Box should consume participation props from child Boxes."
|
||||||
|
(let* ((node (ebox-build
|
||||||
|
'(box :layout flex :width (300)
|
||||||
|
(box :flex-grow 1 :flex-basis (80) "A")
|
||||||
|
(box :flex-grow 2 :flex-basis (80) "B"))))
|
||||||
|
(children (plist-get node :children)))
|
||||||
|
(should (equal (ebox--computed-display node) '(block flex)))
|
||||||
|
(should (= (plist-get (car children) :flex-grow) 1))
|
||||||
|
(should (= (plist-get (cadr children) :flex-grow) 2))
|
||||||
|
(should (string-match-p "A" (ebox-dsl-test--plain node)))
|
||||||
|
(should (string-match-p "B" (ebox-dsl-test--plain node)))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-build-box-layout-consumes-direct-grid-item-properties ()
|
||||||
|
"Canonical Grid Box should consume placement props from child Boxes."
|
||||||
|
(let* ((node (ebox-build
|
||||||
|
'(box :layout grid :grid-template-columns ((20) (20))
|
||||||
|
(box :grid-column (1 :span 2) "Header"))))
|
||||||
|
(child (car (plist-get node :children))))
|
||||||
|
(should (equal (ebox--computed-display node) '(block grid)))
|
||||||
|
(should (equal (plist-get child :grid-column) '(1 :span 2)))
|
||||||
|
(should (string-match-p "Header" (ebox-dsl-test--plain node)))))
|
||||||
|
|
||||||
(ert-deftest ebox-build-wrapper-child-layout-rerenders-on-runtime-viewport-change ()
|
(ert-deftest ebox-build-wrapper-child-layout-rerenders-on-runtime-viewport-change ()
|
||||||
"A DSL wrapper box should rerender child layout content on viewport resize."
|
"A DSL wrapper box should rerender child layout content on viewport resize."
|
||||||
(let ((node (let ((ebox-viewport-width 96))
|
(let ((node (let ((ebox-viewport-width 96))
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user