ourui 0.3.2__tar.gz → 0.3.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. {ourui-0.3.2 → ourui-0.3.3}/PKG-INFO +2 -2
  2. {ourui-0.3.2 → ourui-0.3.3}/README.md +1 -1
  3. {ourui-0.3.2 → ourui-0.3.3}/ourui/analysis/__init__.py +45 -1
  4. {ourui-0.3.2 → ourui-0.3.3}/ourui/emit/html.py +130 -11
  5. {ourui-0.3.2 → ourui-0.3.3}/ourui/lowering/layout.py +6 -0
  6. {ourui-0.3.2 → ourui-0.3.3}/ourui/lowering/presentation.py +6 -0
  7. {ourui-0.3.2 → ourui-0.3.3}/ourui/lowering/render.py +6 -0
  8. {ourui-0.3.2 → ourui-0.3.3}/ourui/lsp/completions.py +1 -0
  9. {ourui-0.3.2 → ourui-0.3.3}/ourui/node.py +7 -1
  10. {ourui-0.3.2 → ourui-0.3.3}/ourui/pipeline.py +1 -1
  11. {ourui-0.3.2 → ourui-0.3.3}/ourui.egg-info/PKG-INFO +2 -2
  12. {ourui-0.3.2 → ourui-0.3.3}/pyproject.toml +1 -1
  13. {ourui-0.3.2 → ourui-0.3.3}/LICENSE +0 -0
  14. {ourui-0.3.2 → ourui-0.3.3}/ourui/__init__.py +0 -0
  15. {ourui-0.3.2 → ourui-0.3.3}/ourui/analysis/components.py +0 -0
  16. {ourui-0.3.2 → ourui-0.3.3}/ourui/cli.py +0 -0
  17. {ourui-0.3.2 → ourui-0.3.3}/ourui/design/__init__.py +0 -0
  18. {ourui-0.3.2 → ourui-0.3.3}/ourui/design/resolve.py +0 -0
  19. {ourui-0.3.2 → ourui-0.3.3}/ourui/emit/__init__.py +0 -0
  20. {ourui-0.3.2 → ourui-0.3.3}/ourui/emit/js.py +0 -0
  21. {ourui-0.3.2 → ourui-0.3.3}/ourui/lowering/__init__.py +0 -0
  22. {ourui-0.3.2 → ourui-0.3.3}/ourui/lowering/intent.py +0 -0
  23. {ourui-0.3.2 → ourui-0.3.3}/ourui/lsp/__init__.py +0 -0
  24. {ourui-0.3.2 → ourui-0.3.3}/ourui/lsp/protocol.py +0 -0
  25. {ourui-0.3.2 → ourui-0.3.3}/ourui/lsp/server.py +0 -0
  26. {ourui-0.3.2 → ourui-0.3.3}/ourui/parse/__init__.py +0 -0
  27. {ourui-0.3.2 → ourui-0.3.3}/ourui/runtime/__init__.py +0 -0
  28. {ourui-0.3.2 → ourui-0.3.3}/ourui/runtime/hmr.py +0 -0
  29. {ourui-0.3.2 → ourui-0.3.3}/ourui/runtime/invoke.py +0 -0
  30. {ourui-0.3.2 → ourui-0.3.3}/ourui/runtime/session.py +0 -0
  31. {ourui-0.3.2 → ourui-0.3.3}/ourui/serialize/__init__.py +0 -0
  32. {ourui-0.3.2 → ourui-0.3.3}/ourui/theme.py +0 -0
  33. {ourui-0.3.2 → ourui-0.3.3}/ourui/ui.py +0 -0
  34. {ourui-0.3.2 → ourui-0.3.3}/ourui.egg-info/SOURCES.txt +0 -0
  35. {ourui-0.3.2 → ourui-0.3.3}/ourui.egg-info/dependency_links.txt +0 -0
  36. {ourui-0.3.2 → ourui-0.3.3}/ourui.egg-info/entry_points.txt +0 -0
  37. {ourui-0.3.2 → ourui-0.3.3}/ourui.egg-info/top_level.txt +0 -0
  38. {ourui-0.3.2 → ourui-0.3.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ourui
3
- Version: 0.3.2
3
+ Version: 0.3.3
4
4
  Summary: Python-first UI compiler — write intent in Python, emit HTML/CSS/JS
5
5
  Author: OurUI contributors
6
6
  License: MIT License
@@ -46,7 +46,7 @@ Description-Content-Type: text/markdown
46
46
  License-File: LICENSE
47
47
  Dynamic: license-file
48
48
 
49
- # ourui 0.3.2
49
+ # ourui 0.3.3
50
50
 
51
51
  Python package for the **OurUI** compiler and runtime.
52
52
 
@@ -1,4 +1,4 @@
1
- # ourui 0.3.2
1
+ # ourui 0.3.3
2
2
 
3
3
  Python package for the **OurUI** compiler and runtime.
4
4
 
@@ -10,7 +10,14 @@ from ourui.analysis.components import (
10
10
  component_call_name,
11
11
  expand_component_call,
12
12
  )
13
- from ourui.node import FORM_CONTROL_KINDS, INPUT_TYPES, THEME_ATTR_KEYS, Node
13
+ from ourui.node import (
14
+ FORM_CONTROL_KINDS,
15
+ INPUT_TYPES,
16
+ NAV_PLACEMENTS,
17
+ NAV_TONES,
18
+ THEME_ATTR_KEYS,
19
+ Node,
20
+ )
14
21
  from ourui.parse import call_kind, literal_value, parse_file, span_for
15
22
  from ourui.theme import apply_theme_overrides, default_tokens, theme_kwargs_to_overrides
16
23
 
@@ -219,6 +226,39 @@ class _GraphBuilder:
219
226
  if cid:
220
227
  child_ids.append(cid)
221
228
  continue
229
+ if kind == "Nav" and kw.arg == "brand" and isinstance(kw.value, ast.Call):
230
+ cid = self.build_call(kw.value, parent_id=nid, expansion_trail=trail)
231
+ if cid:
232
+ attrs["brand"] = cid
233
+ child_ids.append(cid)
234
+ continue
235
+ if kind == "Nav" and kw.arg in {"items", "actions"}:
236
+ slot_ids: list[str] = []
237
+ val_node = kw.value
238
+ if isinstance(val_node, (ast.List, ast.Tuple)):
239
+ for elt in val_node.elts:
240
+ if isinstance(elt, ast.Call):
241
+ cid = self.build_call(elt, parent_id=nid, expansion_trail=trail)
242
+ if cid:
243
+ slot_ids.append(cid)
244
+ child_ids.append(cid)
245
+ elif isinstance(val_node, ast.Call):
246
+ cid = self.build_call(val_node, parent_id=nid, expansion_trail=trail)
247
+ if cid:
248
+ slot_ids.append(cid)
249
+ child_ids.append(cid)
250
+ attrs[kw.arg] = slot_ids
251
+ continue
252
+ if kind == "Nav" and kw.arg == "placement":
253
+ place = literal_value(kw.value)
254
+ if isinstance(place, str):
255
+ attrs["placement"] = place if place in NAV_PLACEMENTS else "sticky-top"
256
+ continue
257
+ if kind == "Nav" and kw.arg == "tone":
258
+ tone = literal_value(kw.value)
259
+ if isinstance(tone, str):
260
+ attrs["tone"] = tone if tone in NAV_TONES else "solid"
261
+ continue
222
262
  if kw.arg == "route" and kind == "Page":
223
263
  route_val = literal_value(kw.value)
224
264
  if isinstance(route_val, str):
@@ -255,6 +295,10 @@ class _GraphBuilder:
255
295
  continue
256
296
  attrs[kw.arg] = literal_value(kw.value)
257
297
 
298
+ if kind == "Nav":
299
+ attrs.setdefault("placement", "sticky-top")
300
+ attrs.setdefault("tone", "solid")
301
+
258
302
  provenance = ["parse:ui_call", "analyze:semantic_graph", *[f"expand:{n}" for n in trail]]
259
303
  node = Node(
260
304
  id=nid,
@@ -13,6 +13,7 @@ _ROLE_TAG: dict[str, str] = {
13
13
  "hero": "header",
14
14
  "section": "section",
15
15
  "shell": "div",
16
+ "nav": "nav",
16
17
  "card": "div",
17
18
  "grid": "div",
18
19
  "text": "span",
@@ -158,10 +159,84 @@ input.ourui-slider {
158
159
  max-width: 24rem;
159
160
  accent-color: var(--ourui-primary);
160
161
  }
162
+ .ourui-nav {
163
+ display: flex;
164
+ align-items: center;
165
+ gap: var(--ourui-space-md);
166
+ flex-wrap: wrap;
167
+ width: 100%;
168
+ box-sizing: border-box;
169
+ padding: var(--ourui-space-sm) var(--ourui-space-md);
170
+ z-index: 40;
171
+ }
172
+ .ourui-nav-brand {
173
+ display: flex;
174
+ align-items: center;
175
+ gap: var(--ourui-space-sm);
176
+ font-weight: 600;
177
+ }
178
+ .ourui-nav-items {
179
+ display: flex;
180
+ align-items: center;
181
+ gap: var(--ourui-space-md);
182
+ flex: 1 1 auto;
183
+ flex-wrap: wrap;
184
+ }
185
+ .ourui-nav-actions {
186
+ display: flex;
187
+ align-items: center;
188
+ gap: var(--ourui-space-sm);
189
+ margin-inline-start: auto;
190
+ }
191
+ .ourui-nav-solid {
192
+ background: var(--ourui-card);
193
+ color: var(--ourui-card-fg);
194
+ border-bottom: 1px solid var(--ourui-border);
195
+ }
196
+ .ourui-nav-glass {
197
+ background: color-mix(in srgb, var(--ourui-card) 72%, transparent);
198
+ color: var(--ourui-card-fg);
199
+ border-bottom: 1px solid var(--ourui-border);
200
+ backdrop-filter: blur(10px);
201
+ -webkit-backdrop-filter: blur(10px);
202
+ }
203
+ .ourui-nav-sticky-top {
204
+ position: sticky;
205
+ top: 0;
206
+ }
207
+ .ourui-nav-fixed-top {
208
+ position: fixed;
209
+ inset-inline: 0;
210
+ top: 0;
211
+ }
212
+ .ourui-nav-fixed-bottom {
213
+ position: fixed;
214
+ inset-inline: 0;
215
+ bottom: 0;
216
+ border-bottom: none;
217
+ border-top: 1px solid var(--ourui-border);
218
+ }
219
+ .ourui-nav-flow {
220
+ position: static;
221
+ }
222
+ .ourui-nav-overlay {
223
+ position: absolute;
224
+ inset-inline: 0;
225
+ top: 0;
226
+ }
227
+ .ourui-nav-backdrop {
228
+ position: absolute;
229
+ inset: 0;
230
+ z-index: 0;
231
+ pointer-events: none;
232
+ }
233
+ .ourui-root:has(.ourui-nav-fixed-top) {
234
+ padding-top: 3.75rem;
235
+ }
161
236
  """
162
237
  # Tone classes above use CSS vars seeded from Resolved Design (Host Contract).
163
238
  # Per-node rules from Resolved Design override for concrete fill/fg/radius/pad.
164
- # Form control chrome is host-private layout — not Design System.
239
+ # Form control + Nav chrome is host-private layout — not Design System.
165
240
 
166
241
  def _as_resolved_design(resolved_design: Any) -> dict[str, Any] | None:
167
242
  if resolved_design is None:
@@ -247,16 +322,17 @@ def _classes_for(node: dict[str, Any]) -> list[str]:
247
322
  shell = attrs.get("shell_layout")
248
323
  role = attrs.get("role", "")
249
324
  classes: list[str] = []
250
- if shell == "split-3":
251
- classes.append("ourui-shell-split-3")
252
- elif shell == "stack":
253
- classes.append("ourui-shell-stack")
254
- elif layout == "vertical":
255
- classes.append("ourui-col")
256
- elif layout == "horizontal":
257
- classes.append("ourui-row")
258
- elif layout == "grid":
259
- classes.append("ourui-grid")
325
+ if role != "nav":
326
+ if shell == "split-3":
327
+ classes.append("ourui-shell-split-3")
328
+ elif shell == "stack":
329
+ classes.append("ourui-shell-stack")
330
+ elif layout == "vertical":
331
+ classes.append("ourui-col")
332
+ elif layout == "horizontal":
333
+ classes.append("ourui-row")
334
+ elif layout == "grid":
335
+ classes.append("ourui-grid")
260
336
  if role == "card":
261
337
  classes.append("ourui-card")
262
338
  if role == "link":
@@ -277,6 +353,12 @@ def _classes_for(node: dict[str, Any]) -> list[str]:
277
353
  classes.append("ourui-toggle")
278
354
  if role == "slider":
279
355
  classes.append("ourui-slider")
356
+ if role == "nav":
357
+ classes.append("ourui-nav")
358
+ placement = attrs.get("placement") or "sticky-top"
359
+ tone = attrs.get("tone") or "solid"
360
+ classes.append(f"ourui-nav-{placement}")
361
+ classes.append(f"ourui-nav-{tone}")
280
362
  if (
281
363
  node["kind"] == "Leaf"
282
364
  and "ourui-control" not in classes
@@ -285,6 +367,7 @@ def _classes_for(node: dict[str, Any]) -> list[str]:
285
367
  and "ourui-select" not in classes
286
368
  and "ourui-toggle" not in classes
287
369
  and "ourui-slider" not in classes
370
+ and "ourui-nav" not in classes
288
371
  ):
289
372
  classes.append("ourui-leaf")
290
373
  return classes
@@ -454,6 +537,42 @@ def _render_node(nid: str, nodes: dict[str, dict[str, Any]], indent: int) -> lis
454
537
  attrs = f"{class_attr}{data_role}{data_id}{field}{events}"
455
538
  return _wrap_field(pad, node_attrs.get("label"), [f"<input{attrs} />"])
456
539
 
540
+ if role == "nav":
541
+ attrs = f"{class_attr}{data_role}{data_id}{events}"
542
+ brand = node_attrs.get("brand")
543
+ items = node_attrs.get("items") if isinstance(node_attrs.get("items"), list) else []
544
+ actions = node_attrs.get("actions") if isinstance(node_attrs.get("actions"), list) else []
545
+ slotted = set()
546
+ if isinstance(brand, str):
547
+ slotted.add(brand)
548
+ slotted.update(i for i in items if isinstance(i, str))
549
+ slotted.update(a for a in actions if isinstance(a, str))
550
+
551
+ def _slot(title: str, ids: list[str]) -> list[str]:
552
+ if not ids:
553
+ return []
554
+ out = [f'{pad} <div class="ourui-nav-{title}">']
555
+ for cid in ids:
556
+ if cid in nodes:
557
+ out.extend(_render_node(cid, nodes, indent + 2))
558
+ out.append(f"{pad} </div>")
559
+ return out
560
+
561
+ lines = [f"{pad}<nav{attrs}>"]
562
+ if isinstance(brand, str) and brand in nodes:
563
+ lines.append(f'{pad} <div class="ourui-nav-brand">')
564
+ lines.extend(_render_node(brand, nodes, indent + 2))
565
+ lines.append(f"{pad} </div>")
566
+ lines.extend(_slot("items", [i for i in items if isinstance(i, str)]))
567
+ lines.extend(_slot("actions", [a for a in actions if isinstance(a, str)]))
568
+ # Positional children not assigned to slots
569
+ for child_id in node.get("children", []):
570
+ if child_id in slotted or child_id not in nodes:
571
+ continue
572
+ lines.extend(_render_node(child_id, nodes, indent + 1))
573
+ lines.append(f"{pad}</nav>")
574
+ return lines
575
+
457
576
  children = node.get("children", [])
458
577
  attrs = f"{class_attr}{data_role}{data_id}{link}{events}"
459
578
 
@@ -11,6 +11,7 @@ _LAYOUT_KIND: dict[str, str] = {
11
11
  "Hero": "Column",
12
12
  "Section": "Column",
13
13
  "Shell": "Row",
14
+ "Nav": "Row",
14
15
  "Grid": "Grid",
15
16
  "Card": "Box",
16
17
  "Button": "Box",
@@ -56,6 +57,11 @@ _PASSTHROUGH_PROPS = (
56
57
  "min",
57
58
  "max",
58
59
  "step",
60
+ "placement",
61
+ "tone",
62
+ "brand",
63
+ "items",
64
+ "actions",
59
65
  )
60
66
 
61
67
 
@@ -12,6 +12,7 @@ _PRESENTATION_KINDS = frozenset(
12
12
  "Hero",
13
13
  "Section",
14
14
  "Shell",
15
+ "Nav",
15
16
  "Button",
16
17
  "Text",
17
18
  "Card",
@@ -42,6 +43,11 @@ _PASSTHROUGH = (
42
43
  "min",
43
44
  "max",
44
45
  "step",
46
+ "placement",
47
+ "tone",
48
+ "brand",
49
+ "items",
50
+ "actions",
45
51
  )
46
52
 
47
53
 
@@ -83,6 +83,11 @@ def lower_to_rtr(ltr: Any) -> RTR:
83
83
  "min",
84
84
  "max",
85
85
  "step",
86
+ "placement",
87
+ "tone",
88
+ "brand",
89
+ "items",
90
+ "actions",
86
91
  ):
87
92
  if key in attrs:
88
93
  props[key] = attrs[key]
@@ -149,6 +154,7 @@ def _role_for(from_intent: str) -> str:
149
154
  "Hero": "hero",
150
155
  "Section": "section",
151
156
  "Shell": "shell",
157
+ "Nav": "nav",
152
158
  "Button": "button",
153
159
  "Card": "card",
154
160
  "Text": "text",
@@ -21,6 +21,7 @@ UI_COMPONENTS: dict[str, str] = {
21
21
  "Grid": "Layout grid container.",
22
22
  "Link": "Presentation domain navigation anchor (href).",
23
23
  "Shell": "Intent domain layout region (layout=stack|row|split-3|grid).",
24
+ "Nav": "Chrome bar; placement= + tone=solid|glass; brand/items/actions slots.",
24
25
  "Theme": "Design token overrides for --ourui-* CSS variables.",
25
26
  }
26
27
 
@@ -60,7 +60,7 @@ class Node:
60
60
  }
61
61
 
62
62
 
63
- INTENT_KINDS = frozenset({"Page", "Hero", "Section", "Shell"})
63
+ INTENT_KINDS = frozenset({"Page", "Hero", "Section", "Shell", "Nav"})
64
64
  PRESENTATION_KINDS = frozenset(
65
65
  {"Button", "Text", "Card", "Grid", "Link", "Input", "Select", "Toggle", "Slider"}
66
66
  )
@@ -75,4 +75,10 @@ FORM_CONTROL_KINDS = frozenset({"Input", "Select", "Toggle", "Slider"})
75
75
  # Input type= enum (S2)
76
76
  INPUT_TYPES = frozenset({"text", "email", "password", "number", "search", "url", "tel"})
77
77
 
78
+ # Nav chrome (S3a)
79
+ NAV_PLACEMENTS = frozenset(
80
+ {"flow", "sticky-top", "fixed-top", "fixed-bottom", "overlay", "backdrop"}
81
+ )
82
+ NAV_TONES = frozenset({"solid", "glass"})
83
+
78
84
  THEME_ATTR_KEYS = frozenset({"variant", "color", "bg", "theme"})
@@ -53,7 +53,7 @@ def compile_dump(path: str | Path) -> dict[str, Any]:
53
53
  path = Path(path)
54
54
  artifacts = compile_to_rtr(path)
55
55
  return {
56
- "version": 14,
56
+ "version": 15,
57
57
  "source": artifacts["source"],
58
58
  "semantic_graph": artifacts["semantic_graph"].to_dict(),
59
59
  "dependency_graph": artifacts["dependency_graph"].to_dict(),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ourui
3
- Version: 0.3.2
3
+ Version: 0.3.3
4
4
  Summary: Python-first UI compiler — write intent in Python, emit HTML/CSS/JS
5
5
  Author: OurUI contributors
6
6
  License: MIT License
@@ -46,7 +46,7 @@ Description-Content-Type: text/markdown
46
46
  License-File: LICENSE
47
47
  Dynamic: license-file
48
48
 
49
- # ourui 0.3.2
49
+ # ourui 0.3.3
50
50
 
51
51
  Python package for the **OurUI** compiler and runtime.
52
52
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ourui"
7
- version = "0.3.2"
7
+ version = "0.3.3"
8
8
  description = "Python-first UI compiler — write intent in Python, emit HTML/CSS/JS"
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes