feat(style): generate checked class tokens
Add a boring CSS/SCSS class-token surface: slhx-build discovers static class tokens from hemplate/templates/stylesheets and emits generated CssClass constants. Add CssClasses for hemplate +class/view data and migrate techdemo dynamic/effect fragments toward hemplate-owned views with DOM-aware assertions. req: style/001 req: style/002 req: style/003 req: html_safety/002 req: view/001 req: test/005
This commit is contained in:
@@ -143,6 +143,85 @@ impl SafeHtml {
|
||||
}
|
||||
}
|
||||
|
||||
/// A generated, checked CSS class token.
|
||||
///
|
||||
/// Plain CSS/SCSS owns appearance; slhx only gives Rust a typed reference to
|
||||
/// class names discovered from build inputs. req: style/001
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct CssClass {
|
||||
name: &'static str,
|
||||
}
|
||||
|
||||
impl CssClass {
|
||||
pub const fn new(name: &'static str) -> Self {
|
||||
Self { name }
|
||||
}
|
||||
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
self.name
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for CssClass {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.name
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for CssClass {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.write_str(self.name)
|
||||
}
|
||||
}
|
||||
|
||||
/// A small displayable list of generated CSS class tokens for hemplate `+class`.
|
||||
/// req: style/003
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct CssClasses {
|
||||
names: String,
|
||||
}
|
||||
|
||||
impl CssClasses {
|
||||
pub fn new(classes: impl IntoIterator<Item = CssClass>) -> Self {
|
||||
let mut names = String::new();
|
||||
for class in classes {
|
||||
if !names.is_empty() {
|
||||
names.push(' ');
|
||||
}
|
||||
names.push_str(class.as_str());
|
||||
}
|
||||
Self { names }
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.names
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CssClass> for CssClasses {
|
||||
fn from(class: CssClass) -> Self {
|
||||
Self::new([class])
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> From<[CssClass; N]> for CssClasses {
|
||||
fn from(classes: [CssClass; N]) -> Self {
|
||||
Self::new(classes)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for CssClasses {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for CssClasses {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.write_str(&self.names)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct FormContract {
|
||||
pub fields: &'static [FormField],
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
use slhx_core::{event, navigate, redirect, replace, Atom, AtomSnapshot, AtomState, BuildFingerprint, Effect, EffectBatch, Form, IntoEffect, KeyedSlot, NavigateMode, Payload, ResourceKind, SafeHtml, ScopeKey, Slot};
|
||||
use slhx_core::{
|
||||
event, navigate, redirect, replace, Atom, AtomSnapshot, AtomState, BuildFingerprint,
|
||||
CssClass, CssClasses, Effect, EffectBatch, Form, IntoEffect, KeyedSlot, NavigateMode, Payload,
|
||||
ResourceKind, SafeHtml, ScopeKey, Slot,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn effect_batch_wire_round_trips() {
|
||||
@@ -88,6 +92,18 @@ fn slot_html_requires_explicit_safe_html() {
|
||||
assert_eq!(payload, Payload::Html(String::from("<strong>ok</strong>")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generated_css_classes_join_for_hemplate_dynamic_class_attrs() {
|
||||
// req: style/003
|
||||
const CARD: CssClass = CssClass::new("work-card");
|
||||
const SELECTED: CssClass = CssClass::new("is-selected");
|
||||
|
||||
let classes = CssClasses::from([CARD, SELECTED]);
|
||||
|
||||
assert_eq!(classes.as_str(), "work-card is-selected");
|
||||
assert_eq!(classes.to_string(), "work-card is-selected");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn atom_state_bootstrap_is_postcard_round_trippable() {
|
||||
let state = AtomState {
|
||||
|
||||
Reference in New Issue
Block a user