mosaic-headless 1.30.0 → 1.31.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/README.ja.md +4 -2
- package/README.ko.md +4 -2
- package/README.md +5 -3
- package/README.zh-TW.md +4 -2
- package/SKILL.md +83 -39
- 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/browser-verification/moksa.csv +3986 -0
- package/data/browser-verification/my-account.csv +148 -0
- package/data/component-wall-verification.csv +1 -1
- package/data/condition-comparators.csv +2 -2
- package/data/content-loop-verification.csv +4 -1
- package/data/data-class-hierarchy.csv +1 -0
- package/data/dialog-form-verification.csv +2 -2
- package/data/dialog-verification.csv +1 -1
- package/data/form-surface-verification.csv +3 -0
- package/data/interaction-types.csv +1 -0
- package/data/intro-verification.csv +16 -16
- package/data/mobile-audit.csv +22 -19
- package/data/navigation-verification.csv +16 -13
- package/data/node-properties.csv +10 -5
- package/data/node-types.csv +1 -0
- package/data/node-verification.csv +3 -0
- package/data/placement-rules.csv +1 -0
- package/data/pluggables.csv +16 -4
- package/data/popover-verification.csv +38 -0
- package/data/style-states.csv +1 -0
- package/data/token-verification.csv +2 -2
- package/package.json +3 -1
- package/references/dialog-and-triggers.md +199 -8
- package/references/dynamic-content.md +19 -0
- package/references/failure-modes.md +36 -4
- package/references/styling.md +9 -0
- package/references/upgrading.md +46 -1
- package/scripts/check-release.mjs +41 -11
- package/sites/_dialogs.py +18 -8
- package/sites/_moksa.py +69 -39
- package/sites/_popovers.py +570 -0
- package/tools/audit_mobile.py +1 -1
- package/tools/extract_default_children.py +1 -1
- package/tools/extract_node_types.py +1 -1
- package/tools/sweep_node_types.py +55 -6
- package/tools/verify_browser.py +20 -8
- package/tools/verify_conversion.py +6 -2
- package/tools/verify_dialog.py +3 -2
- package/tools/verify_dialog_lab.py +11 -11
- package/tools/verify_intro.py +5 -3
- package/tools/verify_popovers.py +348 -0
- package/data/browser-verification.csv +0 -4133
|
@@ -9,8 +9,8 @@ USED/--mk-accent,PASS,"5 element(s), e.g. mk-progress-track, mk-progress, mk-clo
|
|
|
9
9
|
USED/--mk-accent-ink,PASS,"2 element(s), e.g. mk-header-cta, tk-chip-mk-accent-ink"
|
|
10
10
|
USED/--mk-ai,PASS,"1 element(s), e.g. tk-chip-mk-ai"
|
|
11
11
|
USED/--mk-faint,PASS,"1 element(s), e.g. tk-chip-mk-faint"
|
|
12
|
-
USED/--mk-ink,PASS,"
|
|
12
|
+
USED/--mk-ink,PASS,"66 element(s), e.g. mk-clock-m, mk-clock-m, mk-clock-s"
|
|
13
13
|
USED/--mk-muted,PASS,"1 element(s), e.g. tk-chip-mk-muted"
|
|
14
14
|
USED/--mk-panel,PASS,"3 element(s), e.g. tk-sw, tk-chip-mk-panel, tk-props"
|
|
15
|
-
USED/--mk-paper,PASS,"
|
|
15
|
+
USED/--mk-paper,PASS,"9 element(s), e.g. mk-cap-g0-menu, mk-cap-g1-menu, tk-head"
|
|
16
16
|
USED/--mk-rule,PASS,"1 element(s), e.g. tk-chip-mk-rule"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mosaic-headless",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.31.0",
|
|
4
4
|
"description": "AI-agent skill: build Mosaic Pro (Nextend) WordPress sites by writing the underlying data model directly - 23 custom tables, no visual editor, no DOM. Every node type, style property and node property swept against a live install and asserted on the delivered HTML and compiled CSS. Installs into Claude Code, Cursor, Codex CLI, Gemini CLI, Copilot, Continue, Windsurf and Claude.ai.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"wordpress",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"scripts/",
|
|
34
34
|
"assets/",
|
|
35
35
|
"data/*.csv",
|
|
36
|
+
"data/browser-verification/*.csv",
|
|
36
37
|
"references/",
|
|
37
38
|
"sites/_moksa.py",
|
|
38
39
|
"sites/_components.py",
|
|
@@ -41,6 +42,7 @@
|
|
|
41
42
|
"sites/_dialogs.py",
|
|
42
43
|
"sites/_dynamic.py",
|
|
43
44
|
"sites/_forms.py",
|
|
45
|
+
"sites/_popovers.py",
|
|
44
46
|
"sites/_sliders.py",
|
|
45
47
|
"sites/_tokens.py",
|
|
46
48
|
"tools/*.py",
|
|
@@ -1,12 +1,64 @@
|
|
|
1
|
-
# Modal, OpenStreetMap, the
|
|
1
|
+
# Modal, popover, OpenStreetMap, the triggers and run rules (1.0.9, 1.0.10), measured
|
|
2
2
|
|
|
3
3
|
1.0.9 is the first release since this skill began that adds *features* rather than
|
|
4
4
|
renaming things: four node types, three interaction triggers, a rule engine that
|
|
5
5
|
decides whether a trigger is allowed to run, two memory actions, and a shorthand
|
|
6
6
|
that collapses "open this modal when …" into one object.
|
|
7
7
|
|
|
8
|
+
1.0.10 adds the other half: `popover`, a dialog surface that does not block the
|
|
9
|
+
page, and it moves the modal onto the same browser API. Both are measured below -
|
|
10
|
+
the 1.0.10 changes to the modal first, because they overturn three things this page
|
|
11
|
+
used to say, then the popover in full.
|
|
12
|
+
|
|
8
13
|
Everything below was built on the live site and read back out of the delivered page
|
|
9
|
-
or out of the browser. `data/dialog-verification.csv`, 19 checks,
|
|
14
|
+
or out of the browser. `data/dialog-verification.csv`, 19 checks, and
|
|
15
|
+
`data/popover-verification.csv`, 37 checks, all passing on 1.0.10.
|
|
16
|
+
|
|
17
|
+
## 1.0.10: the modal moved onto the Popover API
|
|
18
|
+
|
|
19
|
+
The host is still a `<dialog>`, but it now carries `popover="manual"` and the
|
|
20
|
+
controller opens it with `showPopover()`, so an open modal is in the browser's
|
|
21
|
+
**top layer**. Measured with the same probe page on both versions:
|
|
22
|
+
|
|
23
|
+
| | 1.0.9 | 1.0.10 |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| open modal in the top layer | no | **yes** |
|
|
26
|
+
| `dialog.open` while showing | `true` | **`false`**, always |
|
|
27
|
+
| visible state | `[open]` attribute | `:popover-open`, plus `data-mosaic-dialog-state="open"` |
|
|
28
|
+
| closed state | `dialog:not([open]){display:none}` (UA) | `.m-modal:not(:popover-open){display:none !important}` |
|
|
29
|
+
| controller | `el.mosaicDialog.close()` | **no `close()`** - `open()`, `requestClose()` (a Promise), `toggle()` |
|
|
30
|
+
|
|
31
|
+
What each row costs you:
|
|
32
|
+
|
|
33
|
+
- **Top layer means `z-index` no longer decides anything.** A 1.0.10 modal paints
|
|
34
|
+
above a fixed header, a sticky bar, anything - whatever z-index those carry.
|
|
35
|
+
- **Anything that tested `dialog.open` now reads every modal as closed.** Read
|
|
36
|
+
`el.matches(':popover-open')`. The dialog lab's verifier had exactly this bug on
|
|
37
|
+
the upgrade: it decided the page-load notice was shut, never dismissed it, and
|
|
38
|
+
18 of 33 checks failed behind its overlay. `tools/verify_dialog*.py` read either
|
|
39
|
+
signal now, so they hold on both versions.
|
|
40
|
+
- **To close one from script, `el.mosaicDialog.requestClose()`.** `close()` throws
|
|
41
|
+
on 1.0.10 ("is not a function"); `hidePopover()` bypasses the controller.
|
|
42
|
+
- **`display` on the host is safe now.** The closed state is `!important`, so an
|
|
43
|
+
author's `display:flex` can no longer pin a modal open - the trap the "five forms"
|
|
44
|
+
section below was built around. Measured: a modal whose host sets
|
|
45
|
+
`display:flex; align-items:center; justify-content:center` computes `display:none`
|
|
46
|
+
and 0x0 while closed, and `display:flex` with a centred window when open
|
|
47
|
+
(`FLEXMODAL/*` in `data/popover-verification.csv`). Positioning the window, as
|
|
48
|
+
the forms below do, still works on both versions.
|
|
49
|
+
- **The window keeps Mosaic's default card look** - `border-radius:8px` and a
|
|
50
|
+
three-layer shadow on `:where(.m-modal-window)`. Zero-specificity, so any author
|
|
51
|
+
value wins, but a square-edged site that restyles the border and forgets the
|
|
52
|
+
radius gets rounded corners on a full-screen takeover and an edge-docked sheet.
|
|
53
|
+
Set `border-radius:0` with the border.
|
|
54
|
+
|
|
55
|
+
**`scrollDepth` renamed its action slot.** It used to have one generated slot named
|
|
56
|
+
after the type, `scrollDepth`; 1.0.10 declares two, **`reached`** and **`returned`**
|
|
57
|
+
(scrolled back above the threshold). The data upgrade moves stored actions from
|
|
58
|
+
`scrollDepth` to `reached`, and `completedAfter: "scrollDepth"` with them. Write
|
|
59
|
+
`actionSlots.reached` - a key the type no longer declares is never exported, so an
|
|
60
|
+
action left under `scrollDepth` is stored and silently never runs. The
|
|
61
|
+
`interactionShorthand` form is unaffected: it fills the slot at render.
|
|
10
62
|
|
|
11
63
|
## Modal — a real `<dialog>`
|
|
12
64
|
|
|
@@ -47,12 +99,13 @@ delivers
|
|
|
47
99
|
Measured on a modal authored with no overlay: the delivered HTML contains
|
|
48
100
|
`<div class="m-modal-overlay" tabindex="-1">`, the host carries
|
|
49
101
|
`aria-modal="true"`, and a real wheel event over the page moves it 0px while the
|
|
50
|
-
same page scrolls 700px with nothing open. **There is no non-blocking modal
|
|
51
|
-
1.0.9
|
|
52
|
-
ordinary nodes
|
|
102
|
+
same page scrolls 700px with nothing open. **There is no non-blocking modal.**
|
|
103
|
+
On 1.0.9 a corner notice that must not trap the reader had to be built from
|
|
104
|
+
ordinary nodes; on 1.0.10 it is a `popover` (below).
|
|
53
105
|
- **`closedby` is `everything` | `clickOutside` | `esc` | `nothing`**, and it is
|
|
54
106
|
emitted as `data-mosaic-modal-closedby`, NOT as the native `closedby` attribute —
|
|
55
|
-
Mosaic opens the host
|
|
107
|
+
Mosaic opens the host itself (`show()` on 1.0.9, `showPopover()` on a
|
|
108
|
+
`popover="manual"` host on 1.0.10), where the native light-dismiss does not
|
|
56
109
|
apply, so its own controller reads the attribute. Measured: with `everything`,
|
|
57
110
|
Esc closes it. `nothing` is the cookie-gate case, where the only way out is a
|
|
58
111
|
`modalClose` action you place yourself.
|
|
@@ -281,6 +334,143 @@ anywhere. That is the skill's existing rule (`ValidatorDynamicCode` = bare strin
|
|
|
281
334
|
`ValidatorDynamicCodeObject` = `{"v": …}`) applying to a new element, and it is also
|
|
282
335
|
why those three can hold `@VAR('post/meta_lat')` and a custom field can drive the map.
|
|
283
336
|
|
|
337
|
+
## Popover (1.0.10)
|
|
338
|
+
|
|
339
|
+
A dialog surface that does **not** block the page: a menu, an info card, a tip, a
|
|
340
|
+
notice, a tour step. It appears next to whatever opened it, next to an element you
|
|
341
|
+
name, or in a fixed cell of the viewport, and the visitor can keep using everything
|
|
342
|
+
else while it is open. `data/popover-verification.csv` (37 checks, all passing) is
|
|
343
|
+
the popover lab, `/popovers/` on the demo site, built from `sites/_popovers.py`.
|
|
344
|
+
|
|
345
|
+
```json
|
|
346
|
+
{"type": "popover", "nodeID": "<uuid you mint>",
|
|
347
|
+
"data": {"accessibleLabel": "服務項目", "role": "menu", "closedby": "everything",
|
|
348
|
+
"positioning": {"type": "element",
|
|
349
|
+
"elementOptions": {"side": "block-end", "align": "start",
|
|
350
|
+
"keepInView": "1"}}},
|
|
351
|
+
"children": [ … anything … ]}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
delivers
|
|
355
|
+
|
|
356
|
+
```html
|
|
357
|
+
<div class="m-popover M_Dialog M_IJ _x" id="pp-menu" popover="manual" role="menu"
|
|
358
|
+
aria-label="服務項目" data-mosaic-popover-closedby="everything"
|
|
359
|
+
data-mosaic-popover-placement="block-end start"
|
|
360
|
+
data-mosaic-popover-keep-in-view="1"> … </div>
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
- **It has no required children.** Unlike `modal` there is no window and no
|
|
364
|
+
overlay - and that is the point: no overlay, nothing blocking, no `aria-modal`.
|
|
365
|
+
Measured with a page-load popover showing: the centre of the viewport hit-tests to
|
|
366
|
+
the page, a wheel event scrolls it 600px, and a button elsewhere on the page opens
|
|
367
|
+
a second popover while the first stays up. It may sit anywhere (`placement-rules`
|
|
368
|
+
says `any`), and dialogs may nest inside each other.
|
|
369
|
+
- **`closedby` defaults to `nothing`.** A popover expects an explicit close: a
|
|
370
|
+
`popoverClose` / `popoverToggle` action, usually a button inside it. Measured:
|
|
371
|
+
`nothing` ignores Esc; `clickOutside` ignores Esc and closes on an outside click;
|
|
372
|
+
`everything` closes on both.
|
|
373
|
+
- **`role` has no default** (dialog / menu / listbox / tooltip are the suggested
|
|
374
|
+
values); **`ariaLive`** is `off` | `polite` | `assertive` and is emitted only when
|
|
375
|
+
it announces; **`accessibleLabel`** always emits `aria-label`, as on the modal.
|
|
376
|
+
- **The default look is a card**: white, `border-radius:8px`, shadow, `padding:24px`,
|
|
377
|
+
`max-width:384px`, `display:flex; flex-direction:column; row-gap:8px` while open -
|
|
378
|
+
all on `:where()`, so any style you set wins. The closed state is
|
|
379
|
+
`display:none !important`, so `display` on a popover is safe too.
|
|
380
|
+
|
|
381
|
+
### Where it sits: `positioning`
|
|
382
|
+
|
|
383
|
+
One group, `type` plus that type's options. Both option groups always exist in the
|
|
384
|
+
schema; only the chosen one is read.
|
|
385
|
+
|
|
386
|
+
| `type` | options | what it does |
|
|
387
|
+
|---|---|---|
|
|
388
|
+
| `screen` (default) | `screenOptions.position`: `top-left` … `bottom-right`, nine cells; default `bottom-right` | pure CSS, right in the first painted frame, no JavaScript. Physical directions: a viewport corner stays that corner in RTL |
|
|
389
|
+
| `element` | `elementOptions.side`: `block-start` / `block-end` / `inline-start` / `inline-end` (default `block-end`); `align`: `start` / `center` / `end` (default `center`); `keepInView`: `"1"` / `"0"` (default `"0"`); `anchor`: a target (`{"type": "element", "uuid": "<node id>"}`, or a class / variant) plus optional `anchorModifiers` | positioned with native CSS anchor positioning. Logical directions, so RTL mirrors them. **No anchor means "whatever opened it"** |
|
|
390
|
+
|
|
391
|
+
Measured, in Chromium at 1280x900:
|
|
392
|
+
|
|
393
|
+
- an unanchored menu sits on its trigger's block-end edge, start-aligned, to the
|
|
394
|
+
pixel - and with `keepInView` it flips above when the 404px menu would not fit
|
|
395
|
+
below;
|
|
396
|
+
- an anchor set to another element's node id puts the popover on THAT element
|
|
397
|
+
(bottom edge on the card's top edge, centres within 1px), whatever opened it;
|
|
398
|
+
- all nine screen cells dock where their names say.
|
|
399
|
+
|
|
400
|
+
**`keepInView` is the flip, and `___popover--flipped` is how it looks flipped.**
|
|
401
|
+
With `keepInView: "1"` and no room on the requested side, the popover moves to the
|
|
402
|
+
opposite side and carries `data-mosaic-popover-flipped`; the style state
|
|
403
|
+
`___popover--flipped` (selector
|
|
404
|
+
`&:where(.m-popover[data-mosaic-popover-flipped],.m-popover[data-mosaic-popover-flipped] &)`)
|
|
405
|
+
applies to it and everything inside it. Two measured surprises:
|
|
406
|
+
|
|
407
|
+
- **`keepInView: "0"` does not overflow.** Asked for `inline-end` with no room on
|
|
408
|
+
the right, the popover did not run off-screen - the browser shifted it back inside
|
|
409
|
+
the viewport, **on top of its own trigger** (popover 1020-1280px, trigger
|
|
410
|
+
1080-1231px). If covering the trigger is wrong, keepInView is not optional.
|
|
411
|
+
- **On a phone, neither side may fit, and then nothing flips.** At 390px both
|
|
412
|
+
`inline-*` sides are too narrow for a 260px card: `flip` is armed, `flipped` never
|
|
413
|
+
set, and the card covers the trigger. Anchor to `block-start` / `block-end` for
|
|
414
|
+
anything that has to work at phone width.
|
|
415
|
+
|
|
416
|
+
**A screen popover is glued to the viewport edge.** Mosaic places it with `inset:0`
|
|
417
|
+
plus auto margins, zeroing the margin on the docked side, so `bottom-left` touches
|
|
418
|
+
the bottom-left corner exactly. `top/right/bottom/left: 12px` on the popover moves
|
|
419
|
+
every edge in at once and keeps the cell.
|
|
420
|
+
|
|
421
|
+
### Opening and closing: three actions, one trigger
|
|
422
|
+
|
|
423
|
+
Actions, aimed at the popover's **node id** like the modal's:
|
|
424
|
+
`popoverOpen`, `popoverClose`, `popoverToggle`. The popover's
|
|
425
|
+
`interactionShorthand` action is `popoverOpen`, so `{"type": "pageLoad", …}` on the
|
|
426
|
+
popover itself is a self-opening notice (with run rules, exactly as on a modal).
|
|
427
|
+
|
|
428
|
+
A new trigger, **`popover`** ("Popover visibility change", group Element events),
|
|
429
|
+
goes ON the popover and has three slots: **`show`**, **`beforeClose`** (the native
|
|
430
|
+
hide waits for it to finish), **`afterClose`** (the popover is already hidden):
|
|
431
|
+
|
|
432
|
+
```json
|
|
433
|
+
"interactions": [{"type": "popover", "uuid": "<uuid>",
|
|
434
|
+
"popoverOptions": {"name": "tour-1", "ID": "<uuid>",
|
|
435
|
+
"actionSlots": {"afterClose": {"actions": [
|
|
436
|
+
{"type": "popoverOpen", "uuid": "<uuid>",
|
|
437
|
+
"settings": {"target": {"type": "element",
|
|
438
|
+
"uuid": "<next step>"}}}]}}}}]
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
### Two runtime rules that decide how a sequence has to be built
|
|
442
|
+
|
|
443
|
+
Found by building a three-step tour whose "next" button did nothing:
|
|
444
|
+
|
|
445
|
+
1. **A dialog opened by a trigger inside another dialog becomes its child, and a
|
|
446
|
+
parent closing closes its children.** The controller takes the dialog the
|
|
447
|
+
trigger sits in as the parent. So a "next" button inside step 1 that runs
|
|
448
|
+
`popoverOpen step 2` + `popoverClose step 1` opens step 2 as step 1's child and
|
|
449
|
+
closes both: measured, nothing open 200ms later.
|
|
450
|
+
2. **Closing a popover suspends the interactions of every element inside it.** So
|
|
451
|
+
with the order reversed - close first, then open - the open never runs at all.
|
|
452
|
+
|
|
453
|
+
The sequence therefore has to be advanced by **`afterClose`**: it runs once the step
|
|
454
|
+
is already closed, so the step it opens has no open parent to be taken down with.
|
|
455
|
+
The "next" button only closes its own step. And because afterClose fires on EVERY
|
|
456
|
+
close, Esc included, a step that should be leavable must not take Esc
|
|
457
|
+
(`closedby: "nothing"`); leaving is a button that runs `interactionSuspend` on the
|
|
458
|
+
step first (its own afterClose then does not run) and then `popoverClose`. The
|
|
459
|
+
start button runs `interactionResume` on each step before opening the first, so a
|
|
460
|
+
tour ended halfway can be started again. Measured: start > next > next > done walks
|
|
461
|
+
step 1 > 2 > 3 > the closing card; "end" on step 2 leaves nothing open; a restart
|
|
462
|
+
advances normally; Esc on a step leaves it open.
|
|
463
|
+
|
|
464
|
+
`interactionSuspend` / `interactionResume` take a `target` and an optional
|
|
465
|
+
`settings.interactionID`; with none, every interaction on the target.
|
|
466
|
+
|
|
467
|
+
### The modal, flex-centred
|
|
468
|
+
|
|
469
|
+
The lab's last demo is the 1.0.10 modal change made visible: a modal whose host is
|
|
470
|
+
`display:flex` + centred, which on 1.0.9 would have pinned it open. It hides when
|
|
471
|
+
closed and centres its window when open, and it sits in the top layer above the
|
|
472
|
+
fixed header.
|
|
473
|
+
|
|
284
474
|
## The rest of 1.0.9, briefly
|
|
285
475
|
|
|
286
476
|
Not everything in the release is a node you place. Four things that change how the
|
|
@@ -387,7 +577,7 @@ own `interactionShorthand`, a page's popup policy is one object in that page's d
|
|
|
387
577
|
Nothing else on the page changes, no trigger element is placed, and two pages of the
|
|
388
578
|
same site can behave completely differently without sharing anything.
|
|
389
579
|
|
|
390
|
-
## Five forms out of three nodes — and the one property you must not set
|
|
580
|
+
## Five forms out of three nodes — and, on 1.0.9, the one property you must not set
|
|
391
581
|
|
|
392
582
|
"A modal" in most builders means one centred card, because the builder owns the
|
|
393
583
|
chrome. Mosaic owns none of it: `.m-modal` is a `position:fixed; inset:0` layer
|
|
@@ -395,7 +585,8 @@ with `pointer-events:none`, `.m-modal-overlay` is a second fixed layer that take
|
|
|
395
585
|
pointer events back, and `.m-modal-window` is an ordinary child of the first. The
|
|
396
586
|
form is decided by where the window is pinned inside that layer.
|
|
397
587
|
|
|
398
|
-
**
|
|
588
|
+
**On 1.0.9, never set `display` on the `modal` host** (1.0.10 made it safe - see
|
|
589
|
+
the top of this page). A `<dialog>`'s open/closed visibility
|
|
399
590
|
*is* its display property — the UA stylesheet hides it with
|
|
400
591
|
`dialog:not([open]) { display: none }` — so an author-level `display:flex` on the
|
|
401
592
|
host wins the cascade and every modal on the page is permanently on screen. It is
|
|
@@ -165,3 +165,22 @@ half and the output ends in U+FFFD. Lengths have to be a multiple of the encodin
|
|
|
165
165
|
width — 18 for six three-byte characters — or the truncation has to be left to CSS.
|
|
166
166
|
A count-based check cannot see this; `verify_components.py` looks for the
|
|
167
167
|
replacement character directly.
|
|
168
|
+
|
|
169
|
+
## Date conditions (1.0.10)
|
|
170
|
+
|
|
171
|
+
The `date` comparator - element and form-action server conditions, and the post /
|
|
172
|
+
user loop filters - has four operators now: `after`, `after-or-equal`, `before`,
|
|
173
|
+
`before-or-equal` (1.0.9 had the first and third). The value is any text, read by
|
|
174
|
+
`DateComparison` when it compares rather than validated when it is saved:
|
|
175
|
+
|
|
176
|
+
- a year first: `2026-09-24`, `2026/9/24`, `2026-09`, `2026`;
|
|
177
|
+
- a year last: `28/09/2026`, `9-28-2026` - a number above 12 is the day; when both
|
|
178
|
+
could be, the **site's date format** decides which comes first;
|
|
179
|
+
- `Ymd` (ACF's date picker storage) and `YmdHis`; any shorter digit run is a Unix
|
|
180
|
+
timestamp in the site timezone;
|
|
181
|
+
- a time after the date or on its own (`09:16`, `9:16 pm`), with an optional zone.
|
|
182
|
+
|
|
183
|
+
The less precise side sets the precision: a date against a date-time compares the
|
|
184
|
+
two days. Whatever cannot be read as a date matches **no** operator, so a typo is a
|
|
185
|
+
condition that is never true rather than an error. Loop filters compare in SQL
|
|
186
|
+
(`post_date_gmt` / `post_modified_gmt` as UTC, the others as the site's wall clock).
|
|
@@ -319,7 +319,12 @@ The general shape, worth carrying beyond this repo: a derived artifact checked i
|
|
|
319
319
|
the tree is only as fresh as the last time someone re-derived it, and if the deriving
|
|
320
320
|
input is not in the tree, no amount of CI will notice.
|
|
321
321
|
|
|
322
|
-
## Setting `display` on a `<dialog>` pins it open, and open-state checks cannot see it
|
|
322
|
+
## Setting `display` on a `<dialog>` pins it open, and open-state checks cannot see it (1.0.9)
|
|
323
|
+
|
|
324
|
+
**Fixed in 1.0.10**: the closed modal is `.m-modal:not(:popover-open){display:none !important}`,
|
|
325
|
+
which no author `display` beats - measured, a flex-centred modal host hides when closed
|
|
326
|
+
(`references/dialog-and-triggers.md`). Everything below is the 1.0.9 behaviour, and still
|
|
327
|
+
the reason the checks it produced exist.
|
|
323
328
|
|
|
324
329
|
The modal host is a `<dialog>`. Its open/closed visibility is not a class or an
|
|
325
330
|
attribute Mosaic manages — it is the UA stylesheet rule
|
|
@@ -340,7 +345,8 @@ Two rules come out of it:
|
|
|
340
345
|
edges inside the fixed, `inset:0` host), never by making the host a flex or grid
|
|
341
346
|
container.
|
|
342
347
|
- **Assert the closed state.** `tools/verify_dialog_lab.py` now closes everything
|
|
343
|
-
first and fails if any `
|
|
348
|
+
first and fails if any closed modal (not `:popover-open` and not `[open]`) still
|
|
349
|
+
has a box. The general form of
|
|
344
350
|
that lesson: for anything with a visible and an invisible state, a suite that
|
|
345
351
|
only exercises the visible one is testing half a component, and the half it
|
|
346
352
|
skips is the one the visitor sees most of the time.
|
|
@@ -402,8 +408,9 @@ labels and that an empty required form is actually refused.
|
|
|
402
408
|
|
|
403
409
|
## `display` on an element whose visibility the plugin owns — twice now
|
|
404
410
|
|
|
405
|
-
|
|
406
|
-
every modal open.
|
|
411
|
+
On 1.0.9 a `<dialog>`'s open state was its `display`, so `display:flex` on a modal host
|
|
412
|
+
pinned every modal open (1.0.10 made the closed state `!important`). The same mistake,
|
|
413
|
+
in a family 1.0.10 did not touch: `multi-steps-form-step`
|
|
407
414
|
is shown and hidden by the plugin moving `m-form-step--active`, and a `display:flex`
|
|
408
415
|
on the step wins the cascade, so all three steps sit on the page at once with three
|
|
409
416
|
sets of Next buttons.
|
|
@@ -444,3 +451,28 @@ On a theme that cannot use the variant layer, a `code` node with
|
|
|
444
451
|
verbatim**: it does not wrap anything, so the `<style>` tags are yours to write.
|
|
445
452
|
Without them the rule lands in `<head>` as bare text between `</style>` and
|
|
446
453
|
`</head>`, renders nothing, and reads as a CSS specificity problem.
|
|
454
|
+
|
|
455
|
+
## A "next" button inside a popover cannot open the next popover
|
|
456
|
+
|
|
457
|
+
Measured on 1.0.10, building a three-step tour: the button in step 1 ran
|
|
458
|
+
`popoverOpen` (step 2) and `popoverClose` (step 1), and nothing was open 200ms later.
|
|
459
|
+
Two runtime rules combine:
|
|
460
|
+
|
|
461
|
+
- a dialog opened by a trigger that sits inside another dialog becomes that dialog's
|
|
462
|
+
CHILD, and closing a parent closes its children - step 2 went down with step 1;
|
|
463
|
+
- closing a popover suspends the interactions of every element inside it - with the
|
|
464
|
+
actions reversed, the `popoverOpen` after the close never ran.
|
|
465
|
+
|
|
466
|
+
Advance a sequence on the step's own `afterClose` (it runs after the close, with no
|
|
467
|
+
open parent left), make the button close only its own step, and remember afterClose
|
|
468
|
+
also fires on Esc. The full pattern is in `references/dialog-and-triggers.md`.
|
|
469
|
+
|
|
470
|
+
## A plugin folder swapped in by hand serves every asset as 406
|
|
471
|
+
|
|
472
|
+
Installing 1.0.10 by unzipping it next to the old folder and renaming: the zip's
|
|
473
|
+
directories came out `drwx------`, the web server could not traverse them, and every
|
|
474
|
+
`public/Frontend/*.js` and the theme `style.css` answered **406**. The page still
|
|
475
|
+
rendered - server-side output was fine - but with no front-end runtime every dropdown
|
|
476
|
+
sat open and no modal could open. `find mosaic -type d -exec chmod 775 {} +` (files
|
|
477
|
+
664), matching the other plugin folders, and purge the page cache. A REST-level check
|
|
478
|
+
cannot see this; only fetching an asset can.
|
package/references/styling.md
CHANGED
|
@@ -227,3 +227,12 @@ Both of these are refused at build time by `build_page.py` rather than silently
|
|
|
227
227
|
dropped, which is the only reason they cost minutes instead of a debugging session
|
|
228
228
|
— a property that is not in the registry cannot be set at all, and Mosaic's own
|
|
229
229
|
API accepts the write and discards it.
|
|
230
|
+
|
|
231
|
+
## Gradient stops take units (1.0.10)
|
|
232
|
+
|
|
233
|
+
A gradient's `dots[].position` - background and mask alike - used to be a bare float
|
|
234
|
+
0-100. 1.0.10 validates it as a length-percentage: `"50%"`, `"120px"`, a `calc()`, or a
|
|
235
|
+
collection variable, with `%` as the fallback unit and 0-100 enforced only on
|
|
236
|
+
percentages. A bare number still validates (it is read as a percentage), but the
|
|
237
|
+
values Mosaic itself now writes are strings with a unit - the built-in background
|
|
238
|
+
collection's stops changed from `10` / `50` to `"10%"` / `"50%"`.
|
package/references/upgrading.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Upgrading the plugin: what 1.0.7 → 1.0.8 did to the data, measured
|
|
1
|
+
# Upgrading the plugin: what 1.0.7 → 1.0.8 → 1.0.10 did to the data, measured
|
|
2
2
|
|
|
3
3
|
A Mosaic plugin update is two events, not one. The files change when WordPress
|
|
4
4
|
installs the package; the DATA changes only when Mosaic's own upgrade flow has
|
|
@@ -180,3 +180,48 @@ The order that worked, and why each step is there:
|
|
|
180
180
|
five-minute upstream limit during `createTemplates`; at 25 masters and 8,608
|
|
181
181
|
nodes it finished in under two minutes and `theme_zip_compare.php` passed
|
|
182
182
|
22 of 22, the four renamed tables included.
|
|
183
|
+
|
|
184
|
+
## 1.0.9 → 1.0.10
|
|
185
|
+
|
|
186
|
+
**Getting the package.** The update shows in wp-admin, but its package is
|
|
187
|
+
`private-mosaic-pro://releases/stable/Mosaic-pro-plugin-stable-1.0.10.zip`, which
|
|
188
|
+
`wp plugin update mosaic` could not fetch on an unlicensed install ("No plugins
|
|
189
|
+
updated (1 failed)", and a second run claiming "already updated" while the version
|
|
190
|
+
still read 1.0.9). Two downloads labelled as the new release were 1.0.9 byte for
|
|
191
|
+
byte - one only the inner `Mosaic/` namespace folder. **Check `Version:` in
|
|
192
|
+
`mosaic.php` before installing anything**, and diff the tree against the old one: a
|
|
193
|
+
real 1.0.10 has `Mosaic/NodeTypes/Popover/`. The real package unpacked to a folder
|
|
194
|
+
named `mosaic-next/`; it is installed by renaming it to `mosaic` in place of the old
|
|
195
|
+
folder, then **chmod** (directories 775, files 664 - the zip's `drwx------` served
|
|
196
|
+
every asset as 406), then `tools/data_upgrade.py`, whose six milestones took about
|
|
197
|
+
seven minutes on this theme.
|
|
198
|
+
|
|
199
|
+
**What the migration rewrote.** One thing: scroll-depth interactions. The type
|
|
200
|
+
used to have a single generated action slot named after itself, `scrollDepth`; it
|
|
201
|
+
now declares `reached` and `returned`, and the migration moves stored actions (and
|
|
202
|
+
a `completedAfter: "scrollDepth"`) to `reached` across `mosaic_nodes` and the four
|
|
203
|
+
class tables. No table changed shape (`Schema-1.0.10.php` lists the same tables).
|
|
204
|
+
|
|
205
|
+
**What changed in the delivered page.** The modal host gained `popover="manual"`
|
|
206
|
+
and opens into the top layer; `dialog.open` is always false and the controller has
|
|
207
|
+
`requestClose()` instead of `close()`. Every tool that decided "is it open?" from
|
|
208
|
+
`dialog.open` read every modal as closed afterwards - the dialog lab reported 18 of
|
|
209
|
+
33 failures that were all the verifier's. `references/dialog-and-triggers.md` has
|
|
210
|
+
the measured table.
|
|
211
|
+
|
|
212
|
+
**Re-extracted from the 1.0.10 source and diffed against 1.0.9:** one node type
|
|
213
|
+
(`popover`, 129), five node properties (196), one data class (126), one placement
|
|
214
|
+
rule (129), one style state (`___popover--flipped`, 54), one interaction trigger
|
|
215
|
+
(`popover`, 16), twelve pluggable IDs (261 - the three popover actions and their
|
|
216
|
+
groups, the trigger and its slots, scroll depth's two new slots), the date
|
|
217
|
+
comparator's operators (`after-or-equal`, `before-or-equal`) and gradient stop
|
|
218
|
+
positions accepting units (`references/styling.md`). The 115 editor routes are the
|
|
219
|
+
same 115. Every demo verifier was re-run on the result and passes; the full node
|
|
220
|
+
sweep was run for the types 1.0.10 added or that had never been swept
|
|
221
|
+
(`popover`, `fieldset-legend`, `fieldset-content`, all RENDERED), the other rows
|
|
222
|
+
are the 1.0.9 sweep.
|
|
223
|
+
|
|
224
|
+
**The sweep had stopped measuring anything, and nothing said so.** It read `/`,
|
|
225
|
+
which stopped being the post archive when the site got a static front page; every
|
|
226
|
+
type, a plain `div` included, came back COMMITTED. It now reads `sweepPath`
|
|
227
|
+
(default `?post_type=post`) and refuses to run unless a plain div renders there.
|
|
@@ -26,9 +26,37 @@ import { fileURLToPath } from "node:url";
|
|
|
26
26
|
|
|
27
27
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
28
28
|
const read = (p) => fs.readFileSync(path.join(ROOT, p), "utf8");
|
|
29
|
+
// A table split one file per page (data/browser-verification/) is still one table:
|
|
30
|
+
// its CSVs, in name order, each with its own header.
|
|
31
|
+
const tableFiles = (p) => fs.statSync(path.join(ROOT, p)).isDirectory()
|
|
32
|
+
? fs.readdirSync(path.join(ROOT, p)).filter((f) => f.endsWith(".csv")).sort()
|
|
33
|
+
.map((f) => `${p}/${f}`)
|
|
34
|
+
: [p];
|
|
29
35
|
const problems = [];
|
|
30
36
|
const fail = (m) => problems.push(m);
|
|
31
37
|
|
|
38
|
+
// ---------- the plugin directory's limits -----------------------------------
|
|
39
|
+
//
|
|
40
|
+
// The Claude plugin directory reads the repository root, every tracked file of it,
|
|
41
|
+
// and holds a submission for a reviewer when any file that is not an image or font
|
|
42
|
+
// is over 256 KiB, or when there are more than 512 files. A 400 KB CSV and a 2 MB
|
|
43
|
+
// generated site file did exactly that. Checked here, so it fails on this machine
|
|
44
|
+
// rather than in a review queue a week later.
|
|
45
|
+
try {
|
|
46
|
+
const tracked = execSync("git ls-files -z", { cwd: ROOT }).toString().split("\0").filter(Boolean);
|
|
47
|
+
if (tracked.length > 512) fail(`${tracked.length} tracked files - the plugin directory allows 512`);
|
|
48
|
+
for (const f of tracked) {
|
|
49
|
+
if (/\.(png|jpe?g|gif|webp|svg|ico|woff2?|ttf|otf)$/i.test(f)) continue;
|
|
50
|
+
const full = path.join(ROOT, f);
|
|
51
|
+
if (!fs.existsSync(full)) continue;
|
|
52
|
+
const size = fs.statSync(full).size;
|
|
53
|
+
if (size > 256 * 1024)
|
|
54
|
+
fail(`${f} is ${(size / 1024).toFixed(0)} KiB - the plugin directory holds any non-image file over 256 KiB`);
|
|
55
|
+
}
|
|
56
|
+
} catch {
|
|
57
|
+
// not a git checkout (an unpacked tarball): nothing to measure against
|
|
58
|
+
}
|
|
59
|
+
|
|
32
60
|
// ---------- versions agree --------------------------------------------------
|
|
33
61
|
|
|
34
62
|
const pkg = JSON.parse(read("package.json"));
|
|
@@ -91,28 +119,30 @@ if (!fs.existsSync(path.join(ROOT, pkg.bin["mosaic-headless"])))
|
|
|
91
119
|
|
|
92
120
|
// ---------- the numbers in the docs are counted, not trusted ----------------
|
|
93
121
|
|
|
94
|
-
const rows = (p) =>
|
|
122
|
+
const rows = (p) => tableFiles(p)
|
|
123
|
+
.reduce((n, f) => n + read(f).trim().split("\n").length - 1, 0); // minus each header
|
|
95
124
|
const counts = {
|
|
96
|
-
"data/node-verification.csv":
|
|
125
|
+
"data/node-verification.csv": 129,
|
|
97
126
|
"data/style-verification.csv": 98,
|
|
98
127
|
"data/node-property-verification.csv": 191,
|
|
99
128
|
"data/rwd-verification.csv": 733,
|
|
100
|
-
"data/browser-verification
|
|
129
|
+
"data/browser-verification": 4132,
|
|
101
130
|
"data/style-state-verification.csv": 52,
|
|
102
131
|
"data/interaction-verification.csv": 7,
|
|
103
|
-
"data/data-class-hierarchy.csv":
|
|
132
|
+
"data/data-class-hierarchy.csv": 126,
|
|
104
133
|
"data/custom-fields-verification.csv": 62,
|
|
105
134
|
"data/dialog-verification.csv": 19,
|
|
106
135
|
"data/slider-verification.csv": 30,
|
|
107
136
|
"data/token-verification.csv": 15,
|
|
108
|
-
"data/mobile-audit.csv":
|
|
137
|
+
"data/mobile-audit.csv": 68,
|
|
109
138
|
"data/component-wall-verification.csv": 26,
|
|
110
|
-
"data/form-surface-verification.csv":
|
|
111
|
-
"data/content-loop-verification.csv":
|
|
112
|
-
"data/navigation-verification.csv":
|
|
139
|
+
"data/form-surface-verification.csv": 18,
|
|
140
|
+
"data/content-loop-verification.csv": 15,
|
|
141
|
+
"data/navigation-verification.csv": 39,
|
|
113
142
|
"data/page-modal-verification.csv": 19,
|
|
114
143
|
"data/interaction-rules-verification.csv": 17,
|
|
115
144
|
"data/dialog-form-verification.csv": 49,
|
|
145
|
+
"data/popover-verification.csv": 37,
|
|
116
146
|
"data/value-coercion-verification.csv": 12,
|
|
117
147
|
};
|
|
118
148
|
for (const [file, expected] of Object.entries(counts)) {
|
|
@@ -131,7 +161,7 @@ if (!style.includes("SKIPPED")) fail("style-verification.csv has no SKIPPED rows
|
|
|
131
161
|
|
|
132
162
|
// The browser pass has the same obligation, under a different word: a declaration it
|
|
133
163
|
// cannot soundly compare must say so rather than be counted as agreement.
|
|
134
|
-
const browser =
|
|
164
|
+
const browser = tableFiles("data/browser-verification").map(read).join("\n");
|
|
135
165
|
if (!browser.includes("not-comparable"))
|
|
136
166
|
fail("browser-verification.csv has no not-comparable rows - a computed-value check "
|
|
137
167
|
+ "that claims to compare everything is comparing things it cannot");
|
|
@@ -190,13 +220,13 @@ if (/,FAIL,/.test(zip)) fail("data/theme-zip-verification.csv carries a failed c
|
|
|
190
220
|
// rwd and browser counts, and must not carry the previous ones.
|
|
191
221
|
{
|
|
192
222
|
const fmt = (n) => n.toLocaleString("en-US");
|
|
193
|
-
const want = [rows("data/rwd-verification.csv"), rows("data/browser-verification
|
|
223
|
+
const want = [rows("data/rwd-verification.csv"), rows("data/browser-verification")];
|
|
194
224
|
for (const readme of ["README.md", "README.zh-TW.md", "README.ja.md", "README.ko.md"]) {
|
|
195
225
|
const text = read(readme);
|
|
196
226
|
for (const n of want)
|
|
197
227
|
if (!text.includes(fmt(n)) && !text.includes(String(n)))
|
|
198
228
|
fail(`${readme} does not quote ${fmt(n)} - it is quoting a stale count`);
|
|
199
|
-
if (
|
|
229
|
+
if (/\b0 findings\b|指摘 0 件|지적 0건|0 項發現/.test(text))
|
|
200
230
|
fail(`${readme} still claims a design audit with 0 findings`);
|
|
201
231
|
}
|
|
202
232
|
}
|
package/sites/_dialogs.py
CHANGED
|
@@ -139,6 +139,8 @@ def make_modal(key, num, title, lead, body, specs, host, window,
|
|
|
139
139
|
"paddingLeft": "30px", "paddingRight": "30px",
|
|
140
140
|
"customDeclarations": "border:1px solid rgb(22,24,28);"}
|
|
141
141
|
win_style.update(window)
|
|
142
|
+
win_style["customDeclarations"] = (win_style.get("customDeclarations", "")
|
|
143
|
+
+ "border-radius:0;")
|
|
142
144
|
|
|
143
145
|
children = []
|
|
144
146
|
if overlay:
|
|
@@ -210,9 +212,10 @@ def M(key, num, title, lead, body, specs, host, window, **kw):
|
|
|
210
212
|
CENTRE_MODAL = M(
|
|
211
213
|
"centre", 1, "置中對話框",
|
|
212
214
|
"最直接的一種,也是其他八種的基準。",
|
|
213
|
-
"Mosaic 的 modal 是真正的 <dialog>
|
|
214
|
-
"
|
|
215
|
-
"
|
|
215
|
+
"Mosaic 的 modal 是真正的 <dialog>,1.0.10 起帶 popover=\"manual\","
|
|
216
|
+
"由外掛自己的控制器用 showPopover() 開啟,進入瀏覽器的 top layer——"
|
|
217
|
+
"所以它一定蓋在固定 header 之上,z-index 管不到它。manual 代表瀏覽器不會自己關它,"
|
|
218
|
+
"關閉方式寫在 data-mosaic-modal-closedby 屬性上,由控制器讀取。"
|
|
216
219
|
"另外 aria-modal=\"true\" 不是你寫的——它在 modal 帶有 overlay 子節點時才出現,"
|
|
217
220
|
"因為真正擋住頁面的是 overlay。",
|
|
218
221
|
[("觸發", "click → modalOpen"), ("執行規則", "無"), ("記憶", "無"),
|
|
@@ -225,8 +228,8 @@ SHEET_MODAL = M(
|
|
|
225
228
|
"modal 這層是 position:fixed; inset:0 的全視窗圖層,pointer-events:none;"
|
|
226
229
|
"modal-window 只是它的一個 flex 子元素。所以「抽屜」不是另一種元件,"
|
|
227
230
|
"只是把 window 絕對定位到 left:0; right:0; bottom:0——沒有新的節點型別,也沒有自訂 CSS。"
|
|
228
|
-
"
|
|
229
|
-
"
|
|
231
|
+
"1.0.9 時 host 上不能寫 display——那會讓彈窗永遠關不起來;"
|
|
232
|
+
"1.0.10 把關閉狀態改成 display:none !important,這條限制消失了。"
|
|
230
233
|
"手機上這是比置中卡片更合手的形態,拇指搆得到。",
|
|
231
234
|
[("觸發", "click → modalOpen"), ("執行規則", "無"), ("記憶", "無"),
|
|
232
235
|
("關閉", "clickOutside(只有點遮罩)"), ("排版", "window: left/right/bottom = 0")],
|
|
@@ -261,7 +264,8 @@ TOAST_MODAL = M(
|
|
|
261
264
|
"所以 aria-modal=\"true\" 實際上是拿不掉的,"
|
|
262
265
|
"實測也確認:這個彈窗開著時,滑鼠滾輪同樣捲不動頁面。"
|
|
263
266
|
"角落通知是一種排版,不是一種「不阻擋」的 modal——"
|
|
264
|
-
"1.0.
|
|
267
|
+
"modal 沒有不阻擋的版本。1.0.10 起,真正不擋人的通知用 Popover 做——"
|
|
268
|
+
"浮動層頁的第五個示範就是同一張通知,換成 popover 之後頁面照樣能用。",
|
|
265
269
|
[("觸發", "pageLoad(interactionShorthand)"), ("執行規則", "cap max 1"),
|
|
266
270
|
("記憶", "session(sessionStorage,每個分頁各自計算)"),
|
|
267
271
|
("關閉", "everything"),
|
|
@@ -272,6 +276,8 @@ TOAST_MODAL = M(
|
|
|
272
276
|
"paddingLeft": "20px", "paddingRight": "20px",
|
|
273
277
|
"customDeclarations": "border:1px solid rgb(22,24,28);"
|
|
274
278
|
"box-shadow:0 18px 44px rgba(22,24,28,.18);"},
|
|
279
|
+
window_m={"top": "72px", "left": "0px", "right": "0px", "width": "auto",
|
|
280
|
+
"maxWidth": "none", "maxHeight": "calc(100% - 72px)"},
|
|
275
281
|
shorthand={"type": "pageLoad",
|
|
276
282
|
"pageLoadOptions": {"runs": {"rules": [
|
|
277
283
|
{"uuid": U(), "type": "cap",
|
|
@@ -350,6 +356,8 @@ DEPTH_MODAL = M(
|
|
|
350
356
|
("關閉", "clickOutside"), ("紀錄", "mos:v1:r:dl-depth")],
|
|
351
357
|
{},
|
|
352
358
|
{"left": "24px", "bottom": "24px", "maxWidth": "380px", "width": "100%"},
|
|
359
|
+
window_m={"left": "0px", "right": "0px", "bottom": "0px", "width": "auto",
|
|
360
|
+
"maxWidth": "none"},
|
|
353
361
|
closedby="clickOutside",
|
|
354
362
|
shorthand={"type": "scrollDepth",
|
|
355
363
|
"scrollDepthOptions": {
|
|
@@ -559,8 +567,10 @@ NOTE = section("dl-note", [wrap("dl-note-w", [
|
|
|
559
567
|
"Mosaic 沒有那層外殼——modal、modal-overlay、modal-window "
|
|
560
568
|
"是三個可以自由排版的節點,所以上面九種形態全部來自同一組資料,"
|
|
561
569
|
"差別只在 window 被釘在哪裡。順帶一個踩過的坑:"
|
|
562
|
-
"host 不能設 display——<dialog> 是靠 display 決定開關的,"
|
|
563
|
-
"寫了 display:flex 就等於把每一個彈窗永遠打開。"
|
|
570
|
+
"在 1.0.9,host 不能設 display——<dialog> 是靠 display 決定開關的,"
|
|
571
|
+
"寫了 display:flex 就等於把每一個彈窗永遠打開。"
|
|
572
|
+
"1.0.10 把關閉狀態改成 display:none !important,這個坑填平了;"
|
|
573
|
+
"這裡的九種形態仍然用定位排版,好讓同一份資料在兩個版本都對。",
|
|
564
574
|
fontFamily=CJK, fontSize="15px", lineHeight="1.95",
|
|
565
575
|
color={"token": "--mk-ink"}, marginTop="16px", maxWidth="680px"),
|
|
566
576
|
T("p",
|