@hirokisakabe/pom 7.1.0 → 7.2.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
@@ -38,6 +38,7 @@
38
38
  - **AI Friendly** — Simple XML structure designed for LLM code generation. Include [llm.txt](./website/public/llm.txt) in your system prompt for XML reference. Also available at `https://pom.pptx.app/llm.txt`.
39
39
  - **Declarative** — Describe slides as XML. No imperative API calls needed — just data in, PPTX out.
40
40
  - **Flexible Layout** — Flexbox-style layout with VStack / HStack, powered by yoga-layout.
41
+ - **Shorthand + Dot Notation** — Layout/style attributes (e.g. `padding`, `margin`, `border`, `fill`, `shadow`) can mix shorthand and dot notation on the same node. Shorthand sets defaults and dot notation overrides specific keys.
41
42
  - **Rich Nodes** — 18 built-in node types: charts, flowcharts, tables, timelines, org trees, and more.
42
43
  - **Schema-validated** — XML input is validated with Zod schemas at runtime with clear error messages.
43
44
  - **PowerPoint Native** — Generates real editable PowerPoint shapes — not images. Recipients can modify everything.
@@ -30,6 +30,24 @@ export declare function getObjectShapeFromRule(rule: CoercionRule): Record<strin
30
30
  * endArrow="true" と endArrow.type="triangle" の共存を許可するために使用。
31
31
  */
32
32
  export declare function isBooleanObjectUnionRule(rule: CoercionRule): boolean;
33
+ type ResolvedMixedNotationShorthand = {
34
+ mode: "merge";
35
+ value: Record<string, unknown>;
36
+ } | {
37
+ mode: "ignore";
38
+ } | {
39
+ mode: "conflict";
40
+ };
41
+ /**
42
+ * 同一属性で shorthand と dot notation を併用したときに、
43
+ * shorthand 側をどのように扱うかを解決する。
44
+ *
45
+ * - merge: shorthand をオブジェクト化して dot notation 側で上書き
46
+ * - ignore: boolean shorthand を無視して dot notation を優先
47
+ * - conflict: 併用不可(従来どおりエラー)
48
+ */
49
+ export declare function resolveMixedNotationShorthand(value: string, rule: CoercionRule): ResolvedMixedNotationShorthand;
33
50
  export declare const NODE_COERCION_MAP: Record<string, Record<string, CoercionRule>>;
34
51
  export declare const CHILD_ELEMENT_COERCION_MAP: Record<string, Record<string, CoercionRule>>;
52
+ export {};
35
53
  //# sourceMappingURL=coercionRules.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"coercionRules.d.ts","sourceRoot":"","sources":["../../src/parseXml/coercionRules.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,MAAM,MAAM,YAAY,GACpB,QAAQ,GACR,SAAS,GACT,QAAQ,GACR,MAAM,GACN;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;CAAE,GACrD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,YAAY,EAAE,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;CAAE,CAAC;AAI5D,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,YAAY,GACjB;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAyD1C;AAED,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,YAAY,EAAE,GACtB,OAAO,CAuCT;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAarD;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,YAAY,GACjB,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,GAAG,SAAS,CAY1C;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAQpE;AA8HD,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CA2I1E,CAAC;AAGF,eAAO,MAAM,0BAA0B,EAAE,MAAM,CAC7C,MAAM,EACN,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAgF7B,CAAC"}
1
+ {"version":3,"file":"coercionRules.d.ts","sourceRoot":"","sources":["../../src/parseXml/coercionRules.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,MAAM,MAAM,YAAY,GACpB,QAAQ,GACR,SAAS,GACT,QAAQ,GACR,MAAM,GACN;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;CAAE,GACrD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,YAAY,EAAE,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;CAAE,CAAC;AAI5D,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,YAAY,GACjB;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAyD1C;AAED,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,YAAY,EAAE,GACtB,OAAO,CAuCT;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAarD;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,YAAY,GACjB,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,GAAG,SAAS,CAY1C;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAQpE;AAED,KAAK,8BAA8B,GAC/B;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GACjD;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC;AAiBzB;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,YAAY,GACjB,8BAA8B,CA+BhC;AA8HD,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CA2I1E,CAAC;AAGF,eAAO,MAAM,0BAA0B,EAAE,MAAM,CAC7C,MAAM,EACN,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAgF7B,CAAC"}
@@ -143,6 +143,52 @@ export function isBooleanObjectUnionRule(rule) {
143
143
  const hasObject = rule.options.some((opt) => typeof opt === "object" && opt.type === "object");
144
144
  return hasBoolean && hasObject;
145
145
  }
146
+ function isPlainObject(value) {
147
+ return typeof value === "object" && value !== null && !Array.isArray(value);
148
+ }
149
+ function isDirectionalBoxShape(shape) {
150
+ const keys = Object.keys(shape).sort();
151
+ return (keys.length === 4 &&
152
+ keys[0] === "bottom" &&
153
+ keys[1] === "left" &&
154
+ keys[2] === "right" &&
155
+ keys[3] === "top");
156
+ }
157
+ /**
158
+ * 同一属性で shorthand と dot notation を併用したときに、
159
+ * shorthand 側をどのように扱うかを解決する。
160
+ *
161
+ * - merge: shorthand をオブジェクト化して dot notation 側で上書き
162
+ * - ignore: boolean shorthand を無視して dot notation を優先
163
+ * - conflict: 併用不可(従来どおりエラー)
164
+ */
165
+ export function resolveMixedNotationShorthand(value, rule) {
166
+ const objectShape = getObjectShapeFromRule(rule);
167
+ if (!objectShape)
168
+ return { mode: "conflict" };
169
+ if (isBooleanObjectUnionRule(rule) &&
170
+ (value === "true" || value === "false")) {
171
+ return { mode: "ignore" };
172
+ }
173
+ const coerced = coerceWithRule(value, rule);
174
+ if (coerced.error !== null)
175
+ return { mode: "conflict" };
176
+ if (isPlainObject(coerced.value)) {
177
+ return { mode: "merge", value: coerced.value };
178
+ }
179
+ if (typeof coerced.value === "number" && isDirectionalBoxShape(objectShape)) {
180
+ return {
181
+ mode: "merge",
182
+ value: {
183
+ top: coerced.value,
184
+ right: coerced.value,
185
+ bottom: coerced.value,
186
+ left: coerced.value,
187
+ },
188
+ };
189
+ }
190
+ return { mode: "conflict" };
191
+ }
146
192
  // ===== 共通変換ルール =====
147
193
  const LENGTH_RULE = {
148
194
  type: "union",
@@ -1 +1 @@
1
- {"version":3,"file":"parseXml.d.ts","sourceRoot":"","sources":["../../src/parseXml/parseXml.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,OAAO,EAgBb,MAAM,aAAa,CAAC;AAYrB,qBAAa,aAAc,SAAQ,KAAK;IACtC,SAAgB,MAAM,EAAE,MAAM,EAAE,CAAC;gBACrB,MAAM,EAAE,MAAM,EAAE;CAM7B;AAGD,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAmB9C,CAAC;AA2oCF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,EAAE,CAiCrD"}
1
+ {"version":3,"file":"parseXml.d.ts","sourceRoot":"","sources":["../../src/parseXml/parseXml.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,OAAO,EAgBb,MAAM,aAAa,CAAC;AAYrB,qBAAa,aAAc,SAAQ,KAAK;IACtC,SAAgB,MAAM,EAAE,MAAM,EAAE,CAAC;gBACrB,MAAM,EAAE,MAAM,EAAE;CAM7B;AAGD,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAmB9C,CAAC;AAgpCF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,EAAE,CAiCrD"}
@@ -1,6 +1,6 @@
1
1
  import { XMLBuilder, XMLParser } from "fast-xml-parser";
2
2
  import { textNodeSchema, ulNodeSchema, olNodeSchema, imageNodeSchema, tableNodeSchema, shapeNodeSchema, chartNodeSchema, timelineNodeSchema, matrixNodeSchema, treeNodeSchema, flowNodeSchema, processArrowNodeSchema, pyramidNodeSchema, lineNodeSchema, iconNodeSchema, } from "../types.js";
3
- import { NODE_COERCION_MAP, CHILD_ELEMENT_COERCION_MAP, coerceWithRule, coerceFallback, getObjectShapeFromRule, isBooleanObjectUnionRule, } from "./coercionRules.js";
3
+ import { NODE_COERCION_MAP, CHILD_ELEMENT_COERCION_MAP, coerceWithRule, coerceFallback, getObjectShapeFromRule, resolveMixedNotationShorthand, } from "./coercionRules.js";
4
4
  // ===== ParseXmlError =====
5
5
  export class ParseXmlError extends Error {
6
6
  errors;
@@ -381,14 +381,18 @@ function coerceChildAttrs(parentTagName, tagName, attrs, errors) {
381
381
  // Process regular attributes
382
382
  for (const [key, value] of Object.entries(regularAttrs)) {
383
383
  if (key in dotGroups) {
384
- // When the rule is a union of boolean and object,
385
- // allow boolean shorthand to coexist with dot-notation by ignoring the boolean value.
386
- if (rules &&
387
- rules[key] &&
388
- isBooleanObjectUnionRule(rules[key]) &&
389
- (value === "true" || value === "false")) {
390
- // Silently skip the boolean value; dot-notation takes priority
391
- continue;
384
+ if (rules && rules[key]) {
385
+ const resolved = resolveMixedNotationShorthand(value, rules[key]);
386
+ if (resolved.mode === "ignore") {
387
+ continue;
388
+ }
389
+ if (resolved.mode === "merge") {
390
+ result[key] = {
391
+ ...resolved.value,
392
+ ...result[key],
393
+ };
394
+ continue;
395
+ }
392
396
  }
393
397
  errors.push(`<${parentTagName}>.<${tagName}>: Attribute "${key}" conflicts with dot-notation attributes. Use one or the other, not both`);
394
398
  continue;
@@ -765,14 +769,19 @@ function convertPomNode(nodeType, tagName, attrs, childElements, textContent, er
765
769
  continue;
766
770
  // Conflict check: dot-notation and regular attribute for the same key
767
771
  if (key in dotGroups) {
768
- // When the rule is a union of boolean and object (e.g., endArrow),
769
- // allow boolean shorthand to coexist with dot-notation by ignoring the boolean value.
770
772
  const ruleForConflict = getCoercionRule(nodeType, key);
771
- if (ruleForConflict &&
772
- isBooleanObjectUnionRule(ruleForConflict) &&
773
- (value === "true" || value === "false")) {
774
- // Silently skip the boolean value; dot-notation takes priority
775
- continue;
773
+ if (ruleForConflict) {
774
+ const resolved = resolveMixedNotationShorthand(value, ruleForConflict);
775
+ if (resolved.mode === "ignore") {
776
+ continue;
777
+ }
778
+ if (resolved.mode === "merge") {
779
+ result[key] = {
780
+ ...resolved.value,
781
+ ...result[key],
782
+ };
783
+ continue;
784
+ }
776
785
  }
777
786
  errors.push(`<${tagName}>: Attribute "${key}" conflicts with dot-notation attributes (e.g., "${key}.xxx"). Use one or the other, not both`);
778
787
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hirokisakabe/pom",
3
- "version": "7.1.0",
3
+ "version": "7.2.0",
4
4
  "description": "AI-friendly PowerPoint generation with a Flexbox layout engine.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -46,17 +46,17 @@
46
46
  },
47
47
  "devDependencies": {
48
48
  "@size-limit/file": "^12.0.1",
49
- "@types/node": "^25.5.0",
49
+ "@types/node": "^25.5.2",
50
50
  "@types/opentype.js": "^1.3.8",
51
51
  "@types/pngjs": "6.0.5",
52
- "@vitest/coverage-v8": "^4.1.0",
53
- "@vitest/ui": "^4.1.0",
54
- "lucide-static": "^0.577.0",
52
+ "@vitest/coverage-v8": "^4.1.2",
53
+ "@vitest/ui": "^4.1.2",
54
+ "lucide-static": "^1.7.0",
55
55
  "pixelmatch": "7.1.0",
56
56
  "pngjs": "7.0.0",
57
57
  "size-limit": "^12.0.1",
58
58
  "tsx": "4.21.0",
59
- "typescript-eslint": "^8.47.0"
59
+ "typescript-eslint": "^8.58.0"
60
60
  },
61
61
  "dependencies": {
62
62
  "@resvg/resvg-wasm": "^2.6.2",