@adobe/helix-md2jcr 1.2.21 → 1.4.1

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/CHANGELOG.md CHANGED
@@ -1,3 +1,24 @@
1
+ ## [1.4.1](https://github.com/adobe/helix-md2jcr/compare/v1.4.0...v1.4.1) (2026-06-09)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * republish after npm provenance failures ([c078b3f](https://github.com/adobe/helix-md2jcr/commit/c078b3f68de461213347339cc6afd3de1c3cfcd7))
7
+
8
+ # [1.4.0](https://github.com/adobe/helix-md2jcr/compare/v1.3.0...v1.4.0) (2026-06-09)
9
+
10
+
11
+ ### Features
12
+
13
+ * **section-metadata:** element grouping for section style + classes in modelFields ([#216](https://github.com/adobe/helix-md2jcr/issues/216)) ([861bf48](https://github.com/adobe/helix-md2jcr/commit/861bf481d623e74045a81de2a80bbc0af6a31db1)), closes [#215](https://github.com/adobe/helix-md2jcr/issues/215)
14
+
15
+ # [1.3.0](https://github.com/adobe/helix-md2jcr/compare/v1.2.21...v1.3.0) (2026-06-08)
16
+
17
+
18
+ ### Features
19
+
20
+ * **block-options:** support element grouping for block options ([#215](https://github.com/adobe/helix-md2jcr/issues/215)) ([7388c2d](https://github.com/adobe/helix-md2jcr/commit/7388c2dcf95252393c47fc8097235f4612916342)), closes [#214](https://github.com/adobe/helix-md2jcr/issues/214)
21
+
1
22
  ## [1.2.21](https://github.com/adobe/helix-md2jcr/compare/v1.2.20...v1.2.21) (2026-06-08)
2
23
 
3
24
 
package/README.md CHANGED
@@ -106,6 +106,12 @@ console.log(xml);
106
106
  rows, and error cases). See also the
107
107
  [container-block test fixtures](test/fixtures/blocks/core/container-block/README.md)
108
108
  for a worked scenario-by-scenario breakdown.
109
+ - [Block Options (Classes)](docs/block-options.md) — block option classes and
110
+ element grouping (`classes`, `classes_*`): how header options are routed to
111
+ fields by their options or boolean name-suffix.
112
+ - [Section Styles](docs/section-styles.md) — element grouping for a section's
113
+ `style` group (`style`, `style_*`): how the single Section Metadata `style`
114
+ cell is routed back to fields, mirroring block options.
109
115
  - [Field Hinting](docs/field-hinting.md) — using `<!-- field: name -->` to skip
110
116
  ahead within a field group.
111
117
 
@@ -0,0 +1,116 @@
1
+ # Block Options (Classes)
2
+
3
+ Block options let an author pick variants of a block without creating a whole new
4
+ block. They are expressed as CSS classes on the block and appear in the block
5
+ header parentheses in markdown:
6
+
7
+ ```markdown
8
+ +--------------------------------------+
9
+ | Teaser (variant-a, light, fullwidth) |
10
+ +======================================+
11
+ | My title |
12
+ +--------------------------------------+
13
+ ```
14
+
15
+ A model exposes block options through its `classes` fields. In the simplest case
16
+ there is a single `classes` field that holds a free-form list of class names.
17
+
18
+ ## Element Grouping for Block Options
19
+
20
+ When there are several options — especially mutually exclusive ones — it is
21
+ easier for authors to use multiple fields. Following the
22
+ [element grouping](https://www.aem.live/developer/component-model-definitions#element-grouping-for-block-options)
23
+ naming convention, the **classes group** is the base `classes` field plus any
24
+ `classes_<name>` field (the group name `classes` separated from the option name
25
+ by an underscore):
26
+
27
+ ```jsonc
28
+ { "component": "select", "name": "classes", "options": [ "variant-a", "variant-b" ] }
29
+ { "component": "multiselect", "name": "classes_background", "options": [ "light", "dark" ] }
30
+ { "component": "boolean", "name": "classes_fullwidth" }
31
+ ```
32
+
33
+ When AEM renders the block, every field in the group collapses into one flat
34
+ class list — `class="teaser variant-a light fullwidth"` — and that is what lands
35
+ in the block header parentheses.
36
+
37
+ ## How md2jcr maps header options back to fields
38
+
39
+ Going from markdown to JCR, md2jcr must route each option in the header back to
40
+ the field it came from. Because the rendered class list is **flat** (it does not
41
+ record which field produced each class), routing is driven by the field
42
+ definition — **not by the order of the options**:
43
+
44
+ - **Boolean field** — matched by its **name suffix** (the part after `classes_`).
45
+ The token `fullwidth` sets `classes_fullwidth="true"`. If the token is absent,
46
+ the field is left unset. This suits on/off toggles.
47
+ - **Select / multiselect field** — matched by its **declared option values**. The
48
+ token `light` is routed to `classes_background` because `light` is one of that
49
+ field's options. A multiselect claims **all** of its matching tokens.
50
+ - **Base `classes` field** — receives any leftover tokens that no more specific
51
+ field claimed. This also covers the common case of a single, free-form
52
+ `classes` field.
53
+
54
+ Multi fields are written as `[a, b]`; single fields as a comma-separated value.
55
+
56
+ ### Example
57
+
58
+ Given the model above and the header `Teaser (variant-a, light, fullwidth)`, the
59
+ block node gets these properties:
60
+
61
+ ```text
62
+ classes="variant-a" classes_background="light" classes_fullwidth="true"
63
+ ```
64
+
65
+ A multiselect background claims every matching token:
66
+
67
+ ```text
68
+ Teaser (variant-a, light, dark)
69
+ -> classes="variant-a" classes_background="[light, dark]"
70
+ ```
71
+
72
+ And an unmatched token falls back to the base `classes` field:
73
+
74
+ ```text
75
+ Promo (highlight, featured, sticky) // classes is free-form, classes_sticky is boolean
76
+ -> classes="[highlight, featured]" classes_sticky="true"
77
+ ```
78
+
79
+ The classes group fields are block options, not content, so they never consume
80
+ content rows. They are still listed in `modelFields` so the Universal Editor can
81
+ render their option controls.
82
+
83
+ ## Requirements
84
+
85
+ - Name grouped option fields `classes_<name>`.
86
+ - A `classes_<name>` **select/multiselect must declare `options`** — that is how
87
+ its tokens are recognized.
88
+ - A `classes_<name>` **boolean** needs no options; its name suffix is the class.
89
+ - A **free-form text** `classes_<name>` field (no options) cannot be
90
+ reverse-mapped to its field — its token has no matching signal — so it falls
91
+ back to the base `classes` field. As the spec notes, block options are usually
92
+ select or multiselect, which avoids this.
93
+
94
+ ## Why options, not position?
95
+
96
+ Mapping the first option to the first field, the second to the second, and so on
97
+ seems simpler, but it breaks whenever the number of tokens does not equal the
98
+ number of fields — which is common:
99
+
100
+ - a boolean that is **off** contributes no token,
101
+ - an empty select contributes no token,
102
+ - a multiselect contributes **several** tokens.
103
+
104
+ Any of these slides every following option into the wrong field. Matching by a
105
+ field's declared options (and a boolean's name suffix) is order-independent and
106
+ survives missing, optional, and multi-value fields.
107
+
108
+ ## Examples in the test suite
109
+
110
+ Worked fixtures live under
111
+ [`test/fixtures/blocks/core/block-options`](../test/fixtures/blocks/core/block-options):
112
+
113
+ - `block-options-grouping` — single selects + a boolean that is on
114
+ - `block-options-grouping-multi` — a multiselect claiming several values, boolean off
115
+ - `block-options-grouping-fallback` — free-form base `classes` catch-all + a boolean
116
+ - `block-options-grouping-booleans` — several independent boolean options, mixed on/off
@@ -0,0 +1,65 @@
1
+ # Section Styles (Element Grouping for `style`)
2
+
3
+ A section's appearance is controlled by the **Style** property of its Section
4
+ Metadata, which AEM turns into CSS classes on the containing section element.
5
+ `style` is the section counterpart of a block's [`classes`](./block-options.md)
6
+ property.
7
+
8
+ When a section model exposes several style options — especially mutually
9
+ exclusive ones — it can use the
10
+ [element grouping](https://www.aem.live/developer/component-model-definitions#element-grouping-for-block-options)
11
+ naming convention: a **style group** is the base `style` field plus any
12
+ `style_<name>` field.
13
+
14
+ ```jsonc
15
+ { "component": "select", "name": "style", "options": [ "variant-a", "variant-b" ] }
16
+ { "component": "multiselect", "name": "style_background", "options": [ "light", "dark" ] }
17
+ { "component": "boolean", "name": "style_fullwidth" }
18
+ ```
19
+
20
+ AEM collapses the whole style group into the single, well-known `style` cell of
21
+ the Section Metadata table (a flat, comma-separated list). Going from markdown to
22
+ JCR, md2jcr routes each token back to its field — exactly as it does for block
23
+ `classes`:
24
+
25
+ - **boolean field** — matched by its name suffix (after `style_`); token
26
+ `fullwidth` sets `style_fullwidth="true"`, absent leaves it unset;
27
+ - **select / multiselect field** — matched by its declared option values; a
28
+ multiselect claims all of its matching tokens (`style_background="[light, dark]"`);
29
+ - **base `style` field** — receives any leftover tokens (also the common single,
30
+ free-form `style` field case).
31
+
32
+ ### Example
33
+
34
+ ```markdown
35
+ +-------------------------------------------------------+
36
+ | Section Metadata |
37
+ +================+======================================+
38
+ | style | variant-a, light, fullwidth |
39
+ +----------------+--------------------------------------+
40
+ ```
41
+
42
+ becomes:
43
+
44
+ ```text
45
+ style="variant-a" style_background="light" style_fullwidth="true"
46
+ ```
47
+
48
+ ### Notes
49
+
50
+ - The routing rules and the underlying implementation are shared with block
51
+ options — see [Block Options](./block-options.md).
52
+ - Distribution only kicks in when the section model declares at least one
53
+ `style_<name>` field. A model with only a base `style` field keeps the simple
54
+ one-to-one mapping.
55
+ - The `style` group is listed in `modelFields` so the Universal Editor can
56
+ render those fields — the same as a block's `classes` group.
57
+
58
+ ### Worked fixtures
59
+
60
+ See [`test/fixtures/blocks/core/section-style-options`](../test/fixtures/blocks/core/section-style-options):
61
+
62
+ - `section-style-grouping` — single selects + a boolean that is on
63
+ - `section-style-grouping-multi` — a multiselect claiming several values, boolean off
64
+ - `section-style-grouping-fallback` — free-form base `style` catch-all + a boolean
65
+ - `section-style-grouping-booleans` — several independent boolean options, mixed on/off