66
77## 1. Purpose
88
9- This specification defines how clipped elements scroll their children. It
10- covers the clip configuration API, the two scroll modes (manual and
11- pointer-driven), and the interaction between them.
9+ This specification defines how clipped elements scroll their children. It covers
10+ the clip configuration API, the two scroll modes (manual and pointer-driven),
11+ and the interaction between them.
1212
13- Scrolling builds on the existing clip infrastructure described in the
14- renderer spec (Section 12.2). This specification promotes clip/scroll from
15- an elastic surface to a specified contract.
13+ Scrolling builds on the existing clip infrastructure described in the renderer
14+ spec (Section 12.2). This specification promotes clip/scroll from an elastic
15+ surface to a specified contract.
1616
1717---
1818
@@ -49,17 +49,17 @@ clip?: {
4949
5050Each axis is independently configured:
5151
52- - ** ` undefined ` ** (or omitted) — No clipping or scrolling on this axis.
53- Children may overflow visually.
54- - ** ` number ` ** — Clipping is enabled. Children are offset by the given
55- value (in layout-engine units). The caller controls the offset each frame.
56- Typically negative to scroll content upward/leftward.
57- - ** ` "pointer" ` ** — Clipping is enabled. The offset is managed by the
58- underlying layout engine's scroll system, driven by wheel events passed
59- via ` RenderOptions.event ` .
52+ - ** ` undefined ` ** (or omitted) — No clipping or scrolling on this axis. Children
53+ may overflow visually.
54+ - ** ` number ` ** — Clipping is enabled. Children are offset by the given value (in
55+ layout-engine units). The caller controls the offset each frame. Typically
56+ negative to scroll content upward/leftward.
57+ - ** ` "pointer" ` ** — Clipping is enabled. The offset is managed by the underlying
58+ layout engine's scroll system, driven by wheel events passed via
59+ ` RenderOptions.event ` .
6060
61- A bare ` clip ` value is always an object with optional ` x ` and ` y ` fields.
62- There is no shorthand form.
61+ A bare ` clip ` value is always an object with optional ` x ` and ` y ` fields. There
62+ is no shorthand form.
6363
6464### 3.1 Examples
6565
@@ -69,7 +69,7 @@ Vertical pointer scrolling (typical scroll container):
6969open (" list" , {
7070 layout: { width: grow (), height: fixed (20 ) },
7171 clip: { y: " pointer" },
72- })
72+ });
7373```
7474
7575Manual horizontal offset (e.g., programmatic scroll-to):
@@ -78,7 +78,7 @@ Manual horizontal offset (e.g., programmatic scroll-to):
7878open (" viewport" , {
7979 layout: { width: fixed (40 ), height: fixed (10 ) },
8080 clip: { x: - scrollX },
81- })
81+ });
8282```
8383
8484Mixed — horizontal manual, vertical pointer:
@@ -87,22 +87,22 @@ Mixed — horizontal manual, vertical pointer:
8787open (" editor" , {
8888 layout: { width: grow (), height: grow () },
8989 clip: { x: - columnOffset , y: " pointer" },
90- })
90+ });
9191```
9292
9393---
9494
9595## 4. Pointer-Driven Scrolling
9696
97- When an axis is set to ` "pointer" ` , the underlying layout engine manages
98- scroll state for that axis across frames.
97+ When an axis is set to ` "pointer" ` , the underlying layout engine manages scroll
98+ state for that axis across frames.
9999
100100### 4.1 Wheel scrolling
101101
102- When a ` WheelEvent ` is passed via ` RenderOptions.event ` and the wheel
103- event's coordinates fall within a clip element that has a ` "pointer" ` axis,
104- the layout engine applies the scroll delta to that axis. The innermost
105- scroll container at the event coordinates takes priority.
102+ When a ` WheelEvent ` is passed via ` RenderOptions.event ` and the wheel event's
103+ coordinates fall within a clip element that has a ` "pointer" ` axis, the layout
104+ engine applies the scroll delta to that axis. The innermost scroll container at
105+ the event coordinates takes priority.
106106
107107### 4.2 Clamping
108108
@@ -113,19 +113,19 @@ within the container on a given axis cannot be scrolled on that axis.
113113### 4.3 Future: drag scrolling and momentum
114114
115115The clip configuration and rendering pipeline are designed to support
116- pointer-driven drag scrolling with momentum in the future. The underlying
117- layout engine already supports this via ` Clay_UpdateScrollContainers ` . When
118- added, it would be an opt-in behavior — the current wheel-only scrolling
119- would remain the default.
116+ pointer-driven drag scrolling with momentum in the future. The underlying layout
117+ engine already supports this via ` Clay_UpdateScrollContainers ` . When added, it
118+ would be an opt-in behavior — the current wheel-only scrolling would remain the
119+ default.
120120
121121---
122122
123123## 5. Input Event Integration
124124
125- Wheel events reach the scroll system via ` RenderOptions.event ` , which
126- accepts a single ` InputEvent ` per render transaction. When the event is a
127- ` WheelEvent ` whose coordinates fall within a pointer-driven scroll
128- container, the layout engine applies the scroll delta.
125+ Wheel events reach the scroll system via ` RenderOptions.event ` , which accepts a
126+ single ` InputEvent ` per render transaction. When the event is a ` WheelEvent `
127+ whose coordinates fall within a pointer-driven scroll container, the layout
128+ engine applies the scroll delta.
129129
130130The ` event ` field, its semantics, and the one-event-per-render contract are
131131defined in the [ Renderer Specification] ( renderer-spec.md ) , Section 8.2.3.
@@ -139,8 +139,8 @@ changes to support per-axis modes.
139139
140140For the clip configuration, the transfer encoding includes:
141141
142- - A packed word with axis modes: low byte = x mode, next byte = y mode.
143- Mode values: ` 0 ` = off, ` 1 ` = manual, ` 2 ` = pointer.
142+ - A packed word with axis modes: low byte = x mode, next byte = y mode. Mode
143+ values: ` 0 ` = off, ` 1 ` = manual, ` 2 ` = pointer.
144144- For each axis in mode ` 1 ` (manual): a float32 offset value.
145145
146146When mode is ` 2 ` , no offset value is encoded. The C side reads the scroll
@@ -154,37 +154,36 @@ while the element is open.
154154When decoding a clip configuration, the C side:
155155
1561561 . Reads axis modes from the packed word.
157- 2 . Sets ` decl.clip.horizontal ` / ` decl.clip.vertical ` to true for any
158- non-zero mode.
157+ 2 . Sets ` decl.clip.horizontal ` / ` decl.clip.vertical ` to true for any non-zero
158+ mode.
1591593 . For manual axes, reads the offset from the buffer.
1601604 . For pointer axes, reads the offset from ` Clay_GetScrollOffset() ` .
1611615 . Combines both into ` decl.clip.childOffset ` .
162162
163- This happens while the element is open (between ` Clay__OpenElementWithId `
164- and ` Clay__ConfigureOpenElement ` ), so ` Clay_GetScrollOffset() ` resolves to
165- the correct element.
163+ This happens while the element is open (between ` Clay__OpenElementWithId ` and
164+ ` Clay__ConfigureOpenElement ` ), so ` Clay_GetScrollOffset() ` resolves to the
165+ correct element.
166166
167167---
168168
169169## 8. Invariants
170170
171- ** SCROLL-1.** A clip axis set to ` undefined ` MUST NOT clip children on that
172- axis and MUST NOT participate in scroll input handling.
171+ ** SCROLL-1.** A clip axis set to ` undefined ` MUST NOT clip children on that axis
172+ and MUST NOT participate in scroll input handling.
173173
174- ** SCROLL-2.** A numeric clip axis MUST clip children and offset them by
175- exactly the provided value each frame. The layout engine's internal scroll
176- state for that axis MUST NOT affect the rendered offset.
174+ ** SCROLL-2.** A numeric clip axis MUST clip children and offset them by exactly
175+ the provided value each frame. The layout engine's internal scroll state for
176+ that axis MUST NOT affect the rendered offset.
177177
178- ** SCROLL-3.** A ` "pointer" ` clip axis MUST clip children and derive the
179- offset from the layout engine's scroll state, which is updated by wheel
180- events.
178+ ** SCROLL-3.** A ` "pointer" ` clip axis MUST clip children and derive the offset
179+ from the layout engine's scroll state, which is updated by wheel events.
181180
182- ** SCROLL-4.** Manual and pointer modes MAY be mixed on different axes of
183- the same element.
181+ ** SCROLL-4.** Manual and pointer modes MAY be mixed on different axes of the
182+ same element.
184183
185- ** SCROLL-5.** Scroll position on pointer axes MUST be clamped to content
186- bounds. A container whose content fits within it on a given axis MUST NOT
187- scroll on that axis.
184+ ** SCROLL-5.** Scroll position on pointer axes MUST be clamped to content bounds.
185+ A container whose content fits within it on a given axis MUST NOT scroll on that
186+ axis.
188187
189188** SCROLL-6.** When ` RenderOptions.event ` carries a ` WheelEvent ` , the scroll
190189delta MUST be applied to the innermost pointer-driven scroll container whose
@@ -238,40 +237,47 @@ logical scroll position as a positive number. Clayterm negates it internally
238237before passing to the layout engine.
239238
240239``` ts
241- clip : { y : 20 } // "show content starting at position 20"
240+ clip : {
241+ y : 20 ;
242+ } // "show content starting at position 20"
242243```
243244
244245Pros:
246+
245247- Matches ` scrollTop ` / ` scrollLeft ` conventions from the browser.
246248- More intuitive — "scroll to 20" rather than "offset by -20".
247- - Callers never deal with negative values for a conceptually positive
248- quantity.
249+ - Callers never deal with negative values for a conceptually positive quantity.
249250
250251Cons:
251- - Introduces a sign inversion between the API and the underlying layout
252- engine, which could confuse contributors reading the C code.
252+
253+ - Introduces a sign inversion between the API and the underlying layout engine,
254+ which could confuse contributors reading the C code.
253255- Inconsistent with Clay's ` childOffset ` model, which uses negative values
254256 natively.
255- - Clipping without scrolling (` clip: { y: 0 } ` ) works identically either
256- way, but the mental model differs — 0 means "scroll position zero" vs
257- "zero offset."
257+ - Clipping without scrolling (` clip: { y: 0 } ` ) works identically either way,
258+ but the mental model differs — 0 means "scroll position zero" vs "zero
259+ offset."
258260
259- ** Option B: Negative (raw offset semantics).** The caller provides the raw
260- pixel offset, typically negative. Clayterm passes it through to the layout
261- engine unchanged.
261+ ** Option B: Negative (raw offset semantics).** The caller provides the raw pixel
262+ offset, typically negative. Clayterm passes it through to the layout engine
263+ unchanged.
262264
263265``` ts
264- clip : { y : - 20 } // "offset children by -20 pixels"
266+ clip : {
267+ y : - 20 ;
268+ } // "offset children by -20 pixels"
265269```
266270
267271Pros:
272+
268273- Direct mapping to Clay's ` childOffset ` — no hidden transformation.
269274- Transparent to anyone reading the implementation.
270275- Allows positive offsets if the caller genuinely wants to shift content
271276 downward/rightward (unusual but not impossible).
272277
273278Cons:
279+
274280- Unnatural for the common case — scrolling down requires negative numbers.
275- - Easy to get the sign wrong, especially for callers unfamiliar with the
276- layout engine internals.
281+ - Easy to get the sign wrong, especially for callers unfamiliar with the layout
282+ engine internals.
277283- Every scroll-position calculation needs manual negation.
0 commit comments