feat: add typed Text and Normal Box property boundary
This commit is contained in:
parent
47f9da2ef2
commit
b530b0513c
@ -190,7 +190,7 @@ together.
|
|||||||
| Content/geometry | `:content`, `:box-sizing`, `:width`, `:min-width`, `:max-width`, `:height`, `:min-height`, `:max-height` |
|
| Content/geometry | `:content`, `:box-sizing`, `:width`, `:min-width`, `:max-width`, `:height`, `:min-height`, `:max-height` |
|
||||||
| Padding | `:padding`, `:padding-inline`, `:padding-block`; longhands `:padding-block-start`, `:padding-inline-end`, `:padding-block-end`, `:padding-inline-start`; aliases `:padding-top`, `:padding-right`, `:padding-bottom`, `:padding-left` and the pixel/height aliases `:padding-right-pixel`, `:padding-left-pixel`, `:padding-top-height`, `:padding-bottom-height` |
|
| Padding | `:padding`, `:padding-inline`, `:padding-block`; longhands `:padding-block-start`, `:padding-inline-end`, `:padding-block-end`, `:padding-inline-start`; aliases `:padding-top`, `:padding-right`, `:padding-bottom`, `:padding-left` and the pixel/height aliases `:padding-right-pixel`, `:padding-left-pixel`, `:padding-top-height`, `:padding-bottom-height` |
|
||||||
| Margin | `:margin`, `:margin-inline`, `:margin-block`; longhands `:margin-block-start`, `:margin-inline-end`, `:margin-block-end`, `:margin-inline-start`; aliases `:margin-top`, `:margin-right`, `:margin-bottom`, `:margin-left` and the pixel/height aliases `:margin-right-pixel`, `:margin-left-pixel`, `:margin-top-height`, `:margin-bottom-height` |
|
| Margin | `:margin`, `:margin-inline`, `:margin-block`; longhands `:margin-block-start`, `:margin-inline-end`, `:margin-block-end`, `:margin-inline-start`; aliases `:margin-top`, `:margin-right`, `:margin-bottom`, `:margin-left` and the pixel/height aliases `:margin-right-pixel`, `:margin-left-pixel`, `:margin-top-height`, `:margin-bottom-height` |
|
||||||
| Border | `:border`, `:border-top`, `:border-right`, `:border-bottom`, `:border-left`, `:border-width`, `:border-style`, `:border-color`; side longhands `:border-*-width`, `:border-*-style`, `:border-*-color`; compatibility aliases `:border-top-p`, `:border-bottom-p`, `:border-left-pixel`, `:border-right-pixel` |
|
| Border | `:border`, `:border-top`, `:border-right`, `:border-bottom`, `:border-left`, `:border-width`, `:border-style`, `:border-color`; side longhands `:border-*-width`, `:border-*-style`, `:border-*-color`; Ebox boolean shorthands `:border-top-p`, `:border-bottom-p`; compatibility aliases `:border-left-pixel`, `:border-right-pixel` |
|
||||||
| Paint | `:color`, `:background-color`, and alias `:bgcolor` |
|
| Paint | `:color`, `:background-color`, and alias `:bgcolor` |
|
||||||
| Typography | `:font`, `:font-family`, `:font-height` and alias `:font-size`, `:font-weight`, `:font-slant` |
|
| Typography | `:font`, `:font-family`, `:font-height` and alias `:font-size`, `:font-weight`, `:font-slant` |
|
||||||
| Text/layout | `:text-align` (`left`, `center`, `right`), `:vertical-align` (`top`, `center`/`middle`, `bottom`), `:overflow` (`scroll`, `hidden`, `visible`), `:wrap-mode` (`word`, `char`, `kp`, or nil), `:visibility` (`visible` or `hidden`) |
|
| Text/layout | `:text-align` (`left`, `center`, `right`), `:vertical-align` (`top`, `center`/`middle`, `bottom`), `:overflow` (`scroll`, `hidden`, `visible`), `:wrap-mode` (`word`, `char`, `kp`, or nil), `:visibility` (`visible` or `hidden`) |
|
||||||
@ -587,7 +587,8 @@ style-rule functions immediately below are public module-level style APIs.
|
|||||||
|
|
||||||
| Function | Use |
|
| Function | Use |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `ebox-create`, `ebox-build` | Create a node or compile the `.ebox` list DSL. |
|
| `ebox-text-create`, `ebox-normal-layout-create`, `ebox-box-create` | Framework-integration port for typed TextNode, Normal LayoutConfig, and BoxNode values. |
|
||||||
|
| `ebox-create`, `ebox-build` | Create a legacy node or compile the `.ebox` list DSL during migration. |
|
||||||
| `ebox-concat`, `ebox-stack`, `ebox-row`, `ebox-column`, `ebox-spacer` | Compose simple horizontal/vertical layouts and blank boxes. |
|
| `ebox-concat`, `ebox-stack`, `ebox-row`, `ebox-column`, `ebox-spacer` | Compose simple horizontal/vertical layouts and blank boxes. |
|
||||||
| `ebox-flex`, `ebox-flex-item` | Build flex containers and item metadata. |
|
| `ebox-flex`, `ebox-flex-item` | Build flex containers and item metadata. |
|
||||||
| `ebox-grid`, `ebox-grid-fr`, `ebox-grid-item` | Build Grid layouts, fractional tracks, and placement metadata. |
|
| `ebox-grid`, `ebox-grid-fr`, `ebox-grid-item` | Build Grid layouts, fractional tracks, and placement metadata. |
|
||||||
|
|||||||
@ -173,7 +173,7 @@ property;`ebox-region-update` 接受其目标支持的可变 content/style/scr
|
|||||||
| Content/geometry | `:content`、`:box-sizing`、`:width`、`:min-width`、`:max-width`、`:height`、`:min-height`、`:max-height` |
|
| Content/geometry | `:content`、`:box-sizing`、`:width`、`:min-width`、`:max-width`、`:height`、`:min-height`、`:max-height` |
|
||||||
| Padding | `:padding`、`:padding-inline`、`:padding-block`;longhand `:padding-block-start`、`:padding-inline-end`、`:padding-block-end`、`:padding-inline-start`;别名 `:padding-top`、`:padding-right`、`:padding-bottom`、`:padding-left`,以及 `:padding-right-pixel`、`:padding-left-pixel`、`:padding-top-height`、`:padding-bottom-height` |
|
| Padding | `:padding`、`:padding-inline`、`:padding-block`;longhand `:padding-block-start`、`:padding-inline-end`、`:padding-block-end`、`:padding-inline-start`;别名 `:padding-top`、`:padding-right`、`:padding-bottom`、`:padding-left`,以及 `:padding-right-pixel`、`:padding-left-pixel`、`:padding-top-height`、`:padding-bottom-height` |
|
||||||
| Margin | `:margin`、`:margin-inline`、`:margin-block`;longhand `:margin-block-start`、`:margin-inline-end`、`:margin-block-end`、`:margin-inline-start`;别名 `:margin-top`、`:margin-right`、`:margin-bottom`、`:margin-left`,以及 `:margin-right-pixel`、`:margin-left-pixel`、`:margin-top-height`、`:margin-bottom-height` |
|
| Margin | `:margin`、`:margin-inline`、`:margin-block`;longhand `:margin-block-start`、`:margin-inline-end`、`:margin-block-end`、`:margin-inline-start`;别名 `:margin-top`、`:margin-right`、`:margin-bottom`、`:margin-left`,以及 `:margin-right-pixel`、`:margin-left-pixel`、`:margin-top-height`、`:margin-bottom-height` |
|
||||||
| Border | `:border`、`:border-top`、`:border-right`、`:border-bottom`、`:border-left`、`:border-width`、`:border-style`、`:border-color`;side longhand `:border-*-width`、`:border-*-style`、`:border-*-color`;兼容别名 `:border-top-p`、`:border-bottom-p`、`:border-left-pixel`、`:border-right-pixel` |
|
| Border | `:border`、`:border-top`、`:border-right`、`:border-bottom`、`:border-left`、`:border-width`、`:border-style`、`:border-color`;side longhand `:border-*-width`、`:border-*-style`、`:border-*-color`;Ebox boolean shorthand `:border-top-p`、`:border-bottom-p`;兼容别名 `:border-left-pixel`、`:border-right-pixel` |
|
||||||
| Paint | `:color`、`:background-color`、别名 `:bgcolor` |
|
| Paint | `:color`、`:background-color`、别名 `:bgcolor` |
|
||||||
| Typography | `:font`、`:font-family`、`:font-height` 和别名 `:font-size`、`:font-weight`、`:font-slant` |
|
| Typography | `:font`、`:font-family`、`:font-height` 和别名 `:font-size`、`:font-weight`、`:font-slant` |
|
||||||
| Text/layout | `:text-align`(`left`、`center`、`right`)、`:vertical-align`(`top`、`center`/`middle`、`bottom`)、`:overflow`(`scroll`、`hidden`、`visible`)、`:wrap-mode`(`word`、`char`、`kp` 或 nil)、`:visibility`(`visible` 或 `hidden`) |
|
| Text/layout | `:text-align`(`left`、`center`、`right`)、`:vertical-align`(`top`、`center`/`middle`、`bottom`)、`:overflow`(`scroll`、`hidden`、`visible`)、`:wrap-mode`(`word`、`char`、`kp` 或 nil)、`:visibility`(`visible` 或 `hidden`) |
|
||||||
@ -533,7 +533,8 @@ style API。
|
|||||||
|
|
||||||
| 函数 | 用途 |
|
| 函数 | 用途 |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `ebox-create`、`ebox-build` | 创建节点或编译 `.ebox` list DSL。 |
|
| `ebox-text-create`、`ebox-normal-layout-create`、`ebox-box-create` | 框架集成使用的 typed TextNode、Normal LayoutConfig 与 BoxNode 端口。 |
|
||||||
|
| `ebox-create`、`ebox-build` | 迁移期间创建旧节点或编译 `.ebox` list DSL。 |
|
||||||
| `ebox-concat`、`ebox-stack`、`ebox-row`、`ebox-column`、`ebox-spacer` | 组合简单横向/纵向布局与空白 box。 |
|
| `ebox-concat`、`ebox-stack`、`ebox-row`、`ebox-column`、`ebox-spacer` | 组合简单横向/纵向布局与空白 box。 |
|
||||||
| `ebox-flex`、`ebox-flex-item` | 构建 Flex 容器与 item metadata。 |
|
| `ebox-flex`、`ebox-flex-item` | 构建 Flex 容器与 item metadata。 |
|
||||||
| `ebox-grid`、`ebox-grid-fr`、`ebox-grid-item` | 构建 Grid、分数轨道和 placement metadata。 |
|
| `ebox-grid`、`ebox-grid-fr`、`ebox-grid-item` | 构建 Grid、分数轨道和 placement metadata。 |
|
||||||
|
|||||||
213
ebox-canonical.el
Normal file
213
ebox-canonical.el
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
;;; ebox-canonical.el --- Typed canonical Ebox nodes -*- lexical-binding: t; -*-
|
||||||
|
|
||||||
|
;;; Commentary:
|
||||||
|
;; Owns the typed programmatic boundary between normalized author input and
|
||||||
|
;; Ebox's current runtime representation. Canonical kind, layout, source, and
|
||||||
|
;; child facts remain explicit even when the runtime reuses an existing box
|
||||||
|
;; encoding for rendering. This module does not parse the `.ebox' DSL or
|
||||||
|
;; perform measurement, layout, paint, or buffer publication.
|
||||||
|
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(require 'cl-lib)
|
||||||
|
(require 'subr-x)
|
||||||
|
(require 'ebox-style)
|
||||||
|
(require 'ebox-node-factory)
|
||||||
|
|
||||||
|
(cl-defstruct
|
||||||
|
(ebox-layout-config
|
||||||
|
(:constructor ebox-layout-config--create))
|
||||||
|
"Canonical typed layout selection for one BoxNode."
|
||||||
|
kind
|
||||||
|
props)
|
||||||
|
|
||||||
|
(defun ebox-canonical--validate-plist (plist context)
|
||||||
|
"Validate PLIST shape for error CONTEXT and return PLIST."
|
||||||
|
(unless (proper-list-p plist)
|
||||||
|
(error "%s properties must be a proper plist: %S" context plist))
|
||||||
|
(unless (zerop (% (length plist) 2))
|
||||||
|
(error "%s property is missing a value: %S" context (car (last plist))))
|
||||||
|
(cl-loop for key in plist by #'cddr
|
||||||
|
unless (keywordp key)
|
||||||
|
do (error "%s property name must be a keyword: %S" context key))
|
||||||
|
plist)
|
||||||
|
|
||||||
|
(defun ebox-canonical--without-keys (plist keys)
|
||||||
|
"Return PLIST without entries whose keys occur in KEYS."
|
||||||
|
(cl-loop for (key value) on plist by #'cddr
|
||||||
|
unless (memq key keys)
|
||||||
|
append (list key value)))
|
||||||
|
|
||||||
|
(defun ebox-canonical--field-values (plist field)
|
||||||
|
"Return every FIELD value from PLIST in source order."
|
||||||
|
(cl-loop for (key value) on plist by #'cddr
|
||||||
|
when (eq key field)
|
||||||
|
collect value))
|
||||||
|
|
||||||
|
(defun ebox-canonical--required-field (plist field context)
|
||||||
|
"Return exactly one required FIELD from PLIST for error CONTEXT."
|
||||||
|
(let ((values (ebox-canonical--field-values plist field)))
|
||||||
|
(unless (= (length values) 1)
|
||||||
|
(error "%s requires %S exactly once" context field))
|
||||||
|
(car values)))
|
||||||
|
|
||||||
|
(defun ebox-canonical--optional-field (plist field context)
|
||||||
|
"Return `(PRESENT . VALUE)' for optional FIELD in PLIST and CONTEXT."
|
||||||
|
(let ((values (ebox-canonical--field-values plist field)))
|
||||||
|
(when (> (length values) 1)
|
||||||
|
(error "%s accepts %S at most once" context field))
|
||||||
|
(and values (cons t (car values)))))
|
||||||
|
|
||||||
|
(defun ebox-canonical--compiled-property-definitions (plist context)
|
||||||
|
"Return canonical property definitions compiled from PLIST for CONTEXT."
|
||||||
|
(condition-case err
|
||||||
|
(cl-loop for (property _value)
|
||||||
|
on (ebox-style-compile-declarations plist t) by #'cddr
|
||||||
|
collect (or (ebox-style-property property)
|
||||||
|
(error "Missing canonical Ebox property: %S"
|
||||||
|
property)))
|
||||||
|
(error
|
||||||
|
(error "%s rejected properties: %s" context (error-message-string err)))))
|
||||||
|
|
||||||
|
(defun ebox-canonical--text-measurement-property-p (property)
|
||||||
|
"Return non-nil when PROPERTY belongs to canonical Text measurement."
|
||||||
|
(eq (plist-get property :group) 'typography))
|
||||||
|
|
||||||
|
(defun ebox-canonical--normal-box-property-p (property)
|
||||||
|
"Return non-nil when PROPERTY belongs to Normal Box geometry/participation."
|
||||||
|
(let ((contexts (plist-get property :contexts))
|
||||||
|
(group (plist-get property :group))
|
||||||
|
(name (plist-get property :name)))
|
||||||
|
(or (memq 'item contexts)
|
||||||
|
(and (memq 'box contexts)
|
||||||
|
(not (memq group '(paint typography)))
|
||||||
|
(not (eq name :wrap-mode))))))
|
||||||
|
|
||||||
|
(defun ebox-canonical--assert-property-role (plist predicate context)
|
||||||
|
"Require every compiled PLIST property to satisfy PREDICATE for CONTEXT."
|
||||||
|
(dolist (property
|
||||||
|
(ebox-canonical--compiled-property-definitions plist context))
|
||||||
|
(unless (funcall predicate property)
|
||||||
|
(error "%s does not accept %S"
|
||||||
|
context (plist-get property :name)))))
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun ebox-normal-layout-create ()
|
||||||
|
"Return the canonical Normal layout config."
|
||||||
|
(ebox-layout-config--create :kind 'normal :props nil))
|
||||||
|
|
||||||
|
(defun ebox-node-kind (node)
|
||||||
|
"Return canonical NODE kind, or nil for a legacy runtime node."
|
||||||
|
(and (listp node) (plist-get node :ebox-kind)))
|
||||||
|
|
||||||
|
(defun ebox-text-node-p (node)
|
||||||
|
"Return non-nil when NODE is a canonical TextNode."
|
||||||
|
(eq (ebox-node-kind node) 'text))
|
||||||
|
|
||||||
|
(defun ebox-box-node-p (node)
|
||||||
|
"Return non-nil when NODE is a canonical BoxNode."
|
||||||
|
(eq (ebox-node-kind node) 'box))
|
||||||
|
|
||||||
|
(defun ebox-node-source-handle (node)
|
||||||
|
"Return canonical NODE's opaque source handle."
|
||||||
|
(and (listp node) (plist-get node :ebox-source-handle)))
|
||||||
|
|
||||||
|
(defun ebox-text-node-value (node)
|
||||||
|
"Return canonical TextNode NODE's string payload."
|
||||||
|
(unless (ebox-text-node-p node)
|
||||||
|
(error "Expected canonical TextNode, got %S" node))
|
||||||
|
(plist-get node :ebox-text-value))
|
||||||
|
|
||||||
|
(defun ebox-box-node-layout (node)
|
||||||
|
"Return canonical BoxNode NODE's typed layout config."
|
||||||
|
(unless (ebox-box-node-p node)
|
||||||
|
(error "Expected canonical BoxNode, got %S" node))
|
||||||
|
(plist-get node :ebox-layout-config))
|
||||||
|
|
||||||
|
(defun ebox-box-node-children (node)
|
||||||
|
"Return canonical BoxNode NODE's retained canonical children."
|
||||||
|
(unless (ebox-box-node-p node)
|
||||||
|
(error "Expected canonical BoxNode, got %S" node))
|
||||||
|
(plist-get node :ebox-canonical-children))
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun ebox-text-create (&rest plist)
|
||||||
|
"Create a canonical TextNode from evaluated PLIST.
|
||||||
|
|
||||||
|
`:value' is required and must be one string. `:source-handle' is optional.
|
||||||
|
Only text measurement properties are accepted; paint is projected separately."
|
||||||
|
(ebox-canonical--validate-plist plist "ebox-text-create")
|
||||||
|
(let* ((value (ebox-canonical--required-field
|
||||||
|
plist :value "Ebox Text"))
|
||||||
|
(source-field (ebox-canonical--optional-field
|
||||||
|
plist :source-handle "Ebox Text"))
|
||||||
|
(source-handle (cdr source-field))
|
||||||
|
(props (ebox-canonical--without-keys
|
||||||
|
plist '(:value :source-handle))))
|
||||||
|
(unless (stringp value)
|
||||||
|
(error "Ebox Text :value must be a string: %S" value))
|
||||||
|
(ebox-canonical--assert-property-role
|
||||||
|
props #'ebox-canonical--text-measurement-property-p "Ebox Text")
|
||||||
|
(let ((node (apply #'ebox-create (append props (list :content value)))))
|
||||||
|
(plist-put node :ebox-kind 'text)
|
||||||
|
(plist-put node :ebox-text-value value)
|
||||||
|
(plist-put node :ebox-source-handle source-handle)
|
||||||
|
node)))
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun ebox-box-create (&rest plist)
|
||||||
|
"Create a canonical BoxNode from evaluated PLIST.
|
||||||
|
|
||||||
|
`:layout' must occur exactly once as an `ebox-layout-config'.
|
||||||
|
`:children' is a list of canonical nodes. This first vertical slice supports
|
||||||
|
Normal layout with zero or one child; later slices extend the same typed port
|
||||||
|
to the other Layout variants."
|
||||||
|
(ebox-canonical--validate-plist plist "ebox-box-create")
|
||||||
|
(let* ((layout (ebox-canonical--required-field
|
||||||
|
plist :layout "Ebox Box"))
|
||||||
|
(children-field (ebox-canonical--optional-field
|
||||||
|
plist :children "Ebox Box"))
|
||||||
|
(outer-field (ebox-canonical--optional-field
|
||||||
|
plist :outer "Ebox Box"))
|
||||||
|
(source-field (ebox-canonical--optional-field
|
||||||
|
plist :source-handle "Ebox Box"))
|
||||||
|
(children (if children-field (cdr children-field) nil))
|
||||||
|
(outer (if outer-field (cdr outer-field) 'block))
|
||||||
|
(source-handle (cdr source-field))
|
||||||
|
(props (ebox-canonical--without-keys
|
||||||
|
plist '(:layout :children :outer :source-handle))))
|
||||||
|
(unless (ebox-layout-config-p layout)
|
||||||
|
(error "Ebox Box :layout must be a typed layout config: %S"
|
||||||
|
layout))
|
||||||
|
(unless (memq outer '(inline block))
|
||||||
|
(error "Ebox Box :outer must be inline or block: %S" outer))
|
||||||
|
(unless (proper-list-p children)
|
||||||
|
(error "Ebox Box :children must be a proper list: %S" children))
|
||||||
|
(unless (cl-every (lambda (child)
|
||||||
|
(or (ebox-text-node-p child)
|
||||||
|
(ebox-box-node-p child)))
|
||||||
|
children)
|
||||||
|
(error "Ebox Box children must be canonical Text/Box nodes: %S"
|
||||||
|
children))
|
||||||
|
(unless (eq (ebox-layout-config-kind layout) 'normal)
|
||||||
|
(error "Ebox Box Layout is not implemented in this slice: %S"
|
||||||
|
(ebox-layout-config-kind layout)))
|
||||||
|
(when (> (length children) 1)
|
||||||
|
(error "Ebox Box Normal layout currently accepts at most one child"))
|
||||||
|
(ebox-canonical--assert-property-role
|
||||||
|
props #'ebox-canonical--normal-box-property-p "Ebox Box")
|
||||||
|
(let ((node
|
||||||
|
(apply #'ebox-create
|
||||||
|
(append props
|
||||||
|
(when children
|
||||||
|
(list :ebox-content-node (car children)))))))
|
||||||
|
(plist-put node :ebox-kind 'box)
|
||||||
|
(plist-put node :ebox-layout-config layout)
|
||||||
|
(plist-put node :ebox-canonical-children children)
|
||||||
|
(plist-put node :ebox-source-handle source-handle)
|
||||||
|
(plist-put node :display (list outer 'flow))
|
||||||
|
node)))
|
||||||
|
|
||||||
|
(provide 'ebox-canonical)
|
||||||
|
|
||||||
|
;;; ebox-canonical.el ends here
|
||||||
18
ebox-dsl.el
18
ebox-dsl.el
@ -11,6 +11,7 @@
|
|||||||
(require 'ebox-tree)
|
(require 'ebox-tree)
|
||||||
(require 'ebox-flex)
|
(require 'ebox-flex)
|
||||||
(require 'ebox-grid)
|
(require 'ebox-grid)
|
||||||
|
(require 'ebox-canonical)
|
||||||
|
|
||||||
(declare-function ebox-create "ebox" (&rest plist))
|
(declare-function ebox-create "ebox" (&rest plist))
|
||||||
|
|
||||||
@ -97,6 +98,20 @@ The first value is string content. The second value is a lazy child node."
|
|||||||
(when child-node
|
(when child-node
|
||||||
(list :ebox-content-node child-node)))))))
|
(list :ebox-content-node child-node)))))))
|
||||||
|
|
||||||
|
(defun ebox--build-text (items)
|
||||||
|
"Build one canonical TextNode from DSL ITEMS."
|
||||||
|
(let* ((split (ebox--build-split-attrs items))
|
||||||
|
(props (car split))
|
||||||
|
(payloads (delq nil (cdr split))))
|
||||||
|
(when props
|
||||||
|
(error "Ebox build: Text author properties are not implemented in this slice"))
|
||||||
|
(unless (= (length payloads) 1)
|
||||||
|
(error "ebox-build: text requires exactly one string payload"))
|
||||||
|
(unless (stringp (car payloads))
|
||||||
|
(error "ebox-build: text payload must be a string: %S" (car payloads)))
|
||||||
|
(apply #'ebox-text-create
|
||||||
|
(append props (list :value (car payloads))))))
|
||||||
|
|
||||||
(defun ebox--build-wrap-layout (tag props layout)
|
(defun ebox--build-wrap-layout (tag props layout)
|
||||||
"Wrap LAYOUT with box PROPS for DSL TAG, or return LAYOUT unchanged."
|
"Wrap LAYOUT with box PROPS for DSL TAG, or return LAYOUT unchanged."
|
||||||
(if (null props)
|
(if (null props)
|
||||||
@ -220,13 +235,14 @@ and `column' may also receive box properties; those properties wrap
|
|||||||
the child layout in a box using the rendered child layout as content."
|
the child layout in a box using the rendered child layout as content."
|
||||||
(cond
|
(cond
|
||||||
((ebox--node-p dsl) dsl)
|
((ebox--node-p dsl) dsl)
|
||||||
((stringp dsl) (ebox-create :content dsl))
|
((stringp dsl) (ebox-text-create :value dsl))
|
||||||
((not (and (consp dsl) (symbolp (car dsl))))
|
((not (and (consp dsl) (symbolp (car dsl))))
|
||||||
(error "ebox-build: invalid DSL node %S" dsl))
|
(error "ebox-build: invalid DSL node %S" dsl))
|
||||||
(t
|
(t
|
||||||
(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
|
||||||
|
('text (ebox--build-text items))
|
||||||
('box (ebox--build-canonical-box items))
|
('box (ebox--build-canonical-box items))
|
||||||
('ebox (ebox--build-box items))
|
('ebox (ebox--build-box items))
|
||||||
('row (ebox--build-layout tag #'ebox-row items))
|
('row (ebox--build-layout tag #'ebox-row items))
|
||||||
|
|||||||
62
ebox-node-factory.el
Normal file
62
ebox-node-factory.el
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
;;; ebox-node-factory.el --- Runtime box node factory -*- lexical-binding: t; -*-
|
||||||
|
|
||||||
|
;;; Commentary:
|
||||||
|
;; Owns the legacy runtime box encoding used by render/layout modules during
|
||||||
|
;; the typed canonical migration. Higher programmatic constructors may lower
|
||||||
|
;; through this factory, but author DSL parsing and buffer publication do not
|
||||||
|
;; belong here.
|
||||||
|
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(require 'ebox-style)
|
||||||
|
|
||||||
|
(defconst ebox--longhand
|
||||||
|
'(:content ""
|
||||||
|
:box-sizing border-box
|
||||||
|
:width nil :min-width 0 :max-width nil
|
||||||
|
:height nil :min-height 0 :max-height nil
|
||||||
|
:padding-left-pixel 0 :padding-right-pixel 0
|
||||||
|
:padding-top-height 0 :padding-bottom-height 0
|
||||||
|
:margin-left-pixel 0 :margin-right-pixel 0
|
||||||
|
:margin-top-height 0 :margin-bottom-height 0
|
||||||
|
:border-left-pixel 0 :border-left-color nil
|
||||||
|
:border-right-pixel 0 :border-right-color nil
|
||||||
|
:border-top-p nil :border-top-color nil
|
||||||
|
:border-bottom-p nil :border-bottom-color nil
|
||||||
|
:color nil :bgcolor nil
|
||||||
|
:font nil :font-family nil :font-height nil
|
||||||
|
:font-weight nil :font-slant nil
|
||||||
|
:text-align left :vertical-align top
|
||||||
|
:overflow scroll :visibility visible
|
||||||
|
:wrap-mode word :scroll-offset 0)
|
||||||
|
"Default longhand fields for the current runtime box encoding.")
|
||||||
|
|
||||||
|
(defun ebox--longhand-create (&optional plist)
|
||||||
|
"Return a runtime box field plist overridden by PLIST."
|
||||||
|
(let ((box (copy-sequence ebox--longhand)))
|
||||||
|
(while plist
|
||||||
|
(let ((key (pop plist))
|
||||||
|
(value (pop plist)))
|
||||||
|
(plist-put box key value)))
|
||||||
|
box))
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun ebox-create (&rest plist)
|
||||||
|
"Create one legacy runtime box from PLIST.
|
||||||
|
|
||||||
|
New framework integrations should use `ebox-text-create' and
|
||||||
|
`ebox-box-create'. This factory remains public during source-tree migration."
|
||||||
|
(let ((declarations (ebox-style-compile-declarations plist))
|
||||||
|
(expanded (ebox-style-expand-ebox-plist plist))
|
||||||
|
(controlled-scroll-offset-p (plist-member plist :scroll-offset)))
|
||||||
|
(let ((box (ebox--longhand-create expanded)))
|
||||||
|
(plist-put box :ebox-type 'box)
|
||||||
|
(plist-put box :display '(block flow))
|
||||||
|
(plist-put box :ebox-style-declarations declarations)
|
||||||
|
(when controlled-scroll-offset-p
|
||||||
|
(plist-put box :ebox-scroll-offset-controlled-p t))
|
||||||
|
box)))
|
||||||
|
|
||||||
|
(provide 'ebox-node-factory)
|
||||||
|
|
||||||
|
;;; ebox-node-factory.el ends here
|
||||||
108
ebox-style.el
108
ebox-style.el
@ -114,14 +114,14 @@
|
|||||||
:group geometry :dirty-kind geometry :signature layout
|
:group geometry :dirty-kind geometry :signature layout
|
||||||
:validator nonnegative-size)
|
:validator nonnegative-size)
|
||||||
(:name :border-top-width :id ebox/border-top-width
|
(:name :border-top-width :id ebox/border-top-width
|
||||||
:aliases (:border-top-p) :initial nil :contexts (box)
|
:initial nil :contexts (box)
|
||||||
:group geometry :dirty-kind geometry :signature layout)
|
:group geometry :dirty-kind geometry :signature layout)
|
||||||
(:name :border-right-width :id ebox/border-right-width
|
(:name :border-right-width :id ebox/border-right-width
|
||||||
:aliases (:border-right-pixel) :initial nil :contexts (box)
|
:aliases (:border-right-pixel) :initial nil :contexts (box)
|
||||||
:group geometry :dirty-kind geometry :signature layout
|
:group geometry :dirty-kind geometry :signature layout
|
||||||
:validator nonnegative-size)
|
:validator nonnegative-size)
|
||||||
(:name :border-bottom-width :id ebox/border-bottom-width
|
(:name :border-bottom-width :id ebox/border-bottom-width
|
||||||
:aliases (:border-bottom-p) :initial nil :contexts (box)
|
:initial nil :contexts (box)
|
||||||
:group geometry :dirty-kind geometry :signature layout)
|
:group geometry :dirty-kind geometry :signature layout)
|
||||||
(:name :border-left-width :id ebox/border-left-width
|
(:name :border-left-width :id ebox/border-left-width
|
||||||
:aliases (:border-left-pixel) :initial nil :contexts (box)
|
:aliases (:border-left-pixel) :initial nil :contexts (box)
|
||||||
@ -231,6 +231,14 @@
|
|||||||
(:name :border-right :id ebox/border-right :shorthand border-right)
|
(:name :border-right :id ebox/border-right :shorthand border-right)
|
||||||
(:name :border-bottom :id ebox/border-bottom :shorthand border-bottom)
|
(:name :border-bottom :id ebox/border-bottom :shorthand border-bottom)
|
||||||
(:name :border-left :id ebox/border-left :shorthand border-left)
|
(:name :border-left :id ebox/border-left :shorthand border-left)
|
||||||
|
(:name :border-top-p :id ebox/border-top-p
|
||||||
|
:shorthand border-top-present :validator boolean
|
||||||
|
:exclusive-outputs t
|
||||||
|
:group structure :dirty-kind structure :signature layout)
|
||||||
|
(:name :border-bottom-p :id ebox/border-bottom-p
|
||||||
|
:shorthand border-bottom-present :validator boolean
|
||||||
|
:exclusive-outputs t
|
||||||
|
:group structure :dirty-kind structure :signature layout)
|
||||||
(:name :border-width :id ebox/border-width :shorthand border-width
|
(:name :border-width :id ebox/border-width :shorthand border-width
|
||||||
:group geometry :dirty-kind geometry :signature layout)
|
:group geometry :dirty-kind geometry :signature layout)
|
||||||
(:name :border-style :id ebox/border-style :shorthand border-style
|
(:name :border-style :id ebox/border-style :shorthand border-style
|
||||||
@ -263,15 +271,25 @@ every `ebox-create' needlessly copies the entire property metadata domain.")
|
|||||||
(make-hash-table :test 'equal)
|
(make-hash-table :test 'equal)
|
||||||
"Bounded memo table for canonical Ebox declaration compilation.")
|
"Bounded memo table for canonical Ebox declaration compilation.")
|
||||||
|
|
||||||
|
(defun ebox-style--register-property-name (table name property role)
|
||||||
|
"Register NAME for PROPERTY ROLE in TABLE, rejecting collisions."
|
||||||
|
(when-let* ((existing (gethash name table)))
|
||||||
|
(unless (eq existing property)
|
||||||
|
(error "Ebox property %S %s collides with canonical property %S"
|
||||||
|
name role (plist-get existing :name))))
|
||||||
|
(puthash name property table))
|
||||||
|
|
||||||
(defun ebox-style--ensure-property-table ()
|
(defun ebox-style--ensure-property-table ()
|
||||||
"Return the canonical style property registry table."
|
"Return the canonical style property registry table."
|
||||||
(or ebox-style--property-table
|
(or ebox-style--property-table
|
||||||
(let ((table (make-hash-table :test 'eq)))
|
(let ((table (make-hash-table :test 'eq)))
|
||||||
(dolist (property ebox-style--property-definitions)
|
(dolist (property ebox-style--property-definitions)
|
||||||
(puthash (plist-get property :name) property table)
|
(ebox-style--register-property-name
|
||||||
(puthash (plist-get property :id) property table)
|
table (plist-get property :name) property "name")
|
||||||
|
(ebox-style--register-property-name
|
||||||
|
table (plist-get property :id) property "ID")
|
||||||
(dolist (alias (plist-get property :aliases))
|
(dolist (alias (plist-get property :aliases))
|
||||||
(puthash alias property table)))
|
(ebox-style--register-property-name table alias property "alias")))
|
||||||
(setq ebox-style--property-table table))))
|
(setq ebox-style--property-table table))))
|
||||||
|
|
||||||
(defun ebox-style-property (name)
|
(defun ebox-style-property (name)
|
||||||
@ -457,6 +475,22 @@ every `ebox-create' needlessly copies the entire property metadata domain.")
|
|||||||
"Expand left border VALUE to namespaced longhands."
|
"Expand left border VALUE to namespaced longhands."
|
||||||
(ebox-style--border-side-declarations 'left value))
|
(ebox-style--border-side-declarations 'left value))
|
||||||
|
|
||||||
|
(defun ebox-style--border-present-declarations (side value)
|
||||||
|
"Return canonical boolean border declarations for SIDE and VALUE."
|
||||||
|
(unless (memq value '(nil t))
|
||||||
|
(error "Ebox border-%s-p must be boolean: %S" side value))
|
||||||
|
(list (intern (format "ebox/border-%s-width" side)) (if value 1 0)
|
||||||
|
(intern (format "ebox/border-%s-style" side))
|
||||||
|
(if value 'solid 'none)))
|
||||||
|
|
||||||
|
(defun ebox-style--border-top-present-shorthand (value)
|
||||||
|
"Expand boolean top-border VALUE to canonical width and style."
|
||||||
|
(ebox-style--border-present-declarations 'top value))
|
||||||
|
|
||||||
|
(defun ebox-style--border-bottom-present-shorthand (value)
|
||||||
|
"Expand boolean bottom-border VALUE to canonical width and style."
|
||||||
|
(ebox-style--border-present-declarations 'bottom value))
|
||||||
|
|
||||||
(defun ebox-style--border-component-shorthand (component value)
|
(defun ebox-style--border-component-shorthand (component value)
|
||||||
"Expand border COMPONENT VALUE across all physical sides."
|
"Expand border COMPONENT VALUE across all physical sides."
|
||||||
(ebox-style--trbl-declarations
|
(ebox-style--trbl-declarations
|
||||||
@ -593,6 +627,7 @@ every `ebox-create' needlessly copies the entire property metadata domain.")
|
|||||||
('font-family #'ebox-style--font-family-p)
|
('font-family #'ebox-style--font-family-p)
|
||||||
('font-height #'ebox-style--font-height-p)
|
('font-height #'ebox-style--font-height-p)
|
||||||
('font-attribute #'ebox-style--font-attribute-p)
|
('font-attribute #'ebox-style--font-attribute-p)
|
||||||
|
('boolean (lambda (value) (memq value '(nil t))))
|
||||||
(_ (lambda (_value) t))))
|
(_ (lambda (_value) t))))
|
||||||
|
|
||||||
(defun ebox-style--shorthand (name)
|
(defun ebox-style--shorthand (name)
|
||||||
@ -610,6 +645,8 @@ every `ebox-create' needlessly copies the entire property metadata domain.")
|
|||||||
(border-right . ebox-style--border-right-shorthand)
|
(border-right . ebox-style--border-right-shorthand)
|
||||||
(border-bottom . ebox-style--border-bottom-shorthand)
|
(border-bottom . ebox-style--border-bottom-shorthand)
|
||||||
(border-left . ebox-style--border-left-shorthand)
|
(border-left . ebox-style--border-left-shorthand)
|
||||||
|
(border-top-present . ebox-style--border-top-present-shorthand)
|
||||||
|
(border-bottom-present . ebox-style--border-bottom-present-shorthand)
|
||||||
(border-width . ebox-style--border-width-shorthand)
|
(border-width . ebox-style--border-width-shorthand)
|
||||||
(border-style . ebox-style--border-style-shorthand)
|
(border-style . ebox-style--border-style-shorthand)
|
||||||
(border-color . ebox-style--border-color-shorthand)
|
(border-color . ebox-style--border-color-shorthand)
|
||||||
@ -694,6 +731,58 @@ cascade pass during its initial static projection."
|
|||||||
"Return non-nil when PLIST is a proper even-length list."
|
"Return non-nil when PLIST is a proper even-length list."
|
||||||
(and (proper-list-p plist) (zerop (% (length plist) 2))))
|
(and (proper-list-p plist) (zerop (% (length plist) 2))))
|
||||||
|
|
||||||
|
(defun ebox-style--validate-no-duplicate-properties (plist)
|
||||||
|
"Reject duplicate canonical properties in author PLIST."
|
||||||
|
(let ((seen (make-hash-table :test 'eq)))
|
||||||
|
(cl-loop for (name _value) on plist by #'cddr
|
||||||
|
for property = (ebox-style-property name)
|
||||||
|
for id = (and property (plist-get property :id))
|
||||||
|
when id
|
||||||
|
do (when-let* ((previous (gethash id seen)))
|
||||||
|
(error
|
||||||
|
"Duplicate Ebox property %S via %S and %S"
|
||||||
|
(plist-get property :name) previous name))
|
||||||
|
and do (puthash id name seen)))
|
||||||
|
plist)
|
||||||
|
|
||||||
|
(defun ebox-style--author-property-output-ids (name value)
|
||||||
|
"Return canonical output IDs produced by author NAME and VALUE."
|
||||||
|
(when-let* ((property (ebox-style-property name)))
|
||||||
|
(if-let* ((shorthand-name (plist-get property :shorthand))
|
||||||
|
(expander (ebox-style--shorthand shorthand-name)))
|
||||||
|
(cl-loop for (id _output) on (funcall expander value) by #'cddr
|
||||||
|
collect id)
|
||||||
|
(list (plist-get property :id)))))
|
||||||
|
|
||||||
|
(defun ebox-style--validate-exclusive-properties (plist)
|
||||||
|
"Reject author properties whose canonical outputs overlap in PLIST."
|
||||||
|
(let (entries)
|
||||||
|
(cl-loop for (name value) on plist by #'cddr
|
||||||
|
for property = (ebox-style-property name)
|
||||||
|
when property
|
||||||
|
do (push (list :name name :value value :property property)
|
||||||
|
entries))
|
||||||
|
(when (cl-some (lambda (entry)
|
||||||
|
(plist-get (plist-get entry :property)
|
||||||
|
:exclusive-outputs))
|
||||||
|
entries)
|
||||||
|
(dolist (entry entries)
|
||||||
|
(when (plist-get (plist-get entry :property) :exclusive-outputs)
|
||||||
|
(let ((outputs
|
||||||
|
(ebox-style--author-property-output-ids
|
||||||
|
(plist-get entry :name) (plist-get entry :value))))
|
||||||
|
(dolist (other entries)
|
||||||
|
(unless (eq entry other)
|
||||||
|
(when (cl-intersection
|
||||||
|
outputs
|
||||||
|
(ebox-style--author-property-output-ids
|
||||||
|
(plist-get other :name) (plist-get other :value))
|
||||||
|
:test #'eq)
|
||||||
|
(error "Ebox property %S cannot be combined with %S"
|
||||||
|
(plist-get entry :name)
|
||||||
|
(plist-get other :name))))))))))
|
||||||
|
plist)
|
||||||
|
|
||||||
(defun ebox-style--declaration-cache-key (plist strict)
|
(defun ebox-style--declaration-cache-key (plist strict)
|
||||||
"Return a stable style-only cache key for PLIST and STRICT.
|
"Return a stable style-only cache key for PLIST and STRICT.
|
||||||
Non-style node properties such as content, Host references, and callbacks do
|
Non-style node properties such as content, Host references, and callbacks do
|
||||||
@ -706,7 +795,12 @@ not affect declaration compilation and are intentionally excluded."
|
|||||||
(append canonical (list schema value)))
|
(append canonical (list schema value)))
|
||||||
else if (ebox-style--custom-property-p property)
|
else if (ebox-style--custom-property-p property)
|
||||||
do (setq canonical
|
do (setq canonical
|
||||||
(append canonical (list property value))))
|
(append canonical (list property value)))
|
||||||
|
else if strict
|
||||||
|
do (setq canonical
|
||||||
|
(append canonical
|
||||||
|
(list 'ebox-style/unknown-property
|
||||||
|
property value))))
|
||||||
(list strict canonical)))
|
(list strict canonical)))
|
||||||
|
|
||||||
(defun ebox-style--static-invalid-declaration-p (validator value)
|
(defun ebox-style--static-invalid-declaration-p (validator value)
|
||||||
@ -738,6 +832,8 @@ When STRICT is non-nil, reject properties outside the Ebox style domain."
|
|||||||
(ebox-style--ensure-properties)
|
(ebox-style--ensure-properties)
|
||||||
(unless (ebox-style--valid-plist-p plist)
|
(unless (ebox-style--valid-plist-p plist)
|
||||||
(user-error "Ebox style declarations must be an even property list"))
|
(user-error "Ebox style declarations must be an even property list"))
|
||||||
|
(ebox-style--validate-no-duplicate-properties plist)
|
||||||
|
(ebox-style--validate-exclusive-properties plist)
|
||||||
(let* ((cache-key (ebox-style--declaration-cache-key plist strict))
|
(let* ((cache-key (ebox-style--declaration-cache-key plist strict))
|
||||||
(missing (make-symbol "ebox-style-declaration-cache-missing"))
|
(missing (make-symbol "ebox-style-declaration-cache-missing"))
|
||||||
(cached (gethash cache-key ebox-style--declaration-cache missing)))
|
(cached (gethash cache-key ebox-style--declaration-cache missing)))
|
||||||
|
|||||||
81
ebox.el
81
ebox.el
@ -23,9 +23,11 @@
|
|||||||
"Directory containing the active Ebox Lisp sources.")
|
"Directory containing the active Ebox Lisp sources.")
|
||||||
|
|
||||||
(defconst ebox--compile-sources
|
(defconst ebox--compile-sources
|
||||||
'("ebox-cache.el" "ebox-style.el" "ebox-child-range.el" "ebox-tree.el" "ebox-measure.el"
|
'("ebox-cache.el" "ebox-style.el" "ebox-node-factory.el"
|
||||||
|
"ebox-child-range.el" "ebox-tree.el" "ebox-measure.el"
|
||||||
"ebox-fragment.el" "ebox-render-context.el" "ebox-layout.el"
|
"ebox-fragment.el" "ebox-render-context.el" "ebox-layout.el"
|
||||||
"ebox-flex.el" "ebox-grid.el" "ebox-buffer-backend.el" "ebox-incremental.el"
|
"ebox-flex.el" "ebox-grid.el" "ebox-canonical.el"
|
||||||
|
"ebox-buffer-backend.el" "ebox-incremental.el"
|
||||||
"ebox-native-commit.el" "ebox-surface.el" "ebox-viewport.el"
|
"ebox-native-commit.el" "ebox-surface.el" "ebox-viewport.el"
|
||||||
"ebox-dsl.el" "ebox-selector.el" "ebox.el"
|
"ebox-dsl.el" "ebox-selector.el" "ebox.el"
|
||||||
"ebox-native-reflow.el")
|
"ebox-native-reflow.el")
|
||||||
@ -39,6 +41,7 @@
|
|||||||
(require 'subr-x)
|
(require 'subr-x)
|
||||||
(require 'ebox-cache)
|
(require 'ebox-cache)
|
||||||
(require 'ebox-style)
|
(require 'ebox-style)
|
||||||
|
(require 'ebox-node-factory)
|
||||||
(require 'ebox-child-range)
|
(require 'ebox-child-range)
|
||||||
(require 'ebox-tree)
|
(require 'ebox-tree)
|
||||||
(require 'ebox-measure)
|
(require 'ebox-measure)
|
||||||
@ -47,6 +50,7 @@
|
|||||||
(require 'ebox-layout)
|
(require 'ebox-layout)
|
||||||
(require 'ebox-flex)
|
(require 'ebox-flex)
|
||||||
(require 'ebox-grid)
|
(require 'ebox-grid)
|
||||||
|
(require 'ebox-canonical)
|
||||||
(require 'ebox-buffer-backend)
|
(require 'ebox-buffer-backend)
|
||||||
(require 'ebox-incremental)
|
(require 'ebox-incremental)
|
||||||
(require 'ebox-surface)
|
(require 'ebox-surface)
|
||||||
@ -387,51 +391,6 @@ caller, such as the Playground preview, renders against a concrete window.")
|
|||||||
"Generate next unique region ID."
|
"Generate next unique region ID."
|
||||||
(cl-incf ebox--region-id-counter))
|
(cl-incf ebox--region-id-counter))
|
||||||
|
|
||||||
(defconst ebox--longhand
|
|
||||||
'( :content ""
|
|
||||||
:box-sizing border-box ;; content-box or border-box
|
|
||||||
|
|
||||||
;; 所有宽度的单位是像素,所有高度的单位是行数
|
|
||||||
:width nil :min-width 0 :max-width nil
|
|
||||||
:height nil :min-height 0 :max-height nil
|
|
||||||
|
|
||||||
:padding-left-pixel 0 :padding-right-pixel 0
|
|
||||||
:padding-top-height 0 :padding-bottom-height 0
|
|
||||||
:margin-left-pixel 0 :margin-right-pixel 0
|
|
||||||
:margin-top-height 0 :margin-bottom-height 0
|
|
||||||
|
|
||||||
;; 左右边框支持设置宽度
|
|
||||||
:border-left-pixel 0 :border-left-color nil
|
|
||||||
:border-right-pixel 0 :border-right-color nil
|
|
||||||
;; 上下边框由于 Emacs 限制,用 overline/underline 实现,不支持设置宽度
|
|
||||||
:border-top-p nil :border-top-color nil
|
|
||||||
:border-bottom-p nil :border-bottom-color nil
|
|
||||||
:color nil
|
|
||||||
:bgcolor nil
|
|
||||||
:font nil :font-family nil :font-height nil
|
|
||||||
:font-weight nil :font-slant nil
|
|
||||||
:text-align left :vertical-align top
|
|
||||||
;; supports: 'visible / 'hidden / 'scroll
|
|
||||||
:overflow scroll
|
|
||||||
;; supports: 'visible / 'hidden — hidden suppresses ink while the
|
|
||||||
;; layout footprint, buffer text, and metadata stay identical
|
|
||||||
:visibility visible
|
|
||||||
;; supports: 'word / 'char / 'kp
|
|
||||||
:wrap-mode word
|
|
||||||
:scroll-offset 0 ;; 当前滚动偏移(行数)
|
|
||||||
)
|
|
||||||
"Default plist for a box structure (longhand properties only).")
|
|
||||||
|
|
||||||
(defun ebox--longhand-create (&optional plist)
|
|
||||||
"Create a shorthand box structure.
|
|
||||||
PLIST is a property list that overrides the defaults."
|
|
||||||
(let ((box (copy-sequence ebox--longhand)))
|
|
||||||
(while plist
|
|
||||||
(let ((key (pop plist))
|
|
||||||
(value (pop plist)))
|
|
||||||
(plist-put box key value)))
|
|
||||||
box))
|
|
||||||
|
|
||||||
(defsubst ebox-get (box property)
|
(defsubst ebox-get (box property)
|
||||||
"Retrieve PROPERTY from BOX."
|
"Retrieve PROPERTY from BOX."
|
||||||
(plist-get box property))
|
(plist-get box property))
|
||||||
@ -869,21 +828,6 @@ Supports: t (default fg), nil, string, (light . dark) cons."
|
|||||||
|
|
||||||
;;; --- 核心 API ---
|
;;; --- 核心 API ---
|
||||||
|
|
||||||
;;;###autoload
|
|
||||||
(defun ebox-create (&rest plist)
|
|
||||||
"Create a box from PLIST. Supports all shorthand and longhand properties.
|
|
||||||
Properties are expanded recursively according to `ebox-property-rules'."
|
|
||||||
(let ((declarations (ebox-style-compile-declarations plist))
|
|
||||||
(expanded (ebox--expand-plist plist))
|
|
||||||
(controlled-scroll-offset-p (plist-member plist :scroll-offset)))
|
|
||||||
(let ((box (ebox--longhand-create expanded)))
|
|
||||||
(plist-put box :ebox-type 'box)
|
|
||||||
(plist-put box :display '(block flow))
|
|
||||||
(plist-put box :ebox-style-declarations declarations)
|
|
||||||
(when controlled-scroll-offset-p
|
|
||||||
(plist-put box :ebox-scroll-offset-controlled-p t))
|
|
||||||
box)))
|
|
||||||
|
|
||||||
;;; ============================================================
|
;;; ============================================================
|
||||||
;;; Buffer Runtime State & Scroll State
|
;;; Buffer Runtime State & Scroll State
|
||||||
;;; ============================================================
|
;;; ============================================================
|
||||||
@ -4643,6 +4587,9 @@ through dirty-set and patch-set execution before falling back to root rerender."
|
|||||||
ebox-candidate-patch-host-paint
|
ebox-candidate-patch-host-paint
|
||||||
ebox-clear-cache
|
ebox-clear-cache
|
||||||
ebox-column
|
ebox-column
|
||||||
|
ebox-normal-layout-create
|
||||||
|
ebox-text-create
|
||||||
|
ebox-box-create
|
||||||
ebox-commit
|
ebox-commit
|
||||||
ebox-concat
|
ebox-concat
|
||||||
ebox-create
|
ebox-create
|
||||||
@ -4724,8 +4671,16 @@ and are documented separately in the public API reference.")
|
|||||||
;; All other ebox-- prefixed functions are internal implementation details.
|
;; All other ebox-- prefixed functions are internal implementation details.
|
||||||
;;
|
;;
|
||||||
;; ── Creation ──────────────────────────────────────────────────
|
;; ── Creation ──────────────────────────────────────────────────
|
||||||
|
;; ebox-text-create :value string &rest canonical-measurement-props
|
||||||
|
;; Create one typed canonical TextNode.
|
||||||
|
;; ebox-normal-layout-create
|
||||||
|
;; Create the typed Normal LayoutConfig.
|
||||||
|
;; ebox-box-create :layout config :children nodes &rest geometry-props
|
||||||
|
;; Create one typed canonical BoxNode. The current vertical slice
|
||||||
|
;; accepts Normal layout with zero or one canonical child.
|
||||||
;; ebox-create &rest plist
|
;; ebox-create &rest plist
|
||||||
;; Create a box. Key options: :content :width :height
|
;; Legacy untyped runtime constructor retained during migration.
|
||||||
|
;; Key options: :content :width :height
|
||||||
;; :padding :margin :border :border-color :bgcolor
|
;; :padding :margin :border :border-color :bgcolor
|
||||||
;; :text-align :wrap-mode :box-sizing :surface-properties :host-ref
|
;; :text-align :wrap-mode :box-sizing :surface-properties :host-ref
|
||||||
;; ebox-build dsl
|
;; ebox-build dsl
|
||||||
|
|||||||
@ -5254,13 +5254,13 @@
|
|||||||
:padding (0 (8)) :border "#D97757")
|
:padding (0 (8)) :border "#D97757")
|
||||||
(box :content "Body" :width (120)
|
(box :content "Body" :width (120)
|
||||||
:padding (0 (8)) :border "#D97757"
|
:padding (0 (8)) :border "#D97757"
|
||||||
:border-top-p nil))
|
:border-top none))
|
||||||
(column
|
(column
|
||||||
(box :id right :content "Right" :width (120)
|
(box :id right :content "Right" :width (120)
|
||||||
:padding (0 (8)) :border "#5E7F6A")
|
:padding (0 (8)) :border "#5E7F6A")
|
||||||
(box :content "Body" :width (120)
|
(box :content "Body" :width (120)
|
||||||
:padding (0 (8)) :border "#5E7F6A"
|
:padding (0 (8)) :border "#5E7F6A"
|
||||||
:border-top-p nil)))))
|
:border-top none)))))
|
||||||
(left-id (plist-get
|
(left-id (plist-get
|
||||||
(car (ebox-selector-query-all layout "#left"))
|
(car (ebox-selector-query-all layout "#left"))
|
||||||
:region-id)))
|
:region-id)))
|
||||||
@ -5291,17 +5291,17 @@
|
|||||||
(column
|
(column
|
||||||
(box :id left :content "Left" :width (120)
|
(box :id left :content "Left" :width (120)
|
||||||
:padding (0 (8)) :border "#D97757"
|
:padding (0 (8)) :border "#D97757"
|
||||||
:border-bottom-p nil)
|
:border-bottom none)
|
||||||
(box :content "Body" :width (120)
|
(box :content "Body" :width (120)
|
||||||
:padding (0 (8)) :border "#D97757"
|
:padding (0 (8)) :border "#D97757"
|
||||||
:border-top-p nil))
|
:border-top none))
|
||||||
(column
|
(column
|
||||||
(box :content "Right" :width (120)
|
(box :content "Right" :width (120)
|
||||||
:padding (0 (8)) :border "#5E7F6A"
|
:padding (0 (8)) :border "#5E7F6A"
|
||||||
:border-bottom-p nil)
|
:border-bottom none)
|
||||||
(box :content "Body" :width (120)
|
(box :content "Body" :width (120)
|
||||||
:padding (0 (8)) :border "#5E7F6A"
|
:padding (0 (8)) :border "#5E7F6A"
|
||||||
:border-top-p nil)))))
|
:border-top none)))))
|
||||||
(left-id (plist-get
|
(left-id (plist-get
|
||||||
(car (ebox-selector-query-all layout "#left"))
|
(car (ebox-selector-query-all layout "#left"))
|
||||||
:region-id)))
|
:region-id)))
|
||||||
@ -5335,17 +5335,17 @@
|
|||||||
(column
|
(column
|
||||||
(box :id left :content "Left" :width (120)
|
(box :id left :content "Left" :width (120)
|
||||||
:padding (0 (8)) :border "#D97757"
|
:padding (0 (8)) :border "#D97757"
|
||||||
:border-bottom-p nil)
|
:border-bottom none)
|
||||||
(box :content "Body" :width (120)
|
(box :content "Body" :width (120)
|
||||||
:padding (0 (8)) :border "#D97757"
|
:padding (0 (8)) :border "#D97757"
|
||||||
:border-top-p nil))
|
:border-top none))
|
||||||
(column
|
(column
|
||||||
(box :content "Right" :width (120)
|
(box :content "Right" :width (120)
|
||||||
:padding (0 (8)) :border "#5E7F6A"
|
:padding (0 (8)) :border "#5E7F6A"
|
||||||
:border-bottom-p nil)
|
:border-bottom none)
|
||||||
(box :content "Body" :width (120)
|
(box :content "Body" :width (120)
|
||||||
:padding (0 (8)) :border "#5E7F6A"
|
:padding (0 (8)) :border "#5E7F6A"
|
||||||
:border-top-p nil)))))
|
:border-top none)))))
|
||||||
(left-id (plist-get
|
(left-id (plist-get
|
||||||
(car (ebox-selector-query-all layout "#left"))
|
(car (ebox-selector-query-all layout "#left"))
|
||||||
:region-id)))
|
:region-id)))
|
||||||
@ -8504,7 +8504,7 @@
|
|||||||
`(flex :width (240) :flex-flow (row wrap)
|
`(flex :width (240) :flex-flow (row wrap)
|
||||||
:row-gap 1 :column-gap 0
|
:row-gap 1 :column-gap 0
|
||||||
:padding (0 (8)) :border "#D97757"
|
:padding (0 (8)) :border "#D97757"
|
||||||
:border-top-p nil :bgcolor "#FFF8F2"
|
:border-top none :bgcolor "#FFF8F2"
|
||||||
,@(cl-loop for idx below 6
|
,@(cl-loop for idx below 6
|
||||||
collect
|
collect
|
||||||
`(box :id ,(format "prefix-item-%02d" idx)
|
`(box :id ,(format "prefix-item-%02d" idx)
|
||||||
@ -8610,7 +8610,7 @@
|
|||||||
:flex-flow (row wrap)
|
:flex-flow (row wrap)
|
||||||
:row-gap 1 :column-gap 0
|
:row-gap 1 :column-gap 0
|
||||||
:padding (0 (8)) :border "#D97757"
|
:padding (0 (8)) :border "#D97757"
|
||||||
:border-top-p nil :bgcolor "#FFF8F2"
|
:border-top none :bgcolor "#FFF8F2"
|
||||||
,@(cl-loop for idx below 8
|
,@(cl-loop for idx below 8
|
||||||
collect
|
collect
|
||||||
`(box :content ,(format "wrapper %02d" idx)
|
`(box :content ,(format "wrapper %02d" idx)
|
||||||
@ -8659,7 +8659,7 @@
|
|||||||
:flex-flow (row wrap)
|
:flex-flow (row wrap)
|
||||||
:row-gap 1 :column-gap 0
|
:row-gap 1 :column-gap 0
|
||||||
:padding (0 (8)) :border "#D97757"
|
:padding (0 (8)) :border "#D97757"
|
||||||
:border-top-p nil :bgcolor "#FFF8F2"
|
:border-top none :bgcolor "#FFF8F2"
|
||||||
,@(cl-loop for idx below 4
|
,@(cl-loop for idx below 4
|
||||||
collect
|
collect
|
||||||
`(box :content ,(format "complete %02d" idx)
|
`(box :content ,(format "complete %02d" idx)
|
||||||
@ -8788,7 +8788,7 @@
|
|||||||
:flex-flow (row wrap)
|
:flex-flow (row wrap)
|
||||||
:row-gap 1 :column-gap 0
|
:row-gap 1 :column-gap 0
|
||||||
:padding (0 (8)) :border "#D97757"
|
:padding (0 (8)) :border "#D97757"
|
||||||
:border-top-p nil :bgcolor "#FFF8F2"
|
:border-top none :bgcolor "#FFF8F2"
|
||||||
,@(cl-loop for idx below 8
|
,@(cl-loop for idx below 8
|
||||||
collect
|
collect
|
||||||
`(box :content ,(format "extend %02d" idx)
|
`(box :content ,(format "extend %02d" idx)
|
||||||
@ -8821,7 +8821,7 @@
|
|||||||
`(flex :width (240) :flex-flow (row wrap)
|
`(flex :width (240) :flex-flow (row wrap)
|
||||||
:row-gap 1 :column-gap 0
|
:row-gap 1 :column-gap 0
|
||||||
:padding (0 (8)) :border "#D97757"
|
:padding (0 (8)) :border "#D97757"
|
||||||
:border-top-p nil :bgcolor "#FFF8F2"
|
:border-top none :bgcolor "#FFF8F2"
|
||||||
,@(cl-loop for idx below 16
|
,@(cl-loop for idx below 16
|
||||||
collect
|
collect
|
||||||
`(box :content ,(format "piece %02d" idx)
|
`(box :content ,(format "piece %02d" idx)
|
||||||
@ -8852,7 +8852,7 @@
|
|||||||
:padding (0 (8) 2 (8))
|
:padding (0 (8) 2 (8))
|
||||||
:margin-bottom 2
|
:margin-bottom 2
|
||||||
:border "#D97757"
|
:border "#D97757"
|
||||||
:border-top-p nil
|
:border-top none
|
||||||
:bgcolor "#FFF8F2"
|
:bgcolor "#FFF8F2"
|
||||||
,@(cl-loop for idx below 2
|
,@(cl-loop for idx below 2
|
||||||
collect
|
collect
|
||||||
@ -8897,7 +8897,7 @@
|
|||||||
:row-gap 1 :column-gap 0
|
:row-gap 1 :column-gap 0
|
||||||
:padding (0 (8))
|
:padding (0 (8))
|
||||||
:border "#D97757"
|
:border "#D97757"
|
||||||
:border-top-p nil
|
:border-top none
|
||||||
:bgcolor "#FFF8F2"
|
:bgcolor "#FFF8F2"
|
||||||
,@(cl-loop for idx below 4
|
,@(cl-loop for idx below 4
|
||||||
collect
|
collect
|
||||||
@ -8934,7 +8934,7 @@
|
|||||||
:row-gap 1 :column-gap 0
|
:row-gap 1 :column-gap 0
|
||||||
:padding (0 (8))
|
:padding (0 (8))
|
||||||
:border "#D97757"
|
:border "#D97757"
|
||||||
:border-top-p nil
|
:border-top none
|
||||||
,@(cl-loop for idx below 4
|
,@(cl-loop for idx below 4
|
||||||
collect
|
collect
|
||||||
`(box :content ,(format "atomic %02d" idx)
|
`(box :content ,(format "atomic %02d" idx)
|
||||||
@ -11066,7 +11066,7 @@
|
|||||||
:flex-flow (row wrap)
|
:flex-flow (row wrap)
|
||||||
:row-gap 1 :column-gap (8)
|
:row-gap 1 :column-gap (8)
|
||||||
:padding (0 (12)) :border "#D97757"
|
:padding (0 (12)) :border "#D97757"
|
||||||
:border-top-p nil
|
:border-top none
|
||||||
,@(cl-loop for idx below 24
|
,@(cl-loop for idx below 24
|
||||||
collect
|
collect
|
||||||
`(box :content ,(format "signature %02d" idx)
|
`(box :content ,(format "signature %02d" idx)
|
||||||
|
|||||||
@ -52,6 +52,164 @@
|
|||||||
(ebox-style-apply-computed node style)
|
(ebox-style-apply-computed node style)
|
||||||
(should (equal (ebox--computed-display node) '(inline flex)))))
|
(should (equal (ebox--computed-display node) '(inline flex)))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-style-strict-cache-does-not-hide-unknown-properties ()
|
||||||
|
"Strict declaration validation should remain exact after an empty cache hit."
|
||||||
|
(ebox-style-compile-declarations nil t)
|
||||||
|
(should-error
|
||||||
|
(ebox-style-compile-declarations '(:not-an-ebox-property 1) t)
|
||||||
|
:type 'error))
|
||||||
|
|
||||||
|
(ert-deftest ebox-style-aliases-share-one-canonical-property-id ()
|
||||||
|
"A concise alias should disappear before canonical declarations."
|
||||||
|
(should
|
||||||
|
(equal (ebox-style-compile-declarations '(:bgcolor "red") t)
|
||||||
|
(ebox-style-compile-declarations '(:background-color "red") t))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-style-rejects-canonical-and-alias-duplicates ()
|
||||||
|
"One declaration source should not spell one property twice."
|
||||||
|
(should-error
|
||||||
|
(ebox-style-compile-declarations
|
||||||
|
'(:bgcolor "red" :background-color "blue") t)
|
||||||
|
:type 'error)
|
||||||
|
(should-error
|
||||||
|
(ebox-style-compile-declarations
|
||||||
|
'(:background-color "red" :bgcolor "blue") t)
|
||||||
|
:type 'error))
|
||||||
|
|
||||||
|
(ert-deftest ebox-style-property-registry-rejects-alias-collisions ()
|
||||||
|
"An alias should never shadow another canonical property."
|
||||||
|
(let ((ebox-style--property-table nil)
|
||||||
|
(ebox-style--property-definitions
|
||||||
|
'((:name :first :id ebox/first :aliases (:second))
|
||||||
|
(:name :second :id ebox/second))))
|
||||||
|
(should-error (ebox-style--ensure-property-table) :type 'error)))
|
||||||
|
|
||||||
|
(ert-deftest ebox-style-border-present-is-a-typed-shorthand-not-an-alias ()
|
||||||
|
"The concise border boolean should expand to canonical width and style."
|
||||||
|
(should
|
||||||
|
(equal (ebox-style-compile-declarations '(:border-top-p t) t)
|
||||||
|
'(ebox/border-top-width 1 ebox/border-top-style solid)))
|
||||||
|
(should
|
||||||
|
(equal (ebox-style-compile-declarations '(:border-top-p nil) t)
|
||||||
|
'(ebox/border-top-width 0 ebox/border-top-style none)))
|
||||||
|
(should (eq (ebox-style-canonical-name :border-top-p) :border-top-p)))
|
||||||
|
|
||||||
|
(ert-deftest ebox-style-exclusive-check-does-not-reexpand-ordinary-shorthands ()
|
||||||
|
"Conflict validation should leave the declaration cache effective."
|
||||||
|
(let ((calls 0)
|
||||||
|
(original (symbol-function 'ebox-style--padding-shorthand)))
|
||||||
|
(clrhash ebox-style--declaration-cache)
|
||||||
|
(cl-letf (((symbol-function 'ebox-style--padding-shorthand)
|
||||||
|
(lambda (value)
|
||||||
|
(cl-incf calls)
|
||||||
|
(funcall original value))))
|
||||||
|
(ebox-style-compile-declarations '(:padding 1) t)
|
||||||
|
(ebox-style-compile-declarations '(:padding 1) t))
|
||||||
|
(should (= calls 1))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-style-border-present-rejects-owned-longhand-conflicts ()
|
||||||
|
"One source should not mix the boolean border shorthand with its outputs."
|
||||||
|
(should-error
|
||||||
|
(ebox-style-compile-declarations
|
||||||
|
'(:border-top-p t :border-top-width 2) t)
|
||||||
|
:type 'error)
|
||||||
|
(should-error
|
||||||
|
(ebox-style-compile-declarations
|
||||||
|
'(:border-top-style dashed :border-top-p t) t)
|
||||||
|
:type 'error)
|
||||||
|
(should-error
|
||||||
|
(ebox-style-compile-declarations
|
||||||
|
'(:border-top-p t :border-top (2 dashed "red")) t)
|
||||||
|
:type 'error)
|
||||||
|
(should-error
|
||||||
|
(ebox-style-compile-declarations
|
||||||
|
'(:border-top-p t :border-width 2) t)
|
||||||
|
:type 'error)
|
||||||
|
(should-error
|
||||||
|
(ebox-style-compile-declarations
|
||||||
|
'(:border-style dashed :border-top-p t) t)
|
||||||
|
:type 'error)
|
||||||
|
(should-error
|
||||||
|
(ebox-style-compile-declarations
|
||||||
|
'(:border-bottom-p t :border (1 solid "red")) t)
|
||||||
|
:type 'error))
|
||||||
|
|
||||||
|
(ert-deftest ebox-canonical-text-constructor-is-renderable-and-typed ()
|
||||||
|
"A canonical TextNode should retain its kind, source, and string value."
|
||||||
|
(let ((node (ebox-text-create :value "Hello" :source-handle 'source-text)))
|
||||||
|
(should (ebox-text-node-p node))
|
||||||
|
(should-not (ebox-box-node-p node))
|
||||||
|
(should (eq (ebox-node-source-handle node) 'source-text))
|
||||||
|
(should (string= (ebox-text-node-value node) "Hello"))
|
||||||
|
(should (string= (ebox-dsl-test--plain node) "Hello"))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-canonical-text-constructor-rejects-non-text-shapes ()
|
||||||
|
"A canonical TextNode should have exactly one string payload."
|
||||||
|
(should-error (ebox-text-create :value 7) :type 'error)
|
||||||
|
(should-error (ebox-text-create :value "A" :content "B") :type 'error)
|
||||||
|
(should-error (ebox-text-create :value "A" :children nil) :type 'error)
|
||||||
|
(should-error (ebox-text-create :value "A" :color "red") :type 'error)
|
||||||
|
(should-error
|
||||||
|
(ebox-text-create :value "A" :value "B")
|
||||||
|
:type 'error)
|
||||||
|
(should-error
|
||||||
|
(ebox-text-create :value "A" :source-handle 'a :source-handle 'b)
|
||||||
|
:type 'error))
|
||||||
|
|
||||||
|
(ert-deftest ebox-canonical-normal-box-renders-one-typed-text-child ()
|
||||||
|
"A canonical BoxNode should retain typed Layout and child source facts."
|
||||||
|
(let* ((text (ebox-text-create :value "A" :source-handle 'source-text))
|
||||||
|
(layout (ebox-normal-layout-create))
|
||||||
|
(box (ebox-box-create :layout layout
|
||||||
|
:children (list text)
|
||||||
|
:source-handle 'source-box)))
|
||||||
|
(should (ebox-box-node-p box))
|
||||||
|
(should-not (ebox-text-node-p box))
|
||||||
|
(should (eq (ebox-node-source-handle box) 'source-box))
|
||||||
|
(should (eq (ebox-layout-config-kind
|
||||||
|
(ebox-box-node-layout box))
|
||||||
|
'normal))
|
||||||
|
(should (equal (ebox-box-node-children box) (list text)))
|
||||||
|
(should (string= (ebox-dsl-test--plain box) "A"))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-canonical-box-rejects-paint-only-properties ()
|
||||||
|
"Canonical Box geometry should not duplicate paint-only state."
|
||||||
|
(should-error
|
||||||
|
(ebox-box-create :layout (ebox-normal-layout-create)
|
||||||
|
:bgcolor "red")
|
||||||
|
:type 'error))
|
||||||
|
|
||||||
|
(ert-deftest ebox-canonical-box-requires-unique-reserved-fields ()
|
||||||
|
"Typed Box reserved fields should never be missing or duplicated."
|
||||||
|
(let ((normal (ebox-normal-layout-create)))
|
||||||
|
(should-error (ebox-box-create :children nil) :type 'error)
|
||||||
|
(should-error
|
||||||
|
(ebox-box-create :layout normal :layout normal :children nil)
|
||||||
|
:type 'error)
|
||||||
|
(should-error
|
||||||
|
(ebox-box-create :layout normal :children nil :children nil)
|
||||||
|
:type 'error)
|
||||||
|
(should-error
|
||||||
|
(ebox-box-create :layout normal :outer nil :children nil)
|
||||||
|
:type 'error)))
|
||||||
|
|
||||||
|
(ert-deftest ebox-build-normalizes-text-author-forms ()
|
||||||
|
"The Ebox DSL should normalize explicit and bare Text forms."
|
||||||
|
(let ((text (ebox-build '(text "A")))
|
||||||
|
(bare (ebox-build "B")))
|
||||||
|
(should (ebox-text-node-p text))
|
||||||
|
(should (ebox-text-node-p bare))
|
||||||
|
(should (string= (ebox-dsl-test--plain text) "A"))
|
||||||
|
(should (string= (ebox-dsl-test--plain bare) "B"))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-build-text-requires-one-string-payload ()
|
||||||
|
"The Ebox Text form should reject empty, multiple, and nested payloads."
|
||||||
|
(should-error (ebox-build '(text)) :type 'error)
|
||||||
|
(should-error (ebox-build '(text "A" "B")) :type 'error)
|
||||||
|
(should-error (ebox-build '(text (text "A"))) :type 'error)
|
||||||
|
(should-error (ebox-build '(text :color "red" "A")) :type 'error)
|
||||||
|
(should-error (ebox-build '(text :wrap-mode garbage "A")) :type 'error))
|
||||||
|
|
||||||
(ert-deftest ebox-dsl-module-preserves-public-build-syntax ()
|
(ert-deftest ebox-dsl-module-preserves-public-build-syntax ()
|
||||||
"Extracted DSL compiler should preserve existing ebox-build syntax."
|
"Extracted DSL compiler should preserve existing ebox-build syntax."
|
||||||
(let ((node (ebox-build
|
(let ((node (ebox-build
|
||||||
|
|||||||
@ -416,7 +416,7 @@
|
|||||||
:padding (0 (10)) :border "#5E7F6A"
|
:padding (0 (10)) :border "#5E7F6A"
|
||||||
:text-align center)
|
:text-align center)
|
||||||
(flex :id "body" :max-width (293) :padding (0 (8))
|
(flex :id "body" :max-width (293) :padding (0 (8))
|
||||||
:border "#5E7F6A" :border-top-p nil
|
:border "#5E7F6A" :border-top none
|
||||||
:flex-wrap wrap :column-gap (8)
|
:flex-wrap wrap :column-gap (8)
|
||||||
(item :flex-shrink 0
|
(item :flex-shrink 0
|
||||||
(box :content "A 118" :width (118)))
|
(box :content "A 118" :width (118)))
|
||||||
@ -439,14 +439,14 @@
|
|||||||
(layout
|
(layout
|
||||||
(ebox-build
|
(ebox-build
|
||||||
'(flex :flex-flow (row wrap) :row-gap 1 :column-gap (12)
|
'(flex :flex-flow (row wrap) :row-gap 1 :column-gap (12)
|
||||||
:padding (0 (12)) :border "#6F6A95" :border-top-p nil
|
:padding (0 (12)) :border "#6F6A95" :border-top none
|
||||||
:bgcolor "#FBF9FF"
|
:bgcolor "#FBF9FF"
|
||||||
(column
|
(column
|
||||||
(box :content ":flex-flow (row wrap)" :max-width (440)
|
(box :content ":flex-flow (row wrap)" :max-width (440)
|
||||||
:padding (0 (10)) :border "#6F6A95"
|
:padding (0 (10)) :border "#6F6A95"
|
||||||
:text-align center)
|
:text-align center)
|
||||||
(flex :max-width (440) :padding (0 (8))
|
(flex :max-width (440) :padding (0 (8))
|
||||||
:border "#6F6A95" :border-top-p nil
|
:border "#6F6A95" :border-top none
|
||||||
:flex-flow (row wrap) :gap (1 (12))
|
:flex-flow (row wrap) :gap (1 (12))
|
||||||
(item (box :content "A" :width (170)))
|
(item (box :content "A" :width (170)))
|
||||||
(item (box :content "B" :width (170)))
|
(item (box :content "B" :width (170)))
|
||||||
@ -456,7 +456,7 @@
|
|||||||
:padding (0 (10)) :border "#6F6A95"
|
:padding (0 (10)) :border "#6F6A95"
|
||||||
:text-align center)
|
:text-align center)
|
||||||
(flex :max-width (440) :padding (0 (8))
|
(flex :max-width (440) :padding (0 (8))
|
||||||
:border "#6F6A95" :border-top-p nil
|
:border "#6F6A95" :border-top none
|
||||||
:flex-flow (column wrap) :gap (1 (12))
|
:flex-flow (column wrap) :gap (1 (12))
|
||||||
(item (box :content "A" :width (140)))
|
(item (box :content "A" :width (140)))
|
||||||
(item (box :content "B" :width (140)))
|
(item (box :content "B" :width (140)))
|
||||||
|
|||||||
@ -11,10 +11,12 @@
|
|||||||
|
|
||||||
(defconst ebox-test--autoload-source-files
|
(defconst ebox-test--autoload-source-files
|
||||||
'("ebox.el"
|
'("ebox.el"
|
||||||
|
"ebox-node-factory.el"
|
||||||
"ebox-measure.el"
|
"ebox-measure.el"
|
||||||
"ebox-layout.el"
|
"ebox-layout.el"
|
||||||
"ebox-flex.el"
|
"ebox-flex.el"
|
||||||
"ebox-grid.el"
|
"ebox-grid.el"
|
||||||
|
"ebox-canonical.el"
|
||||||
"ebox-buffer-backend.el"
|
"ebox-buffer-backend.el"
|
||||||
"ebox-selector.el"
|
"ebox-selector.el"
|
||||||
"ebox-native-reflow.el")
|
"ebox-native-reflow.el")
|
||||||
@ -49,12 +51,34 @@
|
|||||||
"The public ebox facade should load all internal foundation modules."
|
"The public ebox facade should load all internal foundation modules."
|
||||||
(require 'ebox)
|
(require 'ebox)
|
||||||
(dolist (feature '(ebox-cache ebox-style ebox-tree ebox-measure
|
(dolist (feature '(ebox-cache ebox-style ebox-tree ebox-measure
|
||||||
|
ebox-node-factory
|
||||||
ebox-fragment ebox-layout ebox-flex ebox-grid
|
ebox-fragment ebox-layout ebox-flex ebox-grid
|
||||||
|
ebox-canonical
|
||||||
ebox-buffer-backend ebox-incremental
|
ebox-buffer-backend ebox-incremental
|
||||||
ebox-surface ebox-viewport ebox-dsl
|
ebox-surface ebox-viewport ebox-dsl
|
||||||
ebox-selector))
|
ebox-selector))
|
||||||
(should (featurep feature))))
|
(should (featurep feature))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-canonical-module-loads-typed-port-without-facade ()
|
||||||
|
"The canonical autoload boundary should not depend on loading `ebox.el'."
|
||||||
|
(let* ((emacs (expand-file-name invocation-name invocation-directory))
|
||||||
|
(parent (file-name-directory (directory-file-name ebox-test--root)))
|
||||||
|
(ecss (expand-file-name "ecss" parent))
|
||||||
|
(tp (expand-file-name "tp" parent)))
|
||||||
|
(with-temp-buffer
|
||||||
|
(should
|
||||||
|
(zerop
|
||||||
|
(process-file
|
||||||
|
emacs nil (current-buffer) nil
|
||||||
|
"-Q" "--batch"
|
||||||
|
"-L" ebox-test--root "-L" ecss "-L" tp
|
||||||
|
"--eval" "(setq load-prefer-newer t)"
|
||||||
|
"--eval"
|
||||||
|
(concat
|
||||||
|
"(progn (require 'ebox-canonical) "
|
||||||
|
"(unless (ebox-text-node-p (ebox-text-create :value \"A\")) "
|
||||||
|
" (error \"Typed Text autoload failed\")))")))))))
|
||||||
|
|
||||||
(ert-deftest ebox-facade-exposes-native-workflow-without-loading-it ()
|
(ert-deftest ebox-facade-exposes-native-workflow-without-loading-it ()
|
||||||
"The two native commands should be available without native load work."
|
"The two native commands should be available without native load work."
|
||||||
(require 'ebox)
|
(require 'ebox)
|
||||||
@ -67,10 +91,12 @@
|
|||||||
(should (commandp 'ebox-byte-compile))
|
(should (commandp 'ebox-byte-compile))
|
||||||
(should
|
(should
|
||||||
(equal ebox--compile-sources
|
(equal ebox--compile-sources
|
||||||
'("ebox-cache.el" "ebox-style.el" "ebox-child-range.el"
|
'("ebox-cache.el" "ebox-style.el" "ebox-node-factory.el"
|
||||||
|
"ebox-child-range.el"
|
||||||
"ebox-tree.el" "ebox-measure.el"
|
"ebox-tree.el" "ebox-measure.el"
|
||||||
"ebox-fragment.el" "ebox-render-context.el" "ebox-layout.el"
|
"ebox-fragment.el" "ebox-render-context.el" "ebox-layout.el"
|
||||||
"ebox-flex.el" "ebox-grid.el" "ebox-buffer-backend.el" "ebox-incremental.el"
|
"ebox-flex.el" "ebox-grid.el" "ebox-canonical.el"
|
||||||
|
"ebox-buffer-backend.el" "ebox-incremental.el"
|
||||||
"ebox-native-commit.el" "ebox-surface.el" "ebox-viewport.el"
|
"ebox-native-commit.el" "ebox-surface.el" "ebox-viewport.el"
|
||||||
"ebox-dsl.el" "ebox-selector.el" "ebox.el"
|
"ebox-dsl.el" "ebox-selector.el" "ebox.el"
|
||||||
"ebox-native-reflow.el"))))
|
"ebox-native-reflow.el"))))
|
||||||
@ -143,6 +169,9 @@
|
|||||||
("ebox-grid-fr" . "defun")
|
("ebox-grid-fr" . "defun")
|
||||||
("ebox-grid-item" . "defun")
|
("ebox-grid-item" . "defun")
|
||||||
("ebox-grid" . "defun")
|
("ebox-grid" . "defun")
|
||||||
|
("ebox-normal-layout-create" . "defun")
|
||||||
|
("ebox-text-create" . "defun")
|
||||||
|
("ebox-box-create" . "defun")
|
||||||
("ebox-build" . "defun")
|
("ebox-build" . "defun")
|
||||||
("ebox-create" . "defun")
|
("ebox-create" . "defun")
|
||||||
("ebox-scroll-down" . "defun")
|
("ebox-scroll-down" . "defun")
|
||||||
@ -178,6 +207,9 @@
|
|||||||
ebox-candidate-replace-host-ref
|
ebox-candidate-replace-host-ref
|
||||||
ebox-clear-cache
|
ebox-clear-cache
|
||||||
ebox-column
|
ebox-column
|
||||||
|
ebox-normal-layout-create
|
||||||
|
ebox-text-create
|
||||||
|
ebox-box-create
|
||||||
ebox-commit
|
ebox-commit
|
||||||
ebox-concat
|
ebox-concat
|
||||||
ebox-create
|
ebox-create
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user