@markuplint/alpine-spec 5.4.0 → 5.5.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/CHANGELOG.md CHANGED
@@ -3,6 +3,13 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [5.5.0](https://github.com/markuplint/markuplint/compare/v5.4.0...v5.5.0) (2026-11-10)
7
+
8
+ ### Features
9
+
10
+ * **alpine-spec:** declare how Alpine.js renders a literal in a binding ([5f05825](https://github.com/markuplint/markuplint/commit/5f05825989a9caacbd4dd890a2fa6d871a3aa69e))
11
+
12
+
6
13
  # [5.4.0](https://github.com/markuplint/markuplint/compare/v5.3.0...v5.4.0) (2026-10-29)
7
14
 
8
15
  **Note:** Version bump only for package @markuplint/alpine-spec
package/lib/index.js CHANGED
@@ -26,6 +26,47 @@
26
26
  */
27
27
  const spec = {
28
28
  cites: ['https://alpinejs.dev/'],
29
+ /**
30
+ * How Alpine.js renders a literal in `:attr="…"` (`bindAttribute` of `alpinejs/src/utils/bind.js`),
31
+ * where it differs from the default of `@markuplint/ml-core`. Names are in lower case.
32
+ *
33
+ * - `null`, `undefined` and `false` remove the attribute, except on `aria-pressed`, `aria-checked`,
34
+ * `aria-expanded` and `aria-selected`, which keep them as the strings `"null"`, `"undefined"` and `"false"`
35
+ * - A boolean attribute (its list is in `bind.js`) is present with its name as the value for anything but
36
+ * those three, `0` and the empty string included
37
+ * - `class` adds to the classes that the element has, and what is added is none for these literals
38
+ * - `hidden` is not in the list of boolean attributes, so `true` is `hidden="true"`
39
+ *
40
+ * What it does not tell, which does not affect a rule:
41
+ *
42
+ * - A boolean attribute has its name as the value (`disabled="disabled"`), not `""`
43
+ * - `:value` sets the property of an `input`, not the attribute (`option` is the one that sets the attribute)
44
+ * - `x-bind="{…}"`, the object syntax, has no attribute name and is not read
45
+ *
46
+ * @see https://alpinejs.dev/directives/bind#special-behavior
47
+ * @see https://github.com/alpinejs/alpine/blob/main/packages/alpinejs/src/utils/bind.js#bindAttribute
48
+ */
49
+ literalValues: {
50
+ booleanAttrs: { '""': '', '0': '' },
51
+ ariaAttrs: { false: null },
52
+ dataAttrs: { false: null },
53
+ otherAttrs: { false: null },
54
+ attrs: {
55
+ 'aria-pressed': { false: 'false', null: 'null', undefined: 'undefined' },
56
+ 'aria-checked': { false: 'false', null: 'null', undefined: 'undefined' },
57
+ 'aria-expanded': { false: 'false', null: 'null', undefined: 'undefined' },
58
+ 'aria-selected': { false: 'false', null: 'null', undefined: 'undefined' },
59
+ style: { false: 'false', null: 'null', undefined: 'undefined' },
60
+ class: { true: null, false: null, null: null, undefined: null, '0': null, '""': null },
61
+ hidden: { true: 'true', '0': '0' },
62
+ // Boolean attributes of the spec that Alpine does not know as boolean
63
+ alpha: { true: 'true', '0': '0', '""': '' },
64
+ focusgroupstart: { true: 'true', '0': '0', '""': '' },
65
+ headingreset: { true: 'true', '0': '0', '""': '' },
66
+ shadowrootcustomelementregistry: { true: 'true', '0': '0', '""': '' },
67
+ switch: { true: 'true', '0': '0', '""': '' },
68
+ },
69
+ },
29
70
  directivePatterns: [
30
71
  /**
31
72
  * Static directives: x-data, x-init, x-show, x-text, x-html,
@@ -52,16 +93,31 @@ const spec = {
52
93
  isDirective: true,
53
94
  isDynamicValue: true,
54
95
  },
96
+ /**
97
+ * x-bind:attr.modifier or :attr.modifier => potentialName = attr. A modifier (`.camel`) changes
98
+ * the name that is bound, so the value is not read as the value of `attr`.
99
+ * @see https://alpinejs.dev/directives/bind
100
+ */
101
+ {
102
+ pattern: '^(?:x-bind:|:)([^.]+)\\.[^.]+$',
103
+ potentialName: '$1',
104
+ isDynamicValue: true,
105
+ valueType: 'code',
106
+ isDuplicatable: ['class', 'style'],
107
+ },
55
108
  /**
56
109
  * x-bind:attr or :attr shorthand => potentialName = attr
57
110
  * @see https://alpinejs.dev/directives/bind
58
111
  */
59
112
  {
60
- pattern: '^(?:x-bind:|:)([^.]+)(?:\\.[^.]+)?$',
113
+ pattern: '^(?:x-bind:|:)([^.]+)$',
61
114
  potentialName: '$1',
62
115
  isDynamicValue: true,
63
116
  valueType: 'code',
64
117
  isDuplicatable: ['class', 'style'],
118
+ // The value is a JavaScript expression that is bound to the attribute, so a literal in it is
119
+ // rendered as Alpine renders it (`literalValues` below)
120
+ expressionLanguage: 'javascript',
65
121
  },
66
122
  /**
67
123
  * x-on:event or @event shorthand => potentialName = onevent
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markuplint/alpine-spec",
3
- "version": "5.4.0",
3
+ "version": "5.5.0",
4
4
  "description": "Extended specification for tags and attributes in Alpine.js",
5
5
  "repository": {
6
6
  "type": "git",
@@ -28,7 +28,7 @@
28
28
  "clean": "tsc --build --clean tsconfig.build.json"
29
29
  },
30
30
  "dependencies": {
31
- "@markuplint/ml-spec": "5.4.0"
31
+ "@markuplint/ml-spec": "5.5.0"
32
32
  },
33
- "gitHead": "ab56aa8e12b67fbf793ddf1267aaba9fda90269d"
33
+ "gitHead": "fedebcfd56eaec4642e68643b2f9e10750d810e1"
34
34
  }