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.
- package/SKILL.md +5 -3
- package/assets/templates/platforms/claude-ai.json +1 -1
- package/assets/templates/platforms/claude-code.json +1 -1
- package/assets/templates/platforms/codex-cli.json +1 -1
- package/assets/templates/platforms/copilot.json +1 -1
- package/assets/templates/platforms/gemini-cli.json +1 -1
- package/data/component-wall-verification.csv +12 -0
- package/data/mobile-audit.csv +24 -20
- package/data/navigation-verification.csv +16 -19
- package/data/placement-rules.csv +127 -127
- package/data/slider-verification.csv +15 -15
- package/package.json +1 -1
- package/references/design-system.md +68 -0
- package/scripts/check-release.mjs +3 -2
- package/sites/_moksa.py +114 -3
- package/sites/moksa.json +5654 -1033
- package/tools/audit_mobile.py +5 -0
- package/tools/build_page.py +8 -1
- package/tools/extract_placement.py +12 -1
- package/tools/verify_components.py +145 -0
|
@@ -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":
|
|
105
|
-
"data/
|
|
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"), ("
|
|
282
|
-
|
|
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
|