@rohal12/spindle 0.36.0 → 0.36.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rohal12/spindle",
3
- "version": "0.36.0",
3
+ "version": "0.36.2",
4
4
  "type": "module",
5
5
  "description": "A Preact-based story format for Twine 2.",
6
6
  "license": "Unlicense",
package/src/markup/ast.ts CHANGED
@@ -152,7 +152,10 @@ export function buildAST(tokens: Token[]): ASTNode[] {
152
152
  }
153
153
 
154
154
  const top = stack[stack.length - 1]!;
155
- if (top.node.type !== 'html' || top.node.tag !== token.tag) {
155
+ if (
156
+ top.node.type !== 'html' ||
157
+ top.node.tag.toLowerCase() !== token.tag.toLowerCase()
158
+ ) {
156
159
  const expected =
157
160
  top.node.type === 'html'
158
161
  ? `</${top.node.tag}>`
@@ -23,6 +23,7 @@ const defaultUpdater: LocalsUpdater = {
23
23
  export const LocalsValuesContext = createContext<Record<string, unknown>>({});
24
24
  export const LocalsUpdateContext = createContext<LocalsUpdater>(defaultUpdater);
25
25
  export const NobrContext = createContext(false);
26
+ export const SvgContext = createContext(false);
26
27
  export const WidgetChildrenContext = createContext<ASTNode[] | null>(null);
27
28
 
28
29
  /**
@@ -85,16 +86,25 @@ function HtmlNodeRenderer({ node }: { node: HtmlNode }) {
85
86
  const resolve = useInterpolate();
86
87
  const nobr = useContext(NobrContext);
87
88
  const locals = useContext(LocalsValuesContext);
89
+ const inSvg = useContext(SvgContext);
88
90
  const attrs: Record<string, unknown> = {};
89
91
  for (const [k, v] of Object.entries(node.attributes)) {
90
92
  attrs[k] = resolve(v) ?? v;
91
93
  }
92
- return h(
93
- node.tag,
94
- attrs,
94
+ const isSvgRoot = node.tag.toLowerCase() === 'svg';
95
+ // Inside SVG, skip markdown processing — markdown wraps content in <p> tags
96
+ // which break the SVG namespace and produce zero-dimension elements.
97
+ const children =
95
98
  node.children.length > 0
96
- ? renderNodes(node.children, { nobr, locals })
97
- : undefined,
99
+ ? inSvg || isSvgRoot
100
+ ? renderInlineNodes(node.children)
101
+ : renderNodes(node.children, { nobr, locals })
102
+ : undefined;
103
+ const element = h(node.tag, attrs, children);
104
+ return isSvgRoot ? (
105
+ <SvgContext.Provider value={true}>{element}</SvgContext.Provider>
106
+ ) : (
107
+ element
98
108
  );
99
109
  }
100
110
 
@@ -53,68 +53,8 @@ export type Token =
53
53
  | VariableToken
54
54
  | HtmlToken;
55
55
 
56
- const HTML_TAGS = new Set([
57
- 'a',
58
- 'article',
59
- 'aside',
60
- 'b',
61
- 'blockquote',
62
- 'br',
63
- 'caption',
64
- 'code',
65
- 'col',
66
- 'colgroup',
67
- 'dd',
68
- 'del',
69
- 'details',
70
- 'dfn',
71
- 'div',
72
- 'dl',
73
- 'dt',
74
- 'em',
75
- 'figcaption',
76
- 'figure',
77
- 'footer',
78
- 'h1',
79
- 'h2',
80
- 'h3',
81
- 'h4',
82
- 'h5',
83
- 'h6',
84
- 'header',
85
- 'hr',
86
- 'i',
87
- 'img',
88
- 'ins',
89
- 'kbd',
90
- 'li',
91
- 'main',
92
- 'mark',
93
- 'nav',
94
- 'ol',
95
- 'p',
96
- 'pre',
97
- 'q',
98
- 's',
99
- 'samp',
100
- 'section',
101
- 'small',
102
- 'span',
103
- 'strong',
104
- 'sub',
105
- 'summary',
106
- 'sup',
107
- 'table',
108
- 'tbody',
109
- 'td',
110
- 'tfoot',
111
- 'th',
112
- 'thead',
113
- 'tr',
114
- 'u',
115
- 'ul',
116
- 'wbr',
117
- ]);
56
+ /** Tag name must start with a letter (covers standard and custom elements). */
57
+ const VALID_TAG_START = /[a-zA-Z]/;
118
58
 
119
59
  const HTML_VOID_TAGS = new Set(['br', 'col', 'hr', 'img', 'wbr']);
120
60
 
@@ -661,13 +601,14 @@ export function tokenize(input: string): Token[] {
661
601
  const isClose = input[j] === '/';
662
602
  if (isClose) j++;
663
603
 
664
- // Read tag name
604
+ // Read tag name (letters, digits, hyphens for custom elements)
665
605
  const tagStart = j;
666
- while (j < input.length && /[a-zA-Z0-9]/.test(input[j]!)) j++;
667
- const tag = input.slice(tagStart, j).toLowerCase();
606
+ while (j < input.length && /[a-zA-Z0-9-]/.test(input[j]!)) j++;
607
+ const tag = input.slice(tagStart, j);
608
+ const tagLower = tag.toLowerCase();
668
609
 
669
- // Only handle known HTML tags
670
- if (tag && HTML_TAGS.has(tag)) {
610
+ // Valid tag name must start with a letter
611
+ if (tag && VALID_TAG_START.test(tag[0]!)) {
671
612
  if (isClose) {
672
613
  // Closing tag: skip whitespace, expect >
673
614
  while (j < input.length && /\s/.test(input[j]!)) j++;
@@ -692,7 +633,7 @@ export function tokenize(input: string): Token[] {
692
633
  const parsed = parseHtmlAttributes(input, j);
693
634
  j = parsed.endIdx;
694
635
 
695
- let isSelfClose = HTML_VOID_TAGS.has(tag);
636
+ let isSelfClose = HTML_VOID_TAGS.has(tagLower);
696
637
  if (input[j] === '/') {
697
638
  isSelfClose = true;
698
639
  j++;