comark-cards 0.7.5 → 0.8.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.md CHANGED
@@ -44,7 +44,7 @@ The plugin adds every problem to `meta.issues`, so a host can reject a document
44
44
 
45
45
  | Renderer | Import |
46
46
  | --- | --- |
47
- | `@comark/html` | `comark-cards/html`: the styles are inlined once per document |
47
+ | `@comark/html` | `comark-cards/html`: the styles are inlined |
48
48
  | `@comark/vue` | `comark-cards/vue` |
49
49
  | `@comark/react` | `comark-cards/react` |
50
50
  | `@comark/svelte` | `comark-cards/svelte` |
@@ -43,16 +43,66 @@ const tokens = [
43
43
  "#737373"
44
44
  ]
45
45
  ];
46
- const resolve = (scheme) => tokens.map((token) => `--_comark-${token[0]}:var(--comark-${token[0]},var(--${token[1]},var(--${token[2]},${token[scheme]})))`).join(";");
46
+ const groups = {
47
+ status: Object.values({
48
+ success: [
49
+ "success",
50
+ "ui-success",
51
+ "success",
52
+ "#259f56",
53
+ "#59d38c"
54
+ ],
55
+ danger: [
56
+ "danger",
57
+ "ui-error",
58
+ "destructive",
59
+ "#ee3533",
60
+ "#fd736d"
61
+ ],
62
+ warning: [
63
+ "warning",
64
+ "ui-warning",
65
+ "",
66
+ "#d97706",
67
+ "#fbbf24"
68
+ ],
69
+ info: [
70
+ "info",
71
+ "ui-info",
72
+ "",
73
+ "#2563eb",
74
+ "#60a5fa"
75
+ ]
76
+ }),
77
+ shadow: [[
78
+ "shadow",
79
+ "",
80
+ "",
81
+ "0 4px 12px rgb(0 0 0/.12)",
82
+ "0 4px 12px rgb(0 0 0/.5)"
83
+ ]]
84
+ };
85
+ const chain = (token, scheme) => {
86
+ const shadcn = token[2] ? `var(--${token[2]},${token[scheme]})` : token[scheme];
87
+ const nuxt = token[1] ? `var(--${token[1]},${shadcn})` : shadcn;
88
+ return `var(--comark-${token[0]},${nuxt})`;
89
+ };
90
+ const resolve = (list, scheme) => list.map((token) => `--_comark-${token[0]}:${chain(token, scheme)}`).join(";");
47
91
  /**
48
92
  * Resolves the `--comark-*` tokens on `selector`, falling back to Nuxt UI's variables, then shadcn/ui's, then shadcn's
49
93
  * neutral theme. Nuxt UI's corners are twice its `--ui-radius`, so the radius halves shadcn's to double it back.
50
94
  * They resolve on the component rather than `:root`, so a `.dark` class anywhere above it switches the theme, and a
51
95
  * `--comark-*` set on any ancestor still inherits down. `:where()` keeps them at zero specificity.
96
+ *
97
+ * Every block gets the core tokens. Name `"status"` for `--comark-success`, `-danger`, `-warning`, and `-info`, and
98
+ * `"shadow"` for `--comark-shadow`, so a block that never draws them doesn't carry them.
52
99
  */
53
- function theme(selector) {
100
+ function theme(selector, ...extras) {
54
101
  const where = `:where(${selector})`;
55
- return `${where}{${resolve(3)};--_comark-radius:var(--comark-radius,calc(var(--ui-radius,calc(var(--radius,.625rem)/2))*2));--_comark-font:var(--comark-font,var(--font-sans,ui-sans-serif,system-ui,sans-serif))}@media (prefers-color-scheme:dark){${where}{${resolve(4)}}}`;
102
+ const added = extras.flatMap((name) => groups[name]);
103
+ const list = [...tokens, ...added];
104
+ const classes = added.length ? `:where(.light,.light *)${where}{${resolve(added, 3)}}:where(.dark,.dark *)${where}{${resolve(added, 4)}}` : "";
105
+ return `${where}{${resolve(list, 3)};--_comark-radius:var(--comark-radius,calc(var(--ui-radius,calc(var(--radius,.625rem)/2))*2));--_comark-font:var(--comark-font,var(--font-sans,ui-sans-serif,system-ui,sans-serif))}@media (prefers-color-scheme:dark){${where}{${resolve(list, 4)}}}` + classes;
56
106
  }
57
107
  //#endregion
58
108
  //#region src/styles.ts
@@ -65,9 +115,8 @@ function theme(selector) {
65
115
  * primary then to the foreground, so a card is neutral like the other plugins until the page has a brand color. The rules sit in the `comark` layer, so any unlayered rule of the host's wins.
66
116
  */
67
117
  const css = `@layer comark{${[
68
- theme(".ccr,.ccr-group,.ccr-columns"),
69
- ":where(.ccr){--ccr-primary:var(--ui-primary,var(--primary,var(--_comark-foreground)));--ccr-secondary:var(--ui-secondary,#2563eb);--ccr-success:var(--ui-success,#16a34a);--ccr-info:var(--ui-info,#2563eb);--ccr-warning:var(--ui-warning,#ca8a04);--ccr-error:var(--ui-error,var(--destructive,#dc2626));--ccr-neutral:var(--_comark-foreground)}",
70
- "@media (prefers-color-scheme:dark){:where(.ccr){--ccr-secondary:var(--ui-secondary,#60a5fa);--ccr-success:var(--ui-success,#4ade80);--ccr-info:var(--ui-info,#60a5fa);--ccr-warning:var(--ui-warning,#facc15);--ccr-error:var(--ui-error,var(--destructive,#f87171))}}",
118
+ theme(".ccr,.ccr-group,.ccr-columns", "status"),
119
+ ":where(.ccr){--ccr-primary:var(--ui-primary,var(--primary,var(--_comark-foreground)));--ccr-secondary:var(--ui-secondary,var(--_comark-info));--ccr-success:var(--_comark-success);--ccr-info:var(--_comark-info);--ccr-warning:var(--_comark-warning);--ccr-error:var(--_comark-danger);--ccr-neutral:var(--_comark-foreground)}",
71
120
  ".ccr-group,.ccr-columns{display:grid;gap:1rem;margin:1.5rem 0;container-type:inline-size}",
72
121
  ".ccr-group{grid-template-columns:repeat(2,minmax(0,1fr))}",
73
122
  ...[
@@ -81,7 +130,7 @@ const css = `@layer comark{${[
81
130
  ".ccr-group>.ccr{margin:0}",
82
131
  ".ccr-column>:first-child{margin-top:0}",
83
132
  ".ccr-column>:last-child{margin-bottom:0}",
84
- ".ccr{--ccr:var(--ccr-primary);position:relative;margin:1.5rem 0;padding:1.25rem;border:1px solid var(--_comark-border);border-radius:calc(var(--_comark-radius) + 2px);background:var(--_comark-background);box-shadow:0 1px 2px rgb(0 0 0/.05);color:var(--_comark-muted);font:400 .875rem/1.375rem var(--_comark-font);transition:background-color .15s,border-color .15s}",
133
+ ".ccr{--ccr:var(--ccr-primary);position:relative;margin:1.5rem 0;padding:1.25rem;border:1px solid var(--_comark-border);border-radius:calc(var(--_comark-radius) + 2px);background:var(--_comark-background);color:var(--_comark-muted);font:400 .875rem/1.375rem var(--_comark-font);transition:background-color .15s,border-color .15s}",
85
134
  ...[
86
135
  "secondary",
87
136
  "success",
@@ -109,15 +158,23 @@ const css = `@layer comark{${[
109
158
  "@media print{.ccr{break-inside:avoid}.ccr-external{display:none}}"
110
159
  ].join("")}}`;
111
160
  //#endregion
112
- //#region src/client.ts
113
- /** Adds the card styles to the page once. The Vue, React, and Svelte components call it when they mount. */
114
- function addStyles() {
115
- if (document.querySelector("style[data-comark-cards]")) return;
161
+ //#region ../../shared/styles.ts
162
+ /**
163
+ * Adds a package's styles to the page once, unless the page already carries them as a `style` or a `link` with
164
+ * `data-comark-<name>`, which is how the HTML renderer inlines them and how an app that imports `style.css` marks them.
165
+ */
166
+ function addStyles$1(name, css) {
167
+ const attribute = `data-comark-${name}`;
168
+ if (document.querySelector(`style[${attribute}],link[${attribute}]`)) return;
116
169
  const style = document.createElement("style");
117
- style.dataset.comarkCards = "";
170
+ style.setAttribute(attribute, "");
118
171
  style.textContent = css;
119
172
  document.head.append(style);
120
173
  }
174
+ //#endregion
175
+ //#region src/client.ts
176
+ /** Adds the card styles to the page once. The Vue, React, and Svelte components call it when they mount. */
177
+ const addStyles = () => addStyles$1("cards", css);
121
178
  /** Lucide's arrow-up-right (ISC), drawn in the corner of a card that opens in a new tab. */
122
179
  const external = "M7 7h10v10M7 17 17 7";
123
180
  /** Returns whether a card URL is safe to place in an href. */
package/dist/client.d.mts CHANGED
@@ -11,7 +11,7 @@ export declare const css: string;
11
11
  //#endregion
12
12
  //#region src/client.d.ts
13
13
  /** Adds the card styles to the page once. The Vue, React, and Svelte components call it when they mount. */
14
- export declare function addStyles(): void;
14
+ export declare const addStyles: () => void;
15
15
  /** Lucide's arrow-up-right (ISC), drawn in the corner of a card that opens in a new tab. */
16
16
  export declare const external = "M7 7h10v10M7 17 17 7";
17
17
  /** Returns whether a card URL is safe to place in an href. */
package/dist/client.mjs CHANGED
@@ -1 +1 @@
1
- export { t as addStyles, o as css, n as external, r as isExternalHref, i as isSafeHref, a as rel } from "./client-C5acm8_C.mjs";
1
+ export { t as addStyles, o as css, n as external, r as isExternalHref, i as isSafeHref, a as rel } from "./client-DsO8I6Oo.mjs";
package/dist/html.mjs CHANGED
@@ -1,10 +1,13 @@
1
- import { a as rel, n as external, o as css } from "./client-C5acm8_C.mjs";
2
- import src_default, { colors, variants } from "./index.mjs";
1
+ import { a as rel, n as external, o as css } from "./client-DsO8I6Oo.mjs";
2
+ import { i as rawText, n as src_default, r as variants, t as colors } from "./src-BsKBRfFx.mjs";
3
3
  import { escapeHtml } from "comark/utils";
4
4
  import { iconHtml, iconStyles } from "comark-icon/html";
5
+ //#region ../../shared/html.ts
6
+ /** A value as the inside of a double-quoted HTML attribute. */
7
+ const attr = (value) => escapeHtml(rawText(value), { "\"": "&quot;" });
8
+ //#endregion
5
9
  //#region src/html.ts
6
10
  const styled = /* @__PURE__ */ new WeakSet();
7
- const attr = (value) => escapeHtml(String(value), { "\"": "&quot;" });
8
11
  const styles = (state) => {
9
12
  if (styled.has(state.context)) return "";
10
13
  styled.add(state.context);
package/dist/index.mjs CHANGED
@@ -1,211 +1 @@
1
- import { i as isSafeHref, r as isExternalHref } from "./client-C5acm8_C.mjs";
2
- import { defineComarkPlugin } from "comark";
3
- import { resolveAttributes } from "comark/render";
4
- import { textContent } from "comark/utils";
5
- import { createIcons, describeIconProblem, iconProblemRule } from "comark-icon";
6
- //#region src/index.ts
7
- /** Nuxt UI's colors, which tint a card's icon and border, and its background with a `variant`. */
8
- const colors = [
9
- "primary",
10
- "secondary",
11
- "success",
12
- "info",
13
- "warning",
14
- "error",
15
- "neutral"
16
- ];
17
- /**
18
- * Nuxt UI's card variants that a document block needs: `outline`, the default, keeps the plain card, `soft` washes it
19
- * in its color, and `subtle` washes it and draws its border in the color too.
20
- */
21
- const variants = [
22
- "outline",
23
- "soft",
24
- "subtle"
25
- ];
26
- const isElement = (node) => Array.isArray(node) && node[0] !== null;
27
- const isContent = (node) => Array.isArray(node) ? node[0] !== null : node.trim() !== "";
28
- const childrenOf = (node) => node.slice(2);
29
- const text = (value) => value === void 0 || value === null || value instanceof Object ? "" : String(value).trim();
30
- const clip = (value) => {
31
- const words = value.trim().replace(/\s+/g, " ");
32
- return words.length > 40 ? `${words.slice(0, 39)}…` : words;
33
- };
34
- const isColor = (value) => colors.some((color) => color === value);
35
- const isVariant = (value) => variants.some((variant) => variant === value);
36
- const isTemplate = (node, name) => isElement(node) && node[0] === "template" && node[1].name === name;
37
- const unwrap = (nodes) => nodes.filter(isContent).flatMap((node) => isElement(node) && node[0] === "p" ? childrenOf(node) : [node]);
38
- const resolve = (node, walk) => resolveAttributes(node[1], walk.data, { parseJson: true });
39
- const CARD_PROPS = [
40
- "title",
41
- "description",
42
- "icon",
43
- "to",
44
- "href",
45
- "target",
46
- "color",
47
- "variant"
48
- ];
49
- function unknownProps(node, name, block, props, walk) {
50
- const known = props.flatMap((prop) => prop === "href" ? [] : [`\`${prop}\``]);
51
- const list = known.length > 1 ? `${known.slice(0, -1).join(", ")}, or ${known.at(-1)}` : known[0];
52
- for (const key of Object.keys(node[1])) {
53
- const prop = key.replace(/^:/, "");
54
- if (!props.includes(prop) && prop !== "id" && prop !== "class") walk.issues.push({
55
- rule: "comark/cards/unknown-prop",
56
- message: `${name}: \`${prop}\` isn't a prop of \`${block}\`, so it's ignored. Use ${list}.`
57
- });
58
- }
59
- }
60
- function card(node, walk) {
61
- const attrs = resolve(node, walk);
62
- const children = childrenOf(node);
63
- const slot = children.find((child) => isTemplate(child, "title"));
64
- const content = children.flatMap((child) => child === slot ? [] : isTemplate(child, "default") ? childrenOf(child) : [child]);
65
- const title = slot ? unwrap(childrenOf(slot)) : text(attrs.title) ? [text(attrs.title)] : [];
66
- const words = clip(textContent([
67
- "p",
68
- {},
69
- ...title
70
- ]));
71
- const name = `\`::card\` ${++walk.cards}${words ? ` ("${words}")` : ""}`;
72
- unknownProps(node, name, "::card", CARD_PROPS, walk);
73
- const description = text(attrs.description);
74
- if (!content.some(isContent) && description) content.push([
75
- "p",
76
- {},
77
- description
78
- ]);
79
- const written = text(attrs.color);
80
- const result = {};
81
- result.color = isColor(written) ? written : "primary";
82
- if (written && !isColor(written)) walk.issues.push({
83
- rule: "comark/cards/invalid-color",
84
- message: `${name}: \`color\` is "${clip(written)}", so it shows as primary. Write one of ${colors.join(", ")}.`
85
- });
86
- const variant = text(attrs.variant);
87
- if (isVariant(variant)) {
88
- if (variant !== "outline") result.variant = variant;
89
- } else if (variant) walk.issues.push({
90
- rule: "comark/cards/invalid-variant",
91
- message: `${name}: \`variant\` is "${clip(variant)}", so it shows as outline. Write one of ${variants.join(", ")}.`
92
- });
93
- let to = text(attrs.to);
94
- const href = text(attrs.href);
95
- if (href && !to) {
96
- walk.issues.push({
97
- rule: "comark/cards/href-instead-of-to",
98
- message: `${name}: Nuxt UI links a card with \`to\`, so it ignores \`href\`. Write \`to="${clip(href)}"\` instead.`
99
- });
100
- to = href;
101
- }
102
- if (to && !isSafeHref(to)) {
103
- walk.issues.push({
104
- rule: "comark/cards/unsafe-link",
105
- message: `${name}: \`to\` must be a page or a web address, so it isn't a link.`
106
- });
107
- to = "";
108
- }
109
- if (to) {
110
- result.to = to;
111
- const target = text(attrs.target) || (isExternalHref(to) ? "_blank" : "");
112
- if (target) result.target = target;
113
- if (target === "_blank") result.external = "true";
114
- result.label = words || "Card link";
115
- }
116
- const icon = text(attrs.icon);
117
- if (icon) walk.icons.push({
118
- card: node,
119
- value: icon,
120
- name
121
- });
122
- node[1] = result;
123
- const head = title.length ? [[
124
- "card-title",
125
- {},
126
- ...title
127
- ]] : [];
128
- node.splice(2, node.length - 2, ...head, ...content);
129
- }
130
- function columns(node, walk) {
131
- const name = `\`::columns\` ${++walk.columns}`;
132
- unknownProps(node, name, "::columns", ["cols"], walk);
133
- const written = text(resolve(node, walk).cols);
134
- let cols = "2";
135
- if (/^[1-6]$/.test(written)) cols = written;
136
- else if (written) walk.issues.push({
137
- rule: "comark/cards/invalid-cols",
138
- message: `${name}: \`cols\` is "${clip(written)}", so it shows 2 columns. Write a number from 1 to 6.`
139
- });
140
- const cells = childrenOf(node).filter(isContent).map((child) => isElement(child) && child[0] === "column" ? [
141
- "column",
142
- {},
143
- ...childrenOf(child)
144
- ] : [
145
- "column",
146
- {},
147
- child
148
- ]);
149
- if (!cells.length) walk.issues.push({
150
- rule: "comark/cards/empty-columns",
151
- message: `${name} is empty. Put a block inside for each column, or remove it.`
152
- });
153
- node[1] = { cols };
154
- node.splice(2, node.length - 2, ...cells);
155
- }
156
- function transform(nodes, walk) {
157
- for (const node of nodes) {
158
- if (!isElement(node)) continue;
159
- if (node[0] === "card") card(node, walk);
160
- else if (node[0] === "columns") columns(node, walk);
161
- else if (node[0] === "card-group") node[1] = {};
162
- transform(childrenOf(node), walk);
163
- }
164
- }
165
- async function addIcons(walk, resolver) {
166
- if (!walk.icons.length) return;
167
- const resolved = await resolver.resolve(walk.icons.map(({ value }) => value));
168
- for (const { card: node, value, name } of walk.icons) {
169
- const icon = resolved.get(value);
170
- if (!icon) continue;
171
- if ("problem" in icon) walk.issues.push({
172
- rule: iconProblemRule(icon),
173
- message: `${name}, icon "${clip(value)}": ${describeIconProblem(value, icon)}`
174
- });
175
- else node[1].icon = icon;
176
- }
177
- }
178
- /**
179
- * Turns `::card-group` and `::card{title description icon to target color variant}` into cards, link cards when they
180
- * have `to`, and `::columns{cols}` into a grid with a cell per block. Problems are added to `meta.issues`.
181
- */
182
- var src_default = defineComarkPlugin((options = {}) => {
183
- const resolver = options.iconResolver ?? createIcons();
184
- return {
185
- name: "comark-cards",
186
- async post(state) {
187
- const { tree } = state;
188
- const walk = {
189
- data: {
190
- frontmatter: tree.frontmatter,
191
- meta: tree.meta,
192
- data: {},
193
- props: {}
194
- },
195
- issues: [],
196
- icons: [],
197
- cards: 0,
198
- columns: 0
199
- };
200
- transform(tree.nodes, walk);
201
- await addIcons(walk, resolver);
202
- tree.meta.issues = [...tree.meta.issues ?? [], ...walk.issues.map((issue) => issue.message)];
203
- tree.meta.issueRules = {
204
- ...tree.meta.issueRules,
205
- ...Object.fromEntries(walk.issues.map((issue) => [issue.message, issue.rule]))
206
- };
207
- }
208
- };
209
- });
210
- //#endregion
211
- export { colors, src_default as default, variants };
1
+ export { t as colors, n as default, r as variants } from "./src-BsKBRfFx.mjs";
package/dist/react.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { a as rel, i as isSafeHref, n as external, r as isExternalHref, t as addStyles } from "./client-C5acm8_C.mjs";
2
- import src_default, { colors, variants } from "./index.mjs";
1
+ import { a as rel, i as isSafeHref, n as external, r as isExternalHref, t as addStyles } from "./client-DsO8I6Oo.mjs";
2
+ import { n as src_default, r as variants, t as colors } from "./src-BsKBRfFx.mjs";
3
3
  import { Icon } from "comark-icon/react";
4
4
  import { createElement, useEffect } from "react";
5
5
  //#region src/react.ts
@@ -0,0 +1,232 @@
1
+ import { i as isSafeHref, r as isExternalHref } from "./client-DsO8I6Oo.mjs";
2
+ import { defineComarkPlugin } from "comark";
3
+ import { resolveAttributes } from "comark/render";
4
+ import { textContent } from "comark/utils";
5
+ import { createIcons, describeIconProblem, iconProblemRule } from "comark-icon";
6
+ //#region ../../shared/text.ts
7
+ /**
8
+ * A prop value as text. Comark writes strings, numbers, and booleans, so an object, array, function, or missing value
9
+ * is no text. The `typeof` check, unlike `instanceof`, can't throw on a revoked proxy.
10
+ */
11
+ function scalar(value) {
12
+ switch (typeof value) {
13
+ case "string": return value;
14
+ case "number":
15
+ case "boolean":
16
+ case "bigint":
17
+ case "symbol": return String(value);
18
+ default: return;
19
+ }
20
+ }
21
+ /** A prop value as text, without its surrounding whitespace, or empty when it has none. */
22
+ const text = (value) => scalar(value)?.trim() ?? "";
23
+ /** A prop value as text exactly as written, or empty when it has none. */
24
+ const rawText = (value) => scalar(value) ?? "";
25
+ //#endregion
26
+ //#region ../../shared/nodes.ts
27
+ const isElement = (node) => Array.isArray(node) && node[0] !== null;
28
+ const childrenOf = ([, , ...rest]) => rest;
29
+ //#endregion
30
+ //#region src/index.ts
31
+ /** Nuxt UI's colors, which tint a card's icon and border, and its background with a `variant`. */
32
+ const colors = [
33
+ "primary",
34
+ "secondary",
35
+ "success",
36
+ "info",
37
+ "warning",
38
+ "error",
39
+ "neutral"
40
+ ];
41
+ /**
42
+ * Nuxt UI's card variants that a document block needs: `outline`, the default, keeps the plain card, `soft` washes it
43
+ * in its color, and `subtle` washes it and draws its border in the color too.
44
+ */
45
+ const variants = [
46
+ "outline",
47
+ "soft",
48
+ "subtle"
49
+ ];
50
+ const isContent = (node) => Array.isArray(node) ? node[0] !== null : node.trim() !== "";
51
+ const clip = (value) => {
52
+ const words = value.trim().replace(/\s+/g, " ");
53
+ return words.length > 40 ? `${words.slice(0, 39)}…` : words;
54
+ };
55
+ const isColor = (value) => colors.some((color) => color === value);
56
+ const isVariant = (value) => variants.some((variant) => variant === value);
57
+ const isTemplate = (node, name) => isElement(node) && node[0] === "template" && node[1].name === name;
58
+ const unwrap = (nodes) => nodes.filter(isContent).flatMap((node) => isElement(node) && node[0] === "p" ? childrenOf(node) : [node]);
59
+ const resolve = (node, walk) => resolveAttributes(node[1], walk.data, { parseJson: true });
60
+ const CARD_PROPS = [
61
+ "title",
62
+ "description",
63
+ "icon",
64
+ "to",
65
+ "href",
66
+ "target",
67
+ "color",
68
+ "variant"
69
+ ];
70
+ function unknownProps(node, name, block, props, walk) {
71
+ const known = props.flatMap((prop) => prop === "href" ? [] : [`\`${prop}\``]);
72
+ const list = known.length > 1 ? `${known.slice(0, -1).join(", ")}, or ${known.at(-1)}` : known[0];
73
+ for (const key of Object.keys(node[1])) {
74
+ const prop = key.replace(/^:/, "");
75
+ if (!props.includes(prop) && prop !== "id" && prop !== "class") walk.issues.push({
76
+ rule: "comark/cards/unknown-prop",
77
+ message: `${name}: \`${prop}\` isn't a prop of \`${block}\`, so it's ignored. Use ${list}.`
78
+ });
79
+ }
80
+ }
81
+ function card(node, walk) {
82
+ const attrs = resolve(node, walk);
83
+ const children = childrenOf(node);
84
+ const slot = children.find((child) => isTemplate(child, "title"));
85
+ const content = children.flatMap((child) => child === slot ? [] : isTemplate(child, "default") ? childrenOf(child) : [child]);
86
+ const title = slot ? unwrap(childrenOf(slot)) : text(attrs.title) ? [text(attrs.title)] : [];
87
+ const words = clip(textContent([
88
+ "p",
89
+ {},
90
+ ...title
91
+ ]));
92
+ const name = `\`::card\` ${++walk.cards}${words ? ` ("${words}")` : ""}`;
93
+ unknownProps(node, name, "::card", CARD_PROPS, walk);
94
+ const description = text(attrs.description);
95
+ if (!content.some(isContent) && description) content.push([
96
+ "p",
97
+ {},
98
+ description
99
+ ]);
100
+ const written = text(attrs.color);
101
+ const result = {};
102
+ result.color = isColor(written) ? written : "primary";
103
+ if (written && !isColor(written)) walk.issues.push({
104
+ rule: "comark/cards/invalid-color",
105
+ message: `${name}: \`color\` is "${clip(written)}", so it shows as primary. Write one of ${colors.join(", ")}.`
106
+ });
107
+ const variant = text(attrs.variant);
108
+ if (isVariant(variant)) {
109
+ if (variant !== "outline") result.variant = variant;
110
+ } else if (variant) walk.issues.push({
111
+ rule: "comark/cards/invalid-variant",
112
+ message: `${name}: \`variant\` is "${clip(variant)}", so it shows as outline. Write one of ${variants.join(", ")}.`
113
+ });
114
+ let to = text(attrs.to);
115
+ const href = text(attrs.href);
116
+ if (href && !to) {
117
+ walk.issues.push({
118
+ rule: "comark/cards/href-instead-of-to",
119
+ message: `${name}: Nuxt UI links a card with \`to\`, so it ignores \`href\`. Write \`to="${clip(href)}"\` instead.`
120
+ });
121
+ to = href;
122
+ }
123
+ if (to && !isSafeHref(to)) {
124
+ walk.issues.push({
125
+ rule: "comark/cards/unsafe-link",
126
+ message: `${name}: \`to\` must be a page or a web address, so it isn't a link.`
127
+ });
128
+ to = "";
129
+ }
130
+ if (to) {
131
+ result.to = to;
132
+ const target = text(attrs.target) || (isExternalHref(to) ? "_blank" : "");
133
+ if (target) result.target = target;
134
+ if (target === "_blank") result.external = "true";
135
+ result.label = words || "Card link";
136
+ }
137
+ const icon = text(attrs.icon);
138
+ if (icon) walk.icons.push({
139
+ card: node,
140
+ value: icon,
141
+ name
142
+ });
143
+ node[1] = result;
144
+ const head = title.length ? [[
145
+ "card-title",
146
+ {},
147
+ ...title
148
+ ]] : [];
149
+ node.splice(2, node.length - 2, ...head, ...content);
150
+ }
151
+ function columns(node, walk) {
152
+ const name = `\`::columns\` ${++walk.columns}`;
153
+ unknownProps(node, name, "::columns", ["cols"], walk);
154
+ const written = text(resolve(node, walk).cols);
155
+ let cols = "2";
156
+ if (/^[1-6]$/.test(written)) cols = written;
157
+ else if (written) walk.issues.push({
158
+ rule: "comark/cards/invalid-cols",
159
+ message: `${name}: \`cols\` is "${clip(written)}", so it shows 2 columns. Write a number from 1 to 6.`
160
+ });
161
+ const cells = childrenOf(node).filter(isContent).map((child) => isElement(child) && child[0] === "column" ? [
162
+ "column",
163
+ {},
164
+ ...childrenOf(child)
165
+ ] : [
166
+ "column",
167
+ {},
168
+ child
169
+ ]);
170
+ if (!cells.length) walk.issues.push({
171
+ rule: "comark/cards/empty-columns",
172
+ message: `${name} is empty. Put a block inside for each column, or remove it.`
173
+ });
174
+ node[1] = { cols };
175
+ node.splice(2, node.length - 2, ...cells);
176
+ }
177
+ function transform(nodes, walk) {
178
+ for (const node of nodes) {
179
+ if (!isElement(node)) continue;
180
+ if (node[0] === "card") card(node, walk);
181
+ else if (node[0] === "columns") columns(node, walk);
182
+ else if (node[0] === "card-group") node[1] = {};
183
+ transform(childrenOf(node), walk);
184
+ }
185
+ }
186
+ async function addIcons(walk, resolver) {
187
+ if (!walk.icons.length) return;
188
+ const resolved = await resolver.resolve(walk.icons.map(({ value }) => value));
189
+ for (const { card: node, value, name } of walk.icons) {
190
+ const icon = resolved.get(value);
191
+ if (!icon) continue;
192
+ if ("problem" in icon) walk.issues.push({
193
+ rule: iconProblemRule(icon),
194
+ message: `${name}, icon "${clip(value)}": ${describeIconProblem(value, icon)}`
195
+ });
196
+ else node[1].icon = icon;
197
+ }
198
+ }
199
+ /**
200
+ * Turns `::card-group` and `::card{title description icon to target color variant}` into cards, link cards when they
201
+ * have `to`, and `::columns{cols}` into a grid with a cell per block. Problems are added to `meta.issues`.
202
+ */
203
+ var src_default = defineComarkPlugin((options = {}) => {
204
+ const resolver = options.iconResolver ?? createIcons();
205
+ return {
206
+ name: "comark-cards",
207
+ async post(state) {
208
+ const { tree } = state;
209
+ const walk = {
210
+ data: {
211
+ frontmatter: tree.frontmatter,
212
+ meta: tree.meta,
213
+ data: {},
214
+ props: {}
215
+ },
216
+ issues: [],
217
+ icons: [],
218
+ cards: 0,
219
+ columns: 0
220
+ };
221
+ transform(tree.nodes, walk);
222
+ await addIcons(walk, resolver);
223
+ tree.meta.issues = [...tree.meta.issues ?? [], ...walk.issues.map((issue) => issue.message)];
224
+ tree.meta.issueRules = {
225
+ ...tree.meta.issueRules,
226
+ ...Object.fromEntries(walk.issues.map((issue) => [issue.message, issue.rule]))
227
+ };
228
+ }
229
+ };
230
+ });
231
+ //#endregion
232
+ export { rawText as i, src_default as n, variants as r, colors as t };
package/dist/style.css CHANGED
@@ -1 +1 @@
1
- @layer comark{:where(.ccr,.ccr-group,.ccr-columns){--_comark-background:var(--comark-background,var(--ui-bg,var(--background,#fff)));--_comark-foreground:var(--comark-foreground,var(--ui-text-highlighted,var(--foreground,#0a0a0a)));--_comark-muted:var(--comark-muted,var(--ui-text-muted,var(--muted-foreground,#737373)));--_comark-border:var(--comark-border,var(--ui-border,var(--border,#e5e5e5)));--_comark-accent:var(--comark-accent,var(--ui-bg-elevated,var(--accent,#f5f5f5)));--_comark-ring:var(--comark-ring,var(--ui-primary,var(--ring,#a1a1a1)));--_comark-radius:var(--comark-radius,calc(var(--ui-radius,calc(var(--radius,.625rem)/2))*2));--_comark-font:var(--comark-font,var(--font-sans,ui-sans-serif,system-ui,sans-serif))}@media (prefers-color-scheme:dark){:where(.ccr,.ccr-group,.ccr-columns){--_comark-background:var(--comark-background,var(--ui-bg,var(--background,#0a0a0a)));--_comark-foreground:var(--comark-foreground,var(--ui-text-highlighted,var(--foreground,#fafafa)));--_comark-muted:var(--comark-muted,var(--ui-text-muted,var(--muted-foreground,#a1a1a1)));--_comark-border:var(--comark-border,var(--ui-border,var(--border,rgb(255 255 255/.1))));--_comark-accent:var(--comark-accent,var(--ui-bg-elevated,var(--accent,#262626)));--_comark-ring:var(--comark-ring,var(--ui-primary,var(--ring,#737373)))}}:where(.ccr){--ccr-primary:var(--ui-primary,var(--primary,var(--_comark-foreground)));--ccr-secondary:var(--ui-secondary,#2563eb);--ccr-success:var(--ui-success,#16a34a);--ccr-info:var(--ui-info,#2563eb);--ccr-warning:var(--ui-warning,#ca8a04);--ccr-error:var(--ui-error,var(--destructive,#dc2626));--ccr-neutral:var(--_comark-foreground)}@media (prefers-color-scheme:dark){:where(.ccr){--ccr-secondary:var(--ui-secondary,#60a5fa);--ccr-success:var(--ui-success,#4ade80);--ccr-info:var(--ui-info,#60a5fa);--ccr-warning:var(--ui-warning,#facc15);--ccr-error:var(--ui-error,var(--destructive,#f87171))}}.ccr-group,.ccr-columns{display:grid;gap:1rem;margin:1.5rem 0;container-type:inline-size}.ccr-group{grid-template-columns:repeat(2,minmax(0,1fr))}.ccr-columns[data-cols='2']{grid-template-columns:repeat(2,minmax(0,1fr))}.ccr-columns[data-cols='3']{grid-template-columns:repeat(3,minmax(0,1fr))}.ccr-columns[data-cols='4']{grid-template-columns:repeat(4,minmax(0,1fr))}.ccr-columns[data-cols='5']{grid-template-columns:repeat(5,minmax(0,1fr))}.ccr-columns[data-cols='6']{grid-template-columns:repeat(6,minmax(0,1fr))}@container (width<36rem){.ccr-group>*,.ccr-columns>*{grid-column:1/-1}}.ccr-group>.ccr{margin:0}.ccr-column>:first-child{margin-top:0}.ccr-column>:last-child{margin-bottom:0}.ccr{--ccr:var(--ccr-primary);position:relative;margin:1.5rem 0;padding:1.25rem;border:1px solid var(--_comark-border);border-radius:calc(var(--_comark-radius) + 2px);background:var(--_comark-background);box-shadow:0 1px 2px rgb(0 0 0/.05);color:var(--_comark-muted);font:400 .875rem/1.375rem var(--_comark-font);transition:background-color .15s,border-color .15s}.ccr[data-color='secondary']{--ccr:var(--ccr-secondary)}.ccr[data-color='success']{--ccr:var(--ccr-success)}.ccr[data-color='info']{--ccr:var(--ccr-info)}.ccr[data-color='warning']{--ccr:var(--ccr-warning)}.ccr[data-color='error']{--ccr:var(--ccr-error)}.ccr[data-color='neutral']{--ccr:var(--ccr-neutral)}.ccr[data-variant='soft'],.ccr[data-variant='subtle']{background:color-mix(in srgb,var(--ccr) 10%,var(--_comark-background))}.ccr[data-variant='soft']{border-color:transparent}.ccr[data-variant='subtle']{border-color:color-mix(in srgb,var(--ccr) 30%,var(--_comark-background))}.ccr>:last-child{margin-bottom:0}.ccr>.ci{--comark-icon-size:1.25rem;display:block;margin-bottom:.75rem;color:var(--ccr)}.ccr-title{margin:0 0 .25rem;color:var(--_comark-foreground);font-size:.9375rem;font-weight:600;line-height:1.375rem;text-wrap:balance}.ccr-title+*{margin-top:0}.ccr-link{position:absolute;inset:0;z-index:0;border-radius:inherit}.ccr-link:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--_comark-ring) 50%,transparent)}.ccr :is(a,button):not(.ccr-link){position:relative;z-index:1}.ccr:has(>.ccr-link):hover{border-color:color-mix(in srgb,var(--ccr) 50%,var(--_comark-border));background:color-mix(in srgb,var(--_comark-accent) 50%,var(--_comark-background))}.ccr[data-variant='soft']:has(>.ccr-link):hover{border-color:transparent}.ccr[data-variant='subtle']:has(>.ccr-link):hover{border-color:color-mix(in srgb,var(--ccr) 30%,var(--_comark-background))}.ccr[data-variant='soft']:has(>.ccr-link):hover,.ccr[data-variant='subtle']:has(>.ccr-link):hover{background:color-mix(in srgb,var(--ccr) 16%,var(--_comark-background))}.ccr-external{position:absolute;top:1rem;inset-inline-end:1rem;width:1rem;height:1rem;color:var(--_comark-muted)}.ccr:dir(rtl) .ccr-external{scale:-1 1}@media print{.ccr{break-inside:avoid}.ccr-external{display:none}}}
1
+ @layer comark{:where(.ccr,.ccr-group,.ccr-columns){--_comark-background:var(--comark-background,var(--ui-bg,var(--background,#fff)));--_comark-foreground:var(--comark-foreground,var(--ui-text-highlighted,var(--foreground,#0a0a0a)));--_comark-muted:var(--comark-muted,var(--ui-text-muted,var(--muted-foreground,#737373)));--_comark-border:var(--comark-border,var(--ui-border,var(--border,#e5e5e5)));--_comark-accent:var(--comark-accent,var(--ui-bg-elevated,var(--accent,#f5f5f5)));--_comark-ring:var(--comark-ring,var(--ui-primary,var(--ring,#a1a1a1)));--_comark-success:var(--comark-success,var(--ui-success,var(--success,#259f56)));--_comark-danger:var(--comark-danger,var(--ui-error,var(--destructive,#ee3533)));--_comark-warning:var(--comark-warning,var(--ui-warning,#d97706));--_comark-info:var(--comark-info,var(--ui-info,#2563eb));--_comark-radius:var(--comark-radius,calc(var(--ui-radius,calc(var(--radius,.625rem)/2))*2));--_comark-font:var(--comark-font,var(--font-sans,ui-sans-serif,system-ui,sans-serif))}@media (prefers-color-scheme:dark){:where(.ccr,.ccr-group,.ccr-columns){--_comark-background:var(--comark-background,var(--ui-bg,var(--background,#0a0a0a)));--_comark-foreground:var(--comark-foreground,var(--ui-text-highlighted,var(--foreground,#fafafa)));--_comark-muted:var(--comark-muted,var(--ui-text-muted,var(--muted-foreground,#a1a1a1)));--_comark-border:var(--comark-border,var(--ui-border,var(--border,rgb(255 255 255/.1))));--_comark-accent:var(--comark-accent,var(--ui-bg-elevated,var(--accent,#262626)));--_comark-ring:var(--comark-ring,var(--ui-primary,var(--ring,#737373)));--_comark-success:var(--comark-success,var(--ui-success,var(--success,#59d38c)));--_comark-danger:var(--comark-danger,var(--ui-error,var(--destructive,#fd736d)));--_comark-warning:var(--comark-warning,var(--ui-warning,#fbbf24));--_comark-info:var(--comark-info,var(--ui-info,#60a5fa))}}:where(.light,.light *):where(.ccr,.ccr-group,.ccr-columns){--_comark-success:var(--comark-success,var(--ui-success,var(--success,#259f56)));--_comark-danger:var(--comark-danger,var(--ui-error,var(--destructive,#ee3533)));--_comark-warning:var(--comark-warning,var(--ui-warning,#d97706));--_comark-info:var(--comark-info,var(--ui-info,#2563eb))}:where(.dark,.dark *):where(.ccr,.ccr-group,.ccr-columns){--_comark-success:var(--comark-success,var(--ui-success,var(--success,#59d38c)));--_comark-danger:var(--comark-danger,var(--ui-error,var(--destructive,#fd736d)));--_comark-warning:var(--comark-warning,var(--ui-warning,#fbbf24));--_comark-info:var(--comark-info,var(--ui-info,#60a5fa))}:where(.ccr){--ccr-primary:var(--ui-primary,var(--primary,var(--_comark-foreground)));--ccr-secondary:var(--ui-secondary,var(--_comark-info));--ccr-success:var(--_comark-success);--ccr-info:var(--_comark-info);--ccr-warning:var(--_comark-warning);--ccr-error:var(--_comark-danger);--ccr-neutral:var(--_comark-foreground)}.ccr-group,.ccr-columns{display:grid;gap:1rem;margin:1.5rem 0;container-type:inline-size}.ccr-group{grid-template-columns:repeat(2,minmax(0,1fr))}.ccr-columns[data-cols='2']{grid-template-columns:repeat(2,minmax(0,1fr))}.ccr-columns[data-cols='3']{grid-template-columns:repeat(3,minmax(0,1fr))}.ccr-columns[data-cols='4']{grid-template-columns:repeat(4,minmax(0,1fr))}.ccr-columns[data-cols='5']{grid-template-columns:repeat(5,minmax(0,1fr))}.ccr-columns[data-cols='6']{grid-template-columns:repeat(6,minmax(0,1fr))}@container (width<36rem){.ccr-group>*,.ccr-columns>*{grid-column:1/-1}}.ccr-group>.ccr{margin:0}.ccr-column>:first-child{margin-top:0}.ccr-column>:last-child{margin-bottom:0}.ccr{--ccr:var(--ccr-primary);position:relative;margin:1.5rem 0;padding:1.25rem;border:1px solid var(--_comark-border);border-radius:calc(var(--_comark-radius) + 2px);background:var(--_comark-background);color:var(--_comark-muted);font:400 .875rem/1.375rem var(--_comark-font);transition:background-color .15s,border-color .15s}.ccr[data-color='secondary']{--ccr:var(--ccr-secondary)}.ccr[data-color='success']{--ccr:var(--ccr-success)}.ccr[data-color='info']{--ccr:var(--ccr-info)}.ccr[data-color='warning']{--ccr:var(--ccr-warning)}.ccr[data-color='error']{--ccr:var(--ccr-error)}.ccr[data-color='neutral']{--ccr:var(--ccr-neutral)}.ccr[data-variant='soft'],.ccr[data-variant='subtle']{background:color-mix(in srgb,var(--ccr) 10%,var(--_comark-background))}.ccr[data-variant='soft']{border-color:transparent}.ccr[data-variant='subtle']{border-color:color-mix(in srgb,var(--ccr) 30%,var(--_comark-background))}.ccr>:last-child{margin-bottom:0}.ccr>.ci{--comark-icon-size:1.25rem;display:block;margin-bottom:.75rem;color:var(--ccr)}.ccr-title{margin:0 0 .25rem;color:var(--_comark-foreground);font-size:.9375rem;font-weight:600;line-height:1.375rem;text-wrap:balance}.ccr-title+*{margin-top:0}.ccr-link{position:absolute;inset:0;z-index:0;border-radius:inherit}.ccr-link:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--_comark-ring) 50%,transparent)}.ccr :is(a,button):not(.ccr-link){position:relative;z-index:1}.ccr:has(>.ccr-link):hover{border-color:color-mix(in srgb,var(--ccr) 50%,var(--_comark-border));background:color-mix(in srgb,var(--_comark-accent) 50%,var(--_comark-background))}.ccr[data-variant='soft']:has(>.ccr-link):hover{border-color:transparent}.ccr[data-variant='subtle']:has(>.ccr-link):hover{border-color:color-mix(in srgb,var(--ccr) 30%,var(--_comark-background))}.ccr[data-variant='soft']:has(>.ccr-link):hover,.ccr[data-variant='subtle']:has(>.ccr-link):hover{background:color-mix(in srgb,var(--ccr) 16%,var(--_comark-background))}.ccr-external{position:absolute;top:1rem;inset-inline-end:1rem;width:1rem;height:1rem;color:var(--_comark-muted)}.ccr:dir(rtl) .ccr-external{scale:-1 1}@media print{.ccr{break-inside:avoid}.ccr-external{display:none}}}
package/dist/vue.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { a as rel, i as isSafeHref, n as external, r as isExternalHref, t as addStyles } from "./client-C5acm8_C.mjs";
2
- import src_default, { colors, variants } from "./index.mjs";
1
+ import { a as rel, i as isSafeHref, n as external, r as isExternalHref, t as addStyles } from "./client-DsO8I6Oo.mjs";
2
+ import { n as src_default, r as variants, t as colors } from "./src-BsKBRfFx.mjs";
3
3
  import { defineComponent, h, onMounted } from "vue";
4
4
  import { Icon } from "comark-icon/vue";
5
5
  //#region src/vue.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "comark-cards",
3
- "version": "0.7.5",
3
+ "version": "0.8.0",
4
4
  "description": "Cards, link cards, and columns in Nuxt UI's syntax, with no script. A Comark plugin.",
5
5
  "keywords": [
6
6
  "card",
@@ -46,7 +46,7 @@
46
46
  "provenance": true
47
47
  },
48
48
  "dependencies": {
49
- "comark-icon": "^0.7.5"
49
+ "comark-icon": "^0.8.0"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@comark/html": "^0.8.1",
@@ -59,7 +59,7 @@
59
59
  "react-dom": "^19.3.1",
60
60
  "svelte": "^5.57.2",
61
61
  "vue": "^3.5.44",
62
- "comark-zoom": "0.7.5"
62
+ "comark-zoom": "0.8.0"
63
63
  },
64
64
  "peerDependencies": {
65
65
  "@comark/html": "^0.8.0",