Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -1190,6 +1190,57 @@
.canvas-host [data-bareforge-container][data-bareforge-hint]:empty::before {
content: attr(data-bareforge-hint);
}
/* --- editor-only table fallback (no `columns` configured) -------- */
/* x-table is `display:grid` and x-table-row is
`grid-template-columns: subgrid`, both driven by the table's
`columns` attribute. With no `columns`:
- the table falls back to *implicit* tracks that size to
max-content, so its cells overflow the page box and ignore the
container's padding; and
- the row's subgrid collapses to one column, stacking cells
vertically.
Until `columns` is configured (it's inspector-editable), give the
table one shrinkable full-width track so it fits the container, and
flow each row's cells into equal auto columns so they read
horizontally. The moment the user sets `columns`, neither rule
matches and the real subgrid takes over — on the canvas and in the
export. Editor-only (.canvas-host). */
.canvas-host x-table:not([columns]),
.canvas-host x-table[columns=""] {
grid-template-columns: minmax(0, 1fr) !important;
}
.canvas-host x-table:not([columns]) x-table-row,
.canvas-host x-table[columns=""] x-table-row {
grid-template-columns: none !important;
grid-auto-flow: column !important;
grid-auto-columns: minmax(0, 1fr) !important;
}
/* The table/row/cell are nested grid items, and a grid item's default
`min-width: auto` (its content's min-content) blocks shrinking — so
populated cells refuse to fit and the row overflows the page box.
Let all three levels shrink to their track. Scoped to `:not(:empty)`
so EMPTY table elements keep the empty-container affordance's
`min-width: 120px` + `min-height: 80px` — otherwise an empty table
collapses in a flex/grid parent and has no droppable footprint to
drop the first row into. */
.canvas-host x-table:not(:empty),
.canvas-host x-table-row:not(:empty),
.canvas-host x-table-cell:not(:empty) { min-width: 0 !important; }
/* An EMPTY cell beside populated siblings is still a subgrid (or
auto-flow) grid item, not a standalone drop zone. The generic
empty-container affordance adds `margin: 4px 0`, and a stretched grid
item fills `track − margins` — so the empty cell renders ~8px shorter
than its populated siblings and sits vertically centred (4px gap top
and bottom) instead of filling the row. Its `min-width: 120px` also
fights the table's equal-width column tracks. Zero both for empty
table cells: the cell then stretches to the row height and follows its
column track, while the affordance's `min-height` floor + dashed
(empty) label survive so an all-empty row keeps a droppable footprint
(stretch wins whenever a sibling makes the row taller). */
.canvas-host x-table-cell[data-bareforge-container]:empty {
margin: 0;
min-width: 0;
}
/* Containers that host an absolute :background child get this
class from the reconciler so they form a positioned ancestor
without touching their own inline style. `isolation: isolate`
Expand Down
6 changes: 3 additions & 3 deletions src/bareforge/export/integrity.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@
;; --- pure: URL composition ----------------------------------------------

(defn manifest-url
"Compose the URL of BareDOM's integrity manifest at version `v` on
the CDN rooted at `cdn-base` (e.g. `https://cdn.jsdelivr.net/npm/
@vanelsas/baredom`)."
"Compose the URL of BareDOM's integrity manifest at version `v` on the
CDN rooted at `cdn-base`
(e.g. `https://cdn.jsdelivr.net/npm/@vanelsas/baredom`)."
[cdn-base v]
(str cdn-base "@" v "/dist/integrity.json"))

Expand Down
6 changes: 5 additions & 1 deletion src/bareforge/meta/augment.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -653,7 +653,11 @@
{:category :data
:label "Table"
:properties
[{:name "columns" :kind :number}
;; `columns` is a grid-template-columns track list; the :grid-columns
;; transform lets the inspector show/edit a simple count (3) while the
;; stored attribute stays a real track layout ("repeat(3, 1fr)").
;; Matches x-grid's columns field.
[{:name "columns" :kind :number :transform :grid-columns}
{:name "row-count" :kind :number}
{:name "caption" :kind :string-short}
{:name "selectable" :kind :enum :choices ["none" "single" "multi"]
Expand Down
72 changes: 72 additions & 0 deletions src/bareforge/meta/slots.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,78 @@
"x-grid"
[{:name "default" :label "Items" :multiple? true}]

"x-table"
[{:name "default" :label "Rows" :multiple? true}]

"x-table-row"
[{:name "default" :label "Cells" :multiple? true}]

"x-table-cell"
[{:name "default" :label "Content" :multiple? true}
{:name "sort-icon" :label "Sort icon" :multiple? false}]

;; --- structural containers that collapse to ~0 height when empty ---
;; Without an entry these fall through to the leaf default, so they get
;; no container affordance and can't be dropped into. They all hold
;; child components, so register a multiple-child slot.
"x-bento-grid"
[{:name "default" :label "Items" :multiple? true}]

"x-bento-item"
[{:name "default" :label "Content" :multiple? true}]

"x-fieldset"
[{:name "default" :label "Fields" :multiple? true}]

"x-form"
[{:name "default" :label "Fields" :multiple? true}]

"x-collapse"
[{:name "default" :label "Content" :multiple? true}]

"x-tabs"
[{:name "default" :label "Tabs" :multiple? true}]

"x-carousel"
[{:name "default" :label "Slides" :multiple? true}]

"x-breadcrumbs"
[{:name "default" :label "Items" :multiple? true}]

"x-timeline"
[{:name "default" :label "Items" :multiple? true}]

"x-timeline-item"
[{:name "label" :label "Label" :multiple? false}
{:name "icon" :label "Icon" :multiple? false}
{:name "default" :label "Content" :multiple? true}
{:name "actions" :label "Actions" :multiple? true}]

"x-spotlight-card"
[{:name "default" :label "Content" :multiple? true}]

"x-morph-stack"
[{:name "state" :label "States" :multiple? true}]

"x-proximity-list"
[{:name "default" :label "Items" :multiple? true}]

"x-scroll"
[{:name "default" :label "Content" :multiple? true}]

"x-scroll-stack"
[{:name "default" :label "Content" :multiple? true}]

"x-scroll-story"
[{:name "media" :label "Media" :multiple? false}
{:name "default" :label "Steps" :multiple? true}]

"x-scroll-timeline"
[{:name "default" :label "Content" :multiple? true}]

"x-scroll-parallax"
[{:name "default" :label "Content" :multiple? true}]

"x-split-pane"
[{:name "start" :label "Start panel" :multiple? true}
{:name "end" :label "End panel" :multiple? true}]
Expand Down
29 changes: 23 additions & 6 deletions src/bareforge/render/canvas_view.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -211,21 +211,38 @@
:pan-y (+ (unchecked-get pan-state "start-pan-y") dy))]
(state/set-canvas-view! view'))))

(defn editable-target?
"Pure: true when `el` is a text-editable widget (native input/
textarea/select or a contenteditable host). Used to decide whether
a Space keystroke is the user typing vs. arming the canvas pan."
[^js el]
(boolean
(and el
(or (.-isContentEditable el)
(contains? #{"input" "textarea" "select"}
(some-> el .-tagName .toLowerCase))))))

(defn- on-keydown!
"Track Space so a subsequent left-button drag pans. Ignored when the
keystroke targets an editable widget — typing a space into an
inspector field must not arm pan. Preview mode is also ignored so
spaces typed into the rendered page stay spaces."
spaces typed into the rendered page stay spaces.

Reads the deepest composed-path node, not `.-target`: inspector
fields are custom elements (x-search-field, x-text-area) whose real
input lives in shadow DOM. A keydown bubbling to window is retargeted
to the host, so `.-target` would be the custom-element tag and the
editable check would miss it — swallowing the space."
[^js e]
(when (and (= " " (.-key e))
(not (.-repeat e))
(not (space-down?))
(edit-mode?))
(let [^js t (.-target e)
tag (some-> t .-tagName .toLowerCase)
ce? (and t (.-isContentEditable t))]
(when-not (or ce?
(contains? #{"input" "textarea" "select"} tag))
(let [^js path (.composedPath e)
^js t (if (and path (pos? (.-length path)))
(aget path 0)
(.-target e))]
(when-not (editable-target? t)
(.preventDefault e)
(unchecked-set pan-state "space-down?" true)
(update-host-cursor!)))))
Expand Down
7 changes: 7 additions & 0 deletions src/bareforge/ui/palette.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,13 @@
;; integer-string coercion exists as a safety net for legacy
;; docs, not as the authoring default.
"x-grid" {:attrs {"columns" "repeat(3, 1fr)"}}
;; x-table is a CSS grid; its `columns` attr is a grid-template-columns
;; track list that its subgrid rows inherit. Seed a default 3-column
;; layout (same shape/convention as x-grid) so a dropped table has a
;; real, inspector-editable column config — its `columns` field shows
;; "3" via the :grid-columns transform — instead of an empty field and
;; cells that depend on the canvas auto-flow fallback.
"x-table" {:attrs {"columns" "repeat(3, 1fr)"}}
;; Overlay components default to `open=false`, so a bare drop renders
;; them closed — zero visible footprint on the canvas (collapsed
;; height, full-width host). Seed them open so they're visible and
Expand Down
10 changes: 9 additions & 1 deletion test/bareforge/meta/registry_test.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,15 @@

(deftest container-true-for-registered-multi-slot-tags
(doseq [tag ["x-container" "x-grid" "x-card" "x-navbar"
"x-modal" "x-drawer" "x-popover" "x-sidebar"]]
"x-modal" "x-drawer" "x-popover" "x-sidebar"
"x-table" "x-table-row" "x-table-cell"
;; structural containers registered so they're droppable
"x-bento-grid" "x-bento-item" "x-fieldset" "x-form"
"x-collapse" "x-tabs" "x-carousel" "x-breadcrumbs"
"x-timeline" "x-timeline-item" "x-spotlight-card"
"x-morph-stack" "x-proximity-list" "x-scroll"
"x-scroll-stack" "x-scroll-story" "x-scroll-timeline"
"x-scroll-parallax"]]
(is (true? (r/container? tag))
(str tag " should be a container"))))

Expand Down
25 changes: 25 additions & 0 deletions test/bareforge/render/canvas_view_test.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,28 @@
(is (= "83%" (cv/format-zoom-percent 0.834)))
(is (= "25%" (cv/format-zoom-percent cv/min-zoom)))
(is (= "400%" (cv/format-zoom-percent cv/max-zoom))))

;; --- editable-target? ----------------------------------------------------

(deftest editable-target-recognises-native-inputs
(testing "Native text-editable tags arm typing, not pan"
(is (true? (cv/editable-target? #js {:tagName "INPUT"})))
(is (true? (cv/editable-target? #js {:tagName "TEXTAREA"})))
(is (true? (cv/editable-target? #js {:tagName "SELECT"})))
(is (true? (cv/editable-target? #js {:tagName "input"}))
"tag match is case-insensitive")
(is (true? (cv/editable-target? #js {:isContentEditable true
:tagName "DIV"}))
"contenteditable host counts as editable")))

(deftest editable-target-rejects-non-editables
(testing "Non-editable targets fall through to the pan gesture"
(is (false? (cv/editable-target? #js {:tagName "DIV"})))
(is (false? (cv/editable-target? nil)))
;; The crux of the bug: a keydown bubbling out of an inspector
;; field's shadow DOM is retargeted to the custom-element host.
;; That host must still be treated as non-editable here — the real
;; editable check happens against composedPath()[0] (the inner
;; native input), not this retargeted host.
(is (false? (cv/editable-target? #js {:tagName "X-SEARCH-FIELD"})))
(is (false? (cv/editable-target? #js {:tagName "X-TEXT-AREA"})))))
14 changes: 12 additions & 2 deletions test/bareforge/render/slot_strips_test.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,25 @@
;; strip with a label). x-navbar / x-modal / x-drawer / x-popover /
;; x-split-pane are multi-slot containers (they get N subdivisions).
;; x-sidebar is a single-slot container (one full-size strip).
;; x-table / x-table-row / x-table-cell and the structural containers
;; (bento, fieldset, form, collapse, tabs, carousel, breadcrumbs,
;; timeline, scroll-*, …) each carry a :multiple? slot and qualify.
;; Leaves like x-button / x-typography / x-icon have no :multiple?
;; slot, so `classify-position` never returns :inside for them and
;; the strips would be dead zones — `render-strips?` excludes them.
(let [all-tags (keys slots/slots)
qualifiers (set (filter ss/render-strips? all-tags))]
(is (= #{"x-card" "x-grid" "x-container" "x-sidebar"
"x-navbar" "x-modal" "x-drawer" "x-popover" "x-split-pane"}
"x-navbar" "x-modal" "x-drawer" "x-popover" "x-split-pane"
"x-table" "x-table-row" "x-table-cell"
"x-bento-grid" "x-bento-item" "x-fieldset" "x-form"
"x-collapse" "x-tabs" "x-carousel" "x-breadcrumbs"
"x-timeline" "x-timeline-item" "x-spotlight-card"
"x-morph-stack" "x-proximity-list" "x-scroll"
"x-scroll-stack" "x-scroll-story" "x-scroll-timeline"
"x-scroll-parallax"}
qualifiers)
"nine container tags qualify; leaves (x-button / x-typography / x-icon) do not")))
"all registered multi-child containers qualify; leaves (x-button / x-typography / x-icon) do not")))
(testing "unknown tags fall through to single-slot default and return false"
(is (false? (ss/render-strips? "x-made-up-tag")))
(is (false? (ss/render-strips? nil)))
Expand Down
9 changes: 9 additions & 0 deletions test/bareforge/ui/inspector_test.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@
(let [node {:id "n" :tag "x-button" :attrs {} :props {}}]
(is (nil? (model/current-value node {:name "variant" :kind :enum})))))

(deftest grid-columns-transform-round-trips-table-columns
(testing "a seeded track layout shows as a plain count in the number field"
(let [node {:id "n" :tag "x-table" :attrs {"columns" "repeat(3, 1fr)"} :props {}}]
(is (= "3" (model/current-value
node {:name "columns" :kind :number :transform :grid-columns})))))
(testing "a typed count commits as a track layout, custom lists pass through"
(is (= "repeat(5, 1fr)" (model/transform-for-commit :grid-columns "5")))
(is (= "2fr 1fr 1fr" (model/transform-for-commit :grid-columns "2fr 1fr 1fr")))))

;; --- inspector-model ------------------------------------------------------

(deftest inspector-model-nil-without-selection
Expand Down
2 changes: 2 additions & 0 deletions test/bareforge/ui/palette_test.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@
(str tag " should seed open=\"\""))))
(testing "sidebar seeds full width so a flow drop is a droppable block"
(is (= "100%" (get-in (p/seed-for-tag "x-sidebar") [:layout :width]))))
(testing "table seeds a default 3-column track layout"
(is (= "repeat(3, 1fr)" (get-in (p/seed-for-tag "x-table") [:attrs "columns"]))))
(testing "unknown tags get empty overrides"
(is (= {} (p/seed-for-tag "x-some-unknown")))))

Expand Down
Loading