deckjsx 0.4.0 → 0.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/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { a as isContentNode, c as toLegacyJsxNode, d as Slide, f as Text, i as isAuthorNode, l as Image, m as isAuthorTreeNode, n as createElement, o as isSlideNode, p as View, s as isLegacyAuthorNode, t as Fragment, u as Shape } from "./jsx-Crlbye9V.mjs";
1
+ import { a as isContentNode, c as toLegacyJsxNode, d as Shape, f as Slide, h as isAuthorTreeNode, i as isAuthorNode, l as isAuthoredTag, m as View, n as createElement, o as isSlideNode, p as Text, s as isLegacyAuthorNode, t as Fragment, u as Image } from "./jsx-B9HB9_cS.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import { mkdir, writeFile } from "node:fs/promises";
4
4
  import { dirname } from "node:path";
@@ -4502,7 +4502,7 @@ function assetEntityId(material) {
4502
4502
  //#region src/graph/roles.ts
4503
4503
  function semanticKindForTag(tag) {
4504
4504
  if (tag === "img") return "image";
4505
- if (tag === "p" || tag.startsWith("h") || tag === "span") return tag === "span" ? "textRun" : "text";
4505
+ if (tag === "p" || /^h[1-6]$/.test(tag) || tag === "span") return tag === "span" ? "textRun" : "text";
4506
4506
  return "container";
4507
4507
  }
4508
4508
  function semanticKindForComponent(component) {
@@ -4957,6 +4957,7 @@ function buildSemanticAuthorGraph(roots) {
4957
4957
  }
4958
4958
  //#endregion
4959
4959
  //#region src/style/resolve.ts
4960
+ const EMPTY_CLASS_NAMES = /* @__PURE__ */ new Set();
4960
4961
  function sourceKeyFor(source) {
4961
4962
  return !source || source.kind === "root" ? "root" : source.sourceIdentity;
4962
4963
  }
@@ -4981,11 +4982,10 @@ function targetsFor(definition) {
4981
4982
  function invalidClassNameReason(name) {
4982
4983
  if (name.trim().length === 0) return "Style Class names must not be empty.";
4983
4984
  if (/\s/.test(name)) return "Style Class names must not contain whitespace.";
4984
- if (name.includes("/")) return "Style Class names must not contain /.";
4985
4985
  }
4986
4986
  function styleDiagnostic(input) {
4987
4987
  return diagnostic({
4988
- severity: "error",
4988
+ severity: input.severity ?? "error",
4989
4989
  code: input.code,
4990
4990
  title: input.title,
4991
4991
  message: input.message,
@@ -4999,51 +4999,243 @@ function styleDiagnostic(input) {
4999
4999
  function compareSpecificity(left, right) {
5000
5000
  return left[0] - right[0] || left[1] - right[1] || left[2] - right[2];
5001
5001
  }
5002
- function parseSelector(selector) {
5003
- const value = selector.trim();
5004
- if (value.length === 0 || /[\s>+~#:[\],*]/.test(value)) return;
5002
+ function addSpecificity(left, right) {
5003
+ return [
5004
+ left[0] + right[0],
5005
+ left[1] + right[1],
5006
+ left[2] + right[2]
5007
+ ];
5008
+ }
5009
+ function isHexDigit(value) {
5010
+ return value !== void 0 && /^[0-9a-fA-F]$/.test(value);
5011
+ }
5012
+ function isSelectorDelimiter(value) {
5013
+ return value === void 0 || /[\s.#:[\]>+~*,/]/.test(value);
5014
+ }
5015
+ function decodeEscape(input, start) {
5016
+ const next = input[start + 1];
5017
+ if (next === void 0 || next === "\n" || next === "\r" || next === "\f") return;
5018
+ if (!isHexDigit(next)) return {
5019
+ value: next,
5020
+ next: start + 2
5021
+ };
5022
+ let index = start + 1;
5023
+ let hex = "";
5024
+ while (index < input.length && hex.length < 6 && isHexDigit(input[index])) {
5025
+ hex += input[index];
5026
+ index += 1;
5027
+ }
5028
+ if (/\s/.test(input[index] ?? "")) index += 1;
5029
+ const codePoint = Number.parseInt(hex, 16);
5030
+ if (codePoint === 0 || codePoint > 1114111) return {
5031
+ value: "�",
5032
+ next: index
5033
+ };
5034
+ return {
5035
+ value: String.fromCodePoint(codePoint),
5036
+ next: index
5037
+ };
5038
+ }
5039
+ function parseClassIdentifier(input, start) {
5040
+ let index = start;
5041
+ let value = "";
5042
+ while (index < input.length && !isSelectorDelimiter(input[index])) {
5043
+ if (input[index] === "\\") {
5044
+ const escaped = decodeEscape(input, index);
5045
+ if (!escaped) return;
5046
+ value += escaped.value;
5047
+ index = escaped.next;
5048
+ continue;
5049
+ }
5050
+ value += input[index];
5051
+ index += 1;
5052
+ }
5053
+ return value.length === 0 ? void 0 : {
5054
+ value,
5055
+ next: index
5056
+ };
5057
+ }
5058
+ function cssEscapeIdentifier(value) {
5059
+ let escaped = "";
5060
+ const firstCodePoint = value.codePointAt(0);
5061
+ let position = 0;
5062
+ for (const char of value) {
5063
+ const codePoint = char.codePointAt(0);
5064
+ if (codePoint === void 0) continue;
5065
+ if (codePoint === 0) {
5066
+ escaped += "�";
5067
+ position += 1;
5068
+ continue;
5069
+ }
5070
+ if (codePoint >= 1 && codePoint <= 31 || codePoint === 127 || position === 0 && codePoint >= 48 && codePoint <= 57 || position === 1 && codePoint >= 48 && codePoint <= 57 && firstCodePoint === 45) {
5071
+ escaped += `\\${codePoint.toString(16)} `;
5072
+ position += 1;
5073
+ continue;
5074
+ }
5075
+ if (position === 0 && value === "-" && codePoint === 45) {
5076
+ escaped += `\\${char}`;
5077
+ position += 1;
5078
+ continue;
5079
+ }
5080
+ if (codePoint >= 128 || codePoint === 45 || codePoint === 95 || codePoint >= 48 && codePoint <= 57 || codePoint >= 65 && codePoint <= 90 || codePoint >= 97 && codePoint <= 122) {
5081
+ escaped += char;
5082
+ position += 1;
5083
+ continue;
5084
+ }
5085
+ escaped += `\\${char}`;
5086
+ position += 1;
5087
+ }
5088
+ return escaped;
5089
+ }
5090
+ function parseSelectorPart(value) {
5091
+ if (value.length === 0) return;
5005
5092
  const tag = value.match(/^[a-z][a-z0-9-]*/)?.[0];
5093
+ if (tag !== void 0 && !isAuthoredTag(tag)) return;
5006
5094
  const rest = tag ? value.slice(tag.length) : value;
5007
5095
  if (rest.length === 0) return {
5008
- ...tag ? { tag } : {},
5009
- classes: [],
5096
+ part: {
5097
+ ...tag ? { tag } : {},
5098
+ classes: []
5099
+ },
5010
5100
  specificity: [
5011
5101
  0,
5012
5102
  0,
5013
5103
  tag ? 1 : 0
5014
5104
  ]
5015
5105
  };
5016
- const classMatches = [...rest.matchAll(/\.([_a-zA-Z-][_a-zA-Z0-9-]*)/g)];
5017
- if (classMatches.map((match) => match[0]).join("") !== rest || classMatches.length === 0) return;
5106
+ const classes = [];
5107
+ let index = 0;
5108
+ while (index < rest.length) {
5109
+ if (rest[index] !== ".") return;
5110
+ const parsedClass = parseClassIdentifier(rest, index + 1);
5111
+ if (!parsedClass) return;
5112
+ classes.push(parsedClass.value);
5113
+ index = parsedClass.next;
5114
+ }
5018
5115
  return {
5019
- ...tag ? { tag } : {},
5020
- classes: classMatches.map((match) => match[1]),
5116
+ part: {
5117
+ ...tag ? { tag } : {},
5118
+ classes
5119
+ },
5021
5120
  specificity: [
5022
5121
  0,
5023
- classMatches.length,
5122
+ classes.length,
5024
5123
  tag ? 1 : 0
5025
5124
  ]
5026
5125
  };
5027
5126
  }
5127
+ function parseSelector(selector) {
5128
+ const value = selector.trim();
5129
+ if (value.length === 0) return;
5130
+ const parsedParts = splitSelectorParts(value).map(parseSelectorPart);
5131
+ const parts = [];
5132
+ let specificity = [
5133
+ 0,
5134
+ 0,
5135
+ 0
5136
+ ];
5137
+ for (const parsedPart of parsedParts) {
5138
+ if (parsedPart === void 0) return;
5139
+ parts.push(parsedPart.part);
5140
+ specificity = addSpecificity(specificity, parsedPart.specificity);
5141
+ }
5142
+ if (parts.length === 0) return;
5143
+ return {
5144
+ parts,
5145
+ specificity
5146
+ };
5147
+ }
5148
+ function splitSelectorParts(value) {
5149
+ const parts = [];
5150
+ let current = "";
5151
+ let index = 0;
5152
+ while (index < value.length) {
5153
+ const char = value[index];
5154
+ if (/\s/.test(char)) {
5155
+ if (current.length > 0) {
5156
+ parts.push(current);
5157
+ current = "";
5158
+ }
5159
+ index += 1;
5160
+ continue;
5161
+ }
5162
+ if (char !== "\\") {
5163
+ current += char;
5164
+ index += 1;
5165
+ continue;
5166
+ }
5167
+ const next = value[index + 1];
5168
+ current += char;
5169
+ if (isHexDigit(next)) {
5170
+ let hexIndex = index + 1;
5171
+ let hexLength = 0;
5172
+ while (hexIndex < value.length && hexLength < 6 && isHexDigit(value[hexIndex])) {
5173
+ current += value[hexIndex];
5174
+ hexIndex += 1;
5175
+ hexLength += 1;
5176
+ }
5177
+ if (/\s/.test(value[hexIndex] ?? "")) {
5178
+ current += value[hexIndex];
5179
+ hexIndex += 1;
5180
+ }
5181
+ index = hexIndex;
5182
+ continue;
5183
+ }
5184
+ if (next !== void 0) {
5185
+ current += next;
5186
+ index += 2;
5187
+ continue;
5188
+ }
5189
+ index += 1;
5190
+ }
5191
+ if (current.length > 0) parts.push(current);
5192
+ return parts;
5193
+ }
5028
5194
  function selectorFor(className, target) {
5029
- if (target === void 0) return `.${className}`;
5195
+ if (target === void 0) return `.${cssEscapeIdentifier(className)}`;
5030
5196
  return target;
5031
5197
  }
5032
- function effectiveSpecificity(className, selector) {
5033
- const includesClassName = selector.classes.includes(className);
5034
- return [
5035
- selector.specificity[0],
5036
- selector.specificity[1] + (includesClassName ? 0 : 1),
5037
- selector.specificity[2]
5038
- ];
5198
+ function rightmostSelectorHasClass(selector, className) {
5199
+ return selector.parts.at(-1)?.classes.includes(className) ?? false;
5200
+ }
5201
+ function collectSelectorConditionClassNames(className, selector) {
5202
+ return selector.parts.flatMap((part, index) => index === selector.parts.length - 1 ? part.classes.filter((name) => name !== className) : part.classes);
5203
+ }
5204
+ function classNamesFor(node, context) {
5205
+ return context.classNamesByNodeId.get(node.id) ?? EMPTY_CLASS_NAMES;
5206
+ }
5207
+ function selectorPartMatches(part, node, context) {
5208
+ if (part.tag !== void 0 && node.authoredTag !== part.tag) return false;
5209
+ const activeClassNames = classNamesFor(node, context);
5210
+ return part.classes.every((name) => activeClassNames.has(name));
5211
+ }
5212
+ function ancestorMatches(parts, node, context) {
5213
+ let parentId = context.parentById.get(node.id);
5214
+ for (let index = parts.length - 1; index >= 0; index -= 1) {
5215
+ const part = parts[index];
5216
+ let matched = false;
5217
+ while (parentId !== void 0) {
5218
+ const parent = context.graph.nodes.get(parentId);
5219
+ parentId = context.parentById.get(parentId);
5220
+ if (!parent) continue;
5221
+ if (selectorPartMatches(part, parent, context)) {
5222
+ matched = true;
5223
+ break;
5224
+ }
5225
+ }
5226
+ if (!matched) return false;
5227
+ }
5228
+ return true;
5039
5229
  }
5040
- function selectorMatches(className, selector, node, activeClassNames) {
5230
+ function selectorMatches(className, selector, node, activeClassNames, context) {
5041
5231
  if (!activeClassNames.has(className)) return false;
5042
- if (selector.tag !== void 0 && node.authoredTag !== selector.tag) return false;
5043
- return selector.classes.every((name) => activeClassNames.has(name));
5232
+ const targetPart = selector.parts.at(-1);
5233
+ if (!targetPart || !selectorPartMatches(targetPart, node, context)) return false;
5234
+ return ancestorMatches(selector.parts.slice(0, -1), node, context);
5044
5235
  }
5045
5236
  function registerStylesheets(sourceKey, stylesheets, diagnostics) {
5046
5237
  const classes = /* @__PURE__ */ new Map();
5238
+ const selectorConditionClassNames = /* @__PURE__ */ new Set();
5047
5239
  let ruleIndex = 0;
5048
5240
  stylesheets?.forEach((stylesheet, stylesheetIndex) => {
5049
5241
  Object.entries(stylesheet.classes).forEach(([className, definition]) => {
@@ -5058,26 +5250,66 @@ function registerStylesheets(sourceKey, stylesheets, diagnostics) {
5058
5250
  }));
5059
5251
  return;
5060
5252
  }
5253
+ const targets = targetsFor(definition);
5254
+ const selectorTexts = targets === void 0 ? [selectorFor(className, void 0)] : targets;
5255
+ let hasTargetDiagnostics = false;
5256
+ const selectors = [];
5257
+ selectorTexts.forEach((selectorText) => {
5258
+ const selector = parseSelector(selectorText);
5259
+ if (!selector) {
5260
+ hasTargetDiagnostics = true;
5261
+ diagnostics.push(styleDiagnostic({
5262
+ code: "E_STYLE_UNSUPPORTED_SELECTOR",
5263
+ title: "unsupported stylesheet selector",
5264
+ path,
5265
+ message: `Selector "${selectorText}" is not supported in v0.4.1.`,
5266
+ help: ["Use class, tag, compound tag/class, or descendant selectors such as .title, p.title, or .card .caption."]
5267
+ }));
5268
+ return;
5269
+ }
5270
+ if (!rightmostSelectorHasClass(selector, className)) {
5271
+ hasTargetDiagnostics = true;
5272
+ diagnostics.push(styleDiagnostic({
5273
+ code: "E_STYLE_INVALID_CLASS_TARGET",
5274
+ title: "style class target must include its class selector",
5275
+ path,
5276
+ message: `Style Class "${className}" target must include .${cssEscapeIdentifier(className)} in the rightmost selector.`,
5277
+ help: ["Write the target as a CSS selector such as p.title or .card .title."]
5278
+ }));
5279
+ return;
5280
+ }
5281
+ collectSelectorConditionClassNames(className, selector).forEach((name) => selectorConditionClassNames.add(name));
5282
+ selectors.push({
5283
+ text: selectorText,
5284
+ selector
5285
+ });
5286
+ });
5061
5287
  const list = classes.get(className) ?? [];
5062
5288
  classes.set(className, [...list, {
5063
5289
  className,
5064
5290
  definition,
5065
5291
  stylesheetIndex,
5066
5292
  ruleIndex: ruleIndex++,
5067
- path
5293
+ path,
5294
+ selectors,
5295
+ hasTargetDiagnostics
5068
5296
  }]);
5069
5297
  });
5070
5298
  });
5071
- return classes;
5299
+ return {
5300
+ classes,
5301
+ selectorConditionClassNames
5302
+ };
5072
5303
  }
5073
- function resolveClassMatches(node, entity, registry, diagnostics) {
5304
+ function resolveClassMatches(node, entity, registry, context, diagnostics) {
5074
5305
  const classRefs = entity.authored.classRefs ?? [];
5075
5306
  const activeClassNames = new Set(classRefs.map((ref) => ref.name));
5076
5307
  const matched = [];
5077
5308
  [...activeClassNames].forEach((className) => {
5078
- const registrations = registry.get(className);
5309
+ const registrations = registry.classes.get(className);
5079
5310
  if (!registrations || registrations.length === 0) {
5080
- diagnostics.push(styleDiagnostic({
5311
+ if (!registry.selectorConditionClassNames.has(className)) diagnostics.push(styleDiagnostic({
5312
+ severity: "warning",
5081
5313
  code: "E_STYLE_UNKNOWN_CLASS",
5082
5314
  title: "unknown style class",
5083
5315
  path: node.origin.path,
@@ -5087,33 +5319,20 @@ function resolveClassMatches(node, entity, registry, diagnostics) {
5087
5319
  return;
5088
5320
  }
5089
5321
  const before = matched.length;
5090
- let hasSelectorDiagnostic = false;
5322
+ let hasTargetDiagnostics = false;
5091
5323
  registrations.forEach((registration) => {
5092
- const targets = targetsFor(registration.definition);
5093
- (targets === void 0 ? [void 0] : targets).forEach((target) => {
5094
- const selectorText = selectorFor(className, target);
5095
- const selector = parseSelector(selectorText);
5096
- if (!selector) {
5097
- hasSelectorDiagnostic = true;
5098
- diagnostics.push(styleDiagnostic({
5099
- code: "E_STYLE_UNSUPPORTED_SELECTOR",
5100
- title: "unsupported stylesheet selector",
5101
- path: registration.path,
5102
- message: `Selector "${selectorText}" is not supported in v0.4.0.`,
5103
- help: ["Use a simple class selector such as .title or a compound selector such as p.title."]
5104
- }));
5105
- return;
5106
- }
5107
- if (!selectorMatches(className, selector, node, activeClassNames)) return;
5324
+ hasTargetDiagnostics ||= registration.hasTargetDiagnostics;
5325
+ registration.selectors.forEach(({ selector, text }) => {
5326
+ if (!selectorMatches(className, selector, node, activeClassNames, context)) return;
5108
5327
  matched.push({
5109
5328
  registration,
5110
- selector: selectorText,
5111
- specificity: effectiveSpecificity(className, selector),
5329
+ selector: text,
5330
+ specificity: selector.specificity,
5112
5331
  style: styleObjectFor(registration.definition)
5113
5332
  });
5114
5333
  });
5115
5334
  });
5116
- if (matched.length === before && !hasSelectorDiagnostic) diagnostics.push(styleDiagnostic({
5335
+ if (matched.length === before && !hasTargetDiagnostics) diagnostics.push(styleDiagnostic({
5117
5336
  code: "E_STYLE_TARGET_MISMATCH",
5118
5337
  title: "style class target does not match element",
5119
5338
  path: node.origin.path,
@@ -5133,10 +5352,10 @@ function applyProperties(style, source, properties) {
5133
5352
  };
5134
5353
  });
5135
5354
  }
5136
- function resolvedStyleFor(node, entity, registry, diagnostics) {
5355
+ function resolvedStyleFor(node, entity, registry, context, diagnostics) {
5137
5356
  const properties = {};
5138
5357
  const appliedClasses = [];
5139
- resolveClassMatches(node, entity, registry, diagnostics).forEach((match) => {
5358
+ resolveClassMatches(node, entity, registry, context, diagnostics).forEach((match) => {
5140
5359
  const source = {
5141
5360
  layer: "class",
5142
5361
  className: match.registration.className,
@@ -5161,11 +5380,33 @@ function nodeByStyleRef(graph) {
5161
5380
  });
5162
5381
  return nodes;
5163
5382
  }
5383
+ function parentMapFor(graph) {
5384
+ const parentById = /* @__PURE__ */ new Map();
5385
+ graph.nodes.forEach((node) => {
5386
+ ("children" in node ? node.children : "inlineChildren" in node ? node.inlineChildren : []).forEach((childId) => {
5387
+ parentById.set(childId, node.id);
5388
+ });
5389
+ });
5390
+ return parentById;
5391
+ }
5392
+ function classNamesByNodeIdFor(graph) {
5393
+ const classNamesByNodeId = /* @__PURE__ */ new Map();
5394
+ graph.nodes.forEach((node) => {
5395
+ const classRefs = node.styleRef ? graph.styles.get(node.styleRef)?.authored.classRefs : void 0;
5396
+ classNamesByNodeId.set(node.id, new Set(classRefs?.map((ref) => ref.name) ?? []));
5397
+ });
5398
+ return classNamesByNodeId;
5399
+ }
5164
5400
  function resolveStyles(graph, roots) {
5165
5401
  const diagnostics = [];
5166
5402
  const stylesheets = classesBySource(roots);
5167
5403
  const registries = /* @__PURE__ */ new Map();
5168
5404
  const nodes = nodeByStyleRef(graph);
5405
+ const selectorContext = {
5406
+ graph,
5407
+ parentById: parentMapFor(graph),
5408
+ classNamesByNodeId: classNamesByNodeIdFor(graph)
5409
+ };
5169
5410
  const resolvedStyles = /* @__PURE__ */ new Map();
5170
5411
  stylesheets.forEach((sourceStylesheets, sourceKey) => {
5171
5412
  registries.set(sourceKey, registerStylesheets(sourceKey, sourceStylesheets, diagnostics));
@@ -5179,7 +5420,7 @@ function resolveStyles(graph, roots) {
5179
5420
  registry = registerStylesheets(sourceKey, stylesheets.get(sourceKey), diagnostics);
5180
5421
  registries.set(sourceKey, registry);
5181
5422
  }
5182
- resolvedStyles.set(id, resolvedStyleFor(node, entity, registry, diagnostics));
5423
+ resolvedStyles.set(id, resolvedStyleFor(node, entity, registry, selectorContext, diagnostics));
5183
5424
  });
5184
5425
  return {
5185
5426
  resolvedStyles,
@@ -287,4 +287,4 @@ function isContentNode(value) {
287
287
  return isAuthorNode(value) && value.kind !== "slide";
288
288
  }
289
289
  //#endregion
290
- export { isContentNode as a, toLegacyJsxNode as c, Slide as d, Text as f, isAuthorNode as i, Image as l, isAuthorTreeNode as m, createElement as n, isSlideNode as o, View as p, createElementWithMetadata as r, isLegacyAuthorNode as s, Fragment as t, Shape as u };
290
+ export { isContentNode as a, toLegacyJsxNode as c, Shape as d, Slide as f, isAuthorTreeNode as h, isAuthorNode as i, isAuthoredTag as l, View as m, createElement as n, isSlideNode as o, Text as p, createElementWithMetadata as r, isLegacyAuthorNode as s, Fragment as t, Image as u };
@@ -1,4 +1,4 @@
1
- import { r as createElementWithMetadata, t as Fragment } from "./jsx-Crlbye9V.mjs";
1
+ import { r as createElementWithMetadata, t as Fragment } from "./jsx-B9HB9_cS.mjs";
2
2
  import { jsx, jsxs } from "./jsx-runtime.mjs";
3
3
  //#region src/jsx-dev-runtime.ts
4
4
  function sourceSpanFromDevSource(source) {
@@ -1,4 +1,4 @@
1
- import { r as createElementWithMetadata, t as Fragment } from "./jsx-Crlbye9V.mjs";
1
+ import { r as createElementWithMetadata, t as Fragment } from "./jsx-B9HB9_cS.mjs";
2
2
  //#region src/jsx-runtime.ts
3
3
  function jsx(type, props, key) {
4
4
  return createElementWithMetadata(type, props, key);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deckjsx",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Generate PowerPoint presentations from TSX/JSX through a compiler pipeline.",
5
5
  "license": "MIT",
6
6
  "author": "deckjsx contributors",