@fusion.dev/helix-core 0.11.0 → 0.12.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.
@@ -4545,6 +4545,14 @@
4545
4545
  "module": "./utils/capture-pointer/capture-pointer.js"
4546
4546
  }
4547
4547
  },
4548
+ {
4549
+ "kind": "js",
4550
+ "name": "closeMarkdown",
4551
+ "declaration": {
4552
+ "name": "closeMarkdown",
4553
+ "module": "./utils/close-markdown/close-markdown.js"
4554
+ }
4555
+ },
4548
4556
  {
4549
4557
  "kind": "js",
4550
4558
  "name": "closestInstance",
@@ -7084,6 +7092,40 @@
7084
7092
  }
7085
7093
  ]
7086
7094
  },
7095
+ {
7096
+ "kind": "javascript-module",
7097
+ "path": "src/utils/close-markdown/close-markdown.ts",
7098
+ "declarations": [
7099
+ {
7100
+ "kind": "function",
7101
+ "name": "closeMarkdown",
7102
+ "return": {
7103
+ "type": {
7104
+ "text": "string"
7105
+ }
7106
+ },
7107
+ "parameters": [
7108
+ {
7109
+ "name": "partial",
7110
+ "type": {
7111
+ "text": "string"
7112
+ }
7113
+ }
7114
+ ],
7115
+ "description": "A prefix of a markdown document, made safe to render as it arrives.\n\n**A streamed answer is a prefix, and a prefix is rarely valid markdown.** The\nmoment a model writes an opening fence, everything after it is code that has\nnot been told it is code: a renderer handed that prefix draws paragraphs,\nthen redraws the whole block when the closing fence lands three seconds\nlater. The same holds for a backtick, a `**`, and a link whose target is\nstill being typed — each renders as punctuation until its partner arrives.\n\nSo this closes what is open. It does not parse markdown and does not render\nit: it returns a string the caller's own renderer can be handed, in which\nevery construct that was opened is also closed.\n\n**What it does not do is remove anything a reader can already read.** The\nexception is a link or image whose brackets are still open, because there is\nno closing that: a target arrives character by character, and `[the docs](ht`\nhas no valid completion that is not a guess at a URL. That fragment is\nwithheld until it is whole, which is the one case where a word appears a beat\nlater than it was received.\n\n**Inside a fence nothing else applies**, which is why closing one returns\nimmediately. The characters in a code block are code: a stray backtick or a\nlone asterisk in a shell command is not an unclosed span, and treating it as\none would append punctuation into somebody's code.\n\n**Underscores are left alone.** `_` opens emphasis in markdown and also sits\ninside `snake_case`, `__init__` and a filename, and a prefix gives no way to\ntell which: closing one turns an identifier into italics for as long as the\nrest of the word takes to arrive, which is worse than the punctuation it was\nmeant to hide. `*` does not have that problem, being punctuation everywhere\nit is not emphasis.\n\nStable across a growing prefix, which is the property that matters: calling\nthis on each frame of a stream cannot make text move that has already\nsettled, because everything it adds is at the end and everything it withholds\nis a fragment that has not been shown yet."
7116
+ }
7117
+ ],
7118
+ "exports": [
7119
+ {
7120
+ "kind": "js",
7121
+ "name": "closeMarkdown",
7122
+ "declaration": {
7123
+ "name": "closeMarkdown",
7124
+ "module": "src/utils/close-markdown/close-markdown.ts"
7125
+ }
7126
+ }
7127
+ ]
7128
+ },
7087
7129
  {
7088
7130
  "kind": "javascript-module",
7089
7131
  "path": "src/utils/closest-instance/closest-instance.ts",
package/dist/index.d.ts CHANGED
@@ -27,6 +27,7 @@ export { type DismissibleElement, MixinDismissible, } from './mixins/mixin-dismi
27
27
  export { MixinField, type MixinFieldInterface } from './mixins/mixin-field/mixin-field.js';
28
28
  export { anchorElement } from './utils/anchor-element/anchor-element.js';
29
29
  export { capturePointer } from './utils/capture-pointer/capture-pointer.js';
30
+ export { closeMarkdown } from './utils/close-markdown/close-markdown.js';
30
31
  export { closestInstance } from './utils/closest-instance/closest-instance.js';
31
32
  export { createSheet } from './utils/create-sheet/create-sheet.js';
32
33
  export { customElement, definedTags, duplicateRegistrations, } from './utils/custom-element/custom-element.js';
package/dist/index.js CHANGED
@@ -27,6 +27,7 @@ export { MixinDismissible, } from './mixins/mixin-dismissible/mixin-dismissible.
27
27
  export { MixinField } from './mixins/mixin-field/mixin-field.js';
28
28
  export { anchorElement } from './utils/anchor-element/anchor-element.js';
29
29
  export { capturePointer } from './utils/capture-pointer/capture-pointer.js';
30
+ export { closeMarkdown } from './utils/close-markdown/close-markdown.js';
30
31
  export { closestInstance } from './utils/closest-instance/closest-instance.js';
31
32
  export { createSheet } from './utils/create-sheet/create-sheet.js';
32
33
  export { customElement, definedTags, duplicateRegistrations, } from './utils/custom-element/custom-element.js';
@@ -0,0 +1,39 @@
1
+ /**
2
+ * A prefix of a markdown document, made safe to render as it arrives.
3
+ *
4
+ * **A streamed answer is a prefix, and a prefix is rarely valid markdown.** The
5
+ * moment a model writes an opening fence, everything after it is code that has
6
+ * not been told it is code: a renderer handed that prefix draws paragraphs,
7
+ * then redraws the whole block when the closing fence lands three seconds
8
+ * later. The same holds for a backtick, a `**`, and a link whose target is
9
+ * still being typed — each renders as punctuation until its partner arrives.
10
+ *
11
+ * So this closes what is open. It does not parse markdown and does not render
12
+ * it: it returns a string the caller's own renderer can be handed, in which
13
+ * every construct that was opened is also closed.
14
+ *
15
+ * **What it does not do is remove anything a reader can already read.** The
16
+ * exception is a link or image whose brackets are still open, because there is
17
+ * no closing that: a target arrives character by character, and `[the docs](ht`
18
+ * has no valid completion that is not a guess at a URL. That fragment is
19
+ * withheld until it is whole, which is the one case where a word appears a beat
20
+ * later than it was received.
21
+ *
22
+ * **Inside a fence nothing else applies**, which is why closing one returns
23
+ * immediately. The characters in a code block are code: a stray backtick or a
24
+ * lone asterisk in a shell command is not an unclosed span, and treating it as
25
+ * one would append punctuation into somebody's code.
26
+ *
27
+ * **Underscores are left alone.** `_` opens emphasis in markdown and also sits
28
+ * inside `snake_case`, `__init__` and a filename, and a prefix gives no way to
29
+ * tell which: closing one turns an identifier into italics for as long as the
30
+ * rest of the word takes to arrive, which is worse than the punctuation it was
31
+ * meant to hide. `*` does not have that problem, being punctuation everywhere
32
+ * it is not emphasis.
33
+ *
34
+ * Stable across a growing prefix, which is the property that matters: calling
35
+ * this on each frame of a stream cannot make text move that has already
36
+ * settled, because everything it adds is at the end and everything it withholds
37
+ * is a fragment that has not been shown yet.
38
+ */
39
+ export declare const closeMarkdown: (partial: string) => string;
@@ -0,0 +1,51 @@
1
+ const FENCE = /^ {0,3}(`{3,}|~{3,})/;
2
+ const OPEN_TARGET = /!?\[[^\]]*\]\([^)]*$/;
3
+ const OPEN_TEXT = /!?\[[^\]]*$/;
4
+ export const closeMarkdown = (partial) => {
5
+ if (partial === '') {
6
+ return partial;
7
+ }
8
+ let owed;
9
+ for (const line of partial.split('\n')) {
10
+ const found = FENCE.exec(line);
11
+ if (!found) {
12
+ continue;
13
+ }
14
+ const mark = found[1];
15
+ if (owed === undefined) {
16
+ owed = mark;
17
+ }
18
+ else if (mark[0] === owed[0] && mark.length >= owed.length) {
19
+ owed = undefined;
20
+ }
21
+ }
22
+ if (owed !== undefined) {
23
+ return `${partial}${partial.endsWith('\n') ? '' : '\n'}${owed}`;
24
+ }
25
+ const cut = partial.lastIndexOf('\n\n');
26
+ const head = cut === -1 ? '' : partial.slice(0, cut + 2);
27
+ let tail = partial.slice(cut === -1 ? 0 : cut + 2);
28
+ const target = OPEN_TARGET.exec(tail);
29
+ if (target) {
30
+ tail = tail.slice(0, target.index);
31
+ }
32
+ else {
33
+ const text = OPEN_TEXT.exec(tail);
34
+ if (text) {
35
+ tail = tail.slice(0, text.index);
36
+ }
37
+ }
38
+ const ticks = (tail.match(/`/g) ?? []).length;
39
+ if (ticks % 2 === 1) {
40
+ tail += '`';
41
+ }
42
+ const strong = (tail.match(/\*\*/g) ?? []).length;
43
+ if (strong % 2 === 1) {
44
+ tail += '**';
45
+ }
46
+ const single = (tail.match(/(?<!\*)\*(?!\*)/g) ?? []).length;
47
+ if (single % 2 === 1) {
48
+ tail += '*';
49
+ }
50
+ return head + tail;
51
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fusion.dev/helix-core",
3
3
  "description": "Helix element primitives: base classes, mixins, decorators, utilities.",
4
- "version": "0.11.0",
4
+ "version": "0.12.0",
5
5
  "license": "MIT",
6
6
  "author": "Ross Sheppard <ross.sheppard@fusion.dev>",
7
7
  "repository": {
@@ -34,12 +34,12 @@
34
34
  "bugs": "https://github.com/project-fusion/fusion/issues",
35
35
  "dependencies": {
36
36
  "@floating-ui/dom": "^1.8.0",
37
- "@fusion.dev/helix-icons": "0.11.0"
37
+ "@fusion.dev/helix-icons": "0.12.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@custom-elements-manifest/analyzer": "^0.11.0",
41
- "@fusion.dev/helix-cem": "0.11.0",
42
- "@fusion.dev/helix-testing": "0.11.0",
41
+ "@fusion.dev/helix-cem": "0.12.0",
42
+ "@fusion.dev/helix-testing": "0.12.0",
43
43
  "lit": "^3.2.1"
44
44
  },
45
45
  "homepage": "https://fusion.dev",