mosaic-headless 1.25.0 → 1.26.0

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.
@@ -128,3 +128,71 @@ this (they are gitignored - they were scaffolding, not a deliverable) — they s
128
128
  everything
129
129
  per node, because their job was to exercise the style compiler across eight visual
130
130
  languages, not to model a maintainable theme. A real site inverts that ratio.
131
+
132
+ ## A grouped menu, and two more places the placement table lied
133
+
134
+ Nine flat links in a header is what happens when a site grows and nobody re-reads
135
+ the nav. Mosaic's `dropdown` family is the fix and is worth knowing for its own
136
+ sake:
137
+
138
+ ```
139
+ dropdown horizontalAlign: left | center | right
140
+ dropdown-toggle url, target, rel; takes the label as children
141
+ dropdown-wrapper takes the links as children
142
+ ```
143
+
144
+ Both children are inserted by `onHeal()` if you leave them out, so only the two
145
+ need authoring. **It opens on CLICK, not hover** — the host gains
146
+ `dropdown--opening dropdown--opened` and the wrapper goes from `display:none` to
147
+ `display:flex`. A hover test reports a dropdown that does not work.
148
+
149
+ `dropdown-wrapper` reads as a leaf in `placement-rules.csv`, because `rule` is the
150
+ `canBeParentFor()` answer and that method is never overridden on it. But its
151
+ factory is `getDropdownWrapperElementDefaultData($data, $children)` and the editor
152
+ fills it. "Will the editor let me drop this here" is not the same question as "can
153
+ this hold children", and a build guard that reads only the first refuses correct
154
+ structures — it refused this menu. `extract_placement.py` now also records
155
+ `takes_children` from the factory signature, and `build_page.py` reads it.
156
+
157
+ That is the second table to be caught short the same way this week. The first was
158
+ `default-children.csv`, which held only the declared `getDefaultData()` statics and
159
+ so knew nothing about children a type HEALS into itself — modal's overlay, loop's
160
+ `loop-items`, slider's `slider-slides`, dropdown's own two. Both fixes are the same
161
+ move: the question the table answers was narrower than the question the table's
162
+ name suggests, so read the other source as well and say which is which.
163
+
164
+ ## Component states live on the element the selector names
165
+
166
+ Two of Mosaic's component states read as a parent/child pair and only one of them
167
+ is about the component itself:
168
+
169
+ ```
170
+ ___tab--active &.m-tab--active the tab
171
+ ___tab--active___descendants .m-tab--active & anything inside it
172
+ ___slider_navigation_bullet--active &.m-slider-bullet--active
173
+ ___slide--active .m-slide--active > &
174
+ ___arrow_hidden &:where(.m-slider-arrow--hidden)
175
+ ```
176
+
177
+ `___tab--active___descendants` compiles with `&` in the DESCENDANT position, so it
178
+ must be committed on the child node. Put it on the tab and it reads "a descendant
179
+ of an active tab that is also that tab", which matches nothing — the plugin
180
+ darkens the active tab, the label keeps the colour you gave it, and the current
181
+ tab is ink on ink. Measured both ways; the fix is to style the label node, not
182
+ the tab.
183
+
184
+ The same shape governs `___slide--active` (`.m-slide--active > &`, so it goes on
185
+ the slide's content) and the slider bullet's own `--active`, which is what turns a
186
+ row of identical hairlines into a position indicator.
187
+
188
+ ## What `openstreetmap` is called
189
+
190
+ `latitude`, `longitude`, `zoom`, `layer`, `lazyLoad` — not `lat` / `lon`. An
191
+ unknown property name is dropped without a word and the element falls back to its
192
+ default centre, which is Times Square: a map that renders, looks deliberate, and
193
+ is in the wrong hemisphere. The three coordinate properties take
194
+ `ValidatorDynamicCodeObject`, so the `{"v": "…"}` object form.
195
+
196
+ The element is an `<iframe>` to openstreetmap.org, not a tile layer, so "how many
197
+ tiles loaded" is unanswerable from the parent document — assert on the frame's
198
+ `src`, which carries `marker=lat,lon`.
@@ -101,8 +101,9 @@ const counts = {
101
101
  "data/dialog-verification.csv": 19,
102
102
  "data/slider-verification.csv": 30,
103
103
  "data/token-verification.csv": 15,
104
- "data/mobile-audit.csv": 54,
105
- "data/navigation-verification.csv": 27,
104
+ "data/mobile-audit.csv": 58,
105
+ "data/component-wall-verification.csv": 11,
106
+ "data/navigation-verification.csv": 24,
106
107
  "data/page-modal-verification.csv": 19,
107
108
  "data/interaction-rules-verification.csv": 17,
108
109
  "data/dialog-form-verification.csv": 49,
package/sites/_moksa.py CHANGED
@@ -277,9 +277,42 @@ def mask_line(attr, text, tag="h1", **st):
277
277
 
278
278
 
279
279
  # ── content, all of it Moksa Web's own ───────────────────────────────────────
280
+ # Nine flat links was the wrong answer to "the site grew". The top level is the
281
+ # studio's own site; everything that demonstrates what Mosaic can DO is one
282
+ # labelled group, which is also the only honest shape - those five pages are a
283
+ # set, not five more sections of the homepage.
280
284
  NAV = [("服務", "#services"), ("作品", "#works"),
281
- ("產品", "#products"), ("彈窗", "/dialogs/"), ("輪播", "/sliders/"), ("系統", "/tokens/"), ("資料", "/dynamic/"), ("轉換", "/conversion/"),
282
- ("聯絡", "#contact")]
285
+ ("產品", "#products"), ("聯絡", "#contact")]
286
+
287
+ # Read from the shipped tables rather than typed, so the menu cannot claim a
288
+ # number the evidence no longer supports.
289
+ def _count(table, fn):
290
+ import csv as _c, io as _i, os as _o
291
+ path = _o.path.join(_o.path.dirname(_o.path.abspath(__file__)), "..", "data", table)
292
+ with _i.open(path, encoding="utf-8") as fh:
293
+ return fn(list(_c.DictReader(fh)))
294
+
295
+
296
+ TOKENABLE_COUNT = _count("style-properties.csv",
297
+ lambda rs: sum(1 for r in rs if r["tokenable"]))
298
+ CONVERTED_COUNT = _count("conversion-batch.csv",
299
+ lambda rs: sum(int(r["converted"]) for r in rs))
300
+
301
+ # label, href, one line of what it demonstrates
302
+ CAPABILITIES = [
303
+ ("元件牆", "/components/",
304
+ "分頁、手風琴、表單、地圖——都能直接操作"),
305
+ ("彈窗與觸發", "/dialogs/",
306
+ "九種形態、四種觸發、四種執行規則"),
307
+ ("輪播", "/sliders/",
308
+ "四種動畫並排、輪轉模式、鍵盤與減少動態"),
309
+ ("設計系統", "/tokens/",
310
+ "設計代幣、variant 層、%d 個接得住代幣的屬性" % TOKENABLE_COUNT),
311
+ ("動態內容", "/dynamic/",
312
+ "迴圈、@VAR 表達式、自訂欄位"),
313
+ ("Elementor 轉換", "/conversion/",
314
+ "19 頁、%s 個元素、每一段字都核過" % "{:,}".format(CONVERTED_COUNT)),
315
+ ]
283
316
 
284
317
  # key / figure / caption, read as a datasheet rather than as four big numbers
285
318
  SPEC = [
@@ -1322,6 +1355,80 @@ def motion_css():
1322
1355
 
1323
1356
 
1324
1357
  # ── the shared shell ──────────────────────────────────────────────────────────
1358
+ NAV_LINK = {"color": {"token": "--mk-ink"}, "fontSize": "13px",
1359
+ "fontWeight": "400", "letterSpacing": "0.04em", "fontFamily": MONO,
1360
+ "cursor": "pointer"}
1361
+
1362
+
1363
+ def capability_dropdown():
1364
+ """The capability pages as one labelled group.
1365
+
1366
+ `dropdown` heals in its own `dropdown-toggle` and `dropdown-wrapper`, so only
1367
+ the two are authored here and the wrapper's children are the links. The
1368
+ wrapper reads as a leaf in `placement-rules.csv` because `canBeParentFor()`
1369
+ is never overridden on it - but its factory is
1370
+ `getDropdownWrapperElementDefaultData($data, $children)`, which is why that
1371
+ table now carries a `takes_children` column and the build guard reads it.
1372
+ """
1373
+ return {"type": "dropdown",
1374
+ "data": {"attrID": "mk-cap", "horizontalAlign": "right"},
1375
+ "style": {"&": {"_": {"position": "relative"}}},
1376
+ "children": [
1377
+ {"type": "dropdown-toggle",
1378
+ "data": {"attrID": "mk-cap-toggle"},
1379
+ "style": {"&": {"_": dict(NAV_LINK, **{
1380
+ "display": "flex", "alignItems": "center", "columnGap": "6px",
1381
+ "customDeclarations":
1382
+ "white-space:nowrap;"}),
1383
+ "_t": {"fontSize": "12px", "paddingTop": "8px",
1384
+ "paddingBottom": "8px"}},
1385
+ "hover": {"_": {"color": {"token": "--mk-accent"}}},
1386
+ "focus-visible": {"_": dict(FOCUS_RING)}},
1387
+ "children": [
1388
+ T("p", "能力展示", fontFamily=MONO,
1389
+ fontSize="13px", letterSpacing="0.04em",
1390
+ color={"token": "--mk-ink"}, _t={"fontSize": "12px"}),
1391
+ T("p", "+", fontFamily=MONO, fontSize="11px",
1392
+ color={"token": "--mk-faint"}),
1393
+ ]},
1394
+ {"type": "dropdown-wrapper",
1395
+ "data": {"attrID": "mk-cap-menu"},
1396
+ "style": bp({"backgroundColor": {"token": "--mk-paper"},
1397
+ "paddingTop": "10px", "paddingBottom": "10px",
1398
+ "minWidth": "272px",
1399
+ "customDeclarations":
1400
+ "border:1px solid rgb(22,24,28);"
1401
+ "box-shadow:0 20px 48px rgba(22,24,28,.14);"},
1402
+ None,
1403
+ # NOT width:100% - that is 100% of the toggle-sized
1404
+ # host, and the panel came out 79px wide and 1124px
1405
+ # tall. A fixed width, right-aligned, fits 390px.
1406
+ {"minWidth": "0px", "width": "296px",
1407
+ "customDeclarations":
1408
+ "border:1px solid rgb(22,24,28);"
1409
+ "box-shadow:0 18px 40px rgba(22,24,28,.16);"}),
1410
+ "children": [
1411
+ {"type": "menu-link",
1412
+ "data": {"attrID": "mk-cap-%d" % i, "url": href},
1413
+ "style": {"&": {"_": {
1414
+ "display": "block", "cursor": "pointer",
1415
+ "paddingTop": "9px", "paddingBottom": "9px",
1416
+ "paddingLeft": "16px", "paddingRight": "16px"}},
1417
+ "hover": {"_": {
1418
+ "backgroundColor": {"token": "--mk-panel"}}},
1419
+ "focus-visible": {"_": dict(FOCUS_RING)}},
1420
+ "children": [
1421
+ T("p", label, fontFamily=CJK, fontSize="13px",
1422
+ fontWeight="500", color={"token": "--mk-ink"}),
1423
+ T("p", blurb, fontFamily=CJK, fontSize="11px",
1424
+ lineHeight="1.6", color={"token": "--mk-muted"},
1425
+ marginTop="3px"),
1426
+ ]}
1427
+ for i, (label, href, blurb) in enumerate(CAPABILITIES)
1428
+ ]},
1429
+ ]}
1430
+
1431
+
1325
1432
  HEADER = box("mk-shell-top", {}, [
1326
1433
  # a `code` node with insertLocation "head" is the only way to get @keyframes into
1327
1434
  # the document - customDeclarations is emitted inside a rule and cannot hold one
@@ -1404,7 +1511,7 @@ HEADER = box("mk-shell-top", {}, [
1404
1511
  "focus-visible": {"_": FOCUS_RING}},
1405
1512
  "text": text}
1406
1513
  for i, (text, href) in enumerate(NAV)
1407
- ]},
1514
+ ] + [capability_dropdown()]},
1408
1515
  # not a pill: a bracketed link, the way a document cross-references
1409
1516
  {"type": "button", "data": {"attrID": "mk-header-cta",
1410
1517
  "url": "#contact"},
@@ -3117,6 +3224,10 @@ if __name__ == "__main__":
3117
3224
  # helpers it reads are pure functions - two copies produce identical dicts.
3118
3225
  import _dialogs
3119
3226
  import _sliders
3227
+ import _components
3228
+ SITE["pages"].append({"slug": "components", "post_id": 281,
3229
+ "title": "元件",
3230
+ "tree": apply_type(_components.TREE, DISPLAY, CJK, "600")})
3120
3231
  import _tokens
3121
3232
  import _dynamic
3122
3233
  import _conversion