hsx/src/element.lisp

122 lines
3.5 KiB
Common Lisp
Raw Normal View History

2024-05-25 12:48:35 +00:00
(defpackage #:hsx/element
2024-05-25 03:00:39 +00:00
(:use #:cl)
2024-05-25 16:29:58 +00:00
(:export #:element-type
2024-05-25 03:00:39 +00:00
#:element-props
2024-05-25 15:57:06 +00:00
#:element-children
2024-05-25 03:00:39 +00:00
#:create-element
2024-05-27 07:03:23 +00:00
#:expand-component))
2024-05-25 12:48:35 +00:00
(in-package #:hsx/element)
2024-05-25 03:00:39 +00:00
2024-05-26 14:29:30 +00:00
;;;; class definitions
2024-05-25 03:00:39 +00:00
(defclass element ()
2024-05-25 16:29:58 +00:00
((type
:reader element-type
:initarg :type)
2024-05-25 03:00:39 +00:00
(props
:reader element-props
2024-05-25 15:57:06 +00:00
:initarg :props)
(children
:reader element-children
:initarg :children)))
2024-05-25 03:00:39 +00:00
2024-05-26 14:29:30 +00:00
(defclass builtin-element (element) ())
(defclass tag-element (builtin-element) ())
(defclass html-tag-element (tag-element) ())
(defclass fragment-element (builtin-element) ())
(defclass component-element (element) ())
;;;; constructor
2024-05-25 16:29:58 +00:00
(defun create-element (type props &rest children)
2024-05-26 14:29:30 +00:00
(let ((elm (make-instance (cond ((functionp type) 'component-element)
((string= type "<>") 'fragment-element)
((string= type "html") 'html-tag-element)
(t 'tag-element))
2024-05-26 03:26:09 +00:00
:type type
:props props
:children (flatten children))))
2024-05-26 14:29:30 +00:00
(create-element-hook elm)
elm))
2024-05-25 03:00:39 +00:00
(defun flatten (x)
(labels ((rec (x acc)
(cond ((null x) acc)
((atom x) (cons x acc))
(t (rec
(car x)
(rec (cdr x) acc))))))
(rec x nil)))
2024-05-26 04:57:51 +00:00
2024-05-26 14:29:30 +00:00
(defmethod create-element-hook ((elm element)))
(defmethod create-element-hook ((elm fragment-element))
(when (element-props elm)
(error "Cannot pass props to fragment.")))
(defmethod create-element-hook ((elm component-element))
;dry-run to validate props
2024-05-27 07:03:23 +00:00
(expand-component elm))
2024-05-26 14:29:30 +00:00
;;;; methods
(defmethod print-object ((elm tag-element) stream)
(with-accessors ((type element-type)
(props element-props)
(children element-children)) elm
(if children
(format stream (if (rest children)
"~@<<~a~a>~2I~:@_~<~@{~a~^~:@_~}~:>~0I~:@_</~a>~:>"
"~@<<~a~a>~2I~:_~<~a~^~:@_~:>~0I~_</~a>~:>")
type
(props->string props)
children
type)
(format stream "<~a~a></~a>"
type
(props->string props)
type))))
(defun props->string (props)
(with-output-to-string (stream)
(loop
:for (key value) :on props :by #'cddr
:do (format stream (if (typep value 'boolean)
"~@[ ~a~]"
2024-05-27 09:11:04 +00:00
" ~a=\"~a\"")
2024-05-26 14:29:30 +00:00
(string-downcase key)
value))))
(defmethod print-object ((elm html-tag-element) stream)
(format stream "<!DOCTYPE html>~%")
(call-next-method))
(defmethod print-object ((elm fragment-element) stream)
(with-accessors ((children element-children)) elm
(if children
(format stream (if (rest children)
"~<~@{~a~^~:@_~}~:>"
"~<~a~:>")
children))))
(defmethod print-object ((elm component-element) stream)
2024-05-27 07:03:23 +00:00
(print-object (expand-component elm) stream))
(defmethod expand-component ((elm component-element))
(with-accessors ((type element-type)
(props element-props)
(children element-children)) elm
(apply type (merge-children-into-props props children))))
(defun merge-children-into-props (props children)
(append props
(and children
(list :children children))))