@estiva-app/ui 0.72.0-widgets2.2 → 0.73.0-inline-text.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.
Files changed (55) hide show
  1. package/dist/CommentHighlights.d.ts +100 -0
  2. package/dist/CommentHighlights.d.ts.map +1 -0
  3. package/dist/ComposeBox.d.ts +55 -0
  4. package/dist/ComposeBox.d.ts.map +1 -0
  5. package/dist/ComposerTriggers.d.ts.map +1 -1
  6. package/dist/InlineText.d.ts +56 -0
  7. package/dist/InlineText.d.ts.map +1 -0
  8. package/dist/SelectionToolbar.d.ts +17 -1
  9. package/dist/SelectionToolbar.d.ts.map +1 -1
  10. package/dist/editor.d.ts +4 -1
  11. package/dist/editor.d.ts.map +1 -1
  12. package/dist/editor.js +748 -240
  13. package/dist/editor.js.map +4 -4
  14. package/dist/index.js.map +1 -1
  15. package/dist/looks.d.ts +6 -0
  16. package/dist/looks.d.ts.map +1 -1
  17. package/package.json +1 -5
  18. package/registry.json +403 -3
  19. package/src/CommentHighlights.mdx +61 -0
  20. package/src/CommentHighlights.stories.tsx +72 -0
  21. package/src/CommentHighlights.test.ts +100 -0
  22. package/src/CommentHighlights.ts +413 -0
  23. package/src/ComposeBox.mdx +58 -0
  24. package/src/ComposeBox.stories.tsx +81 -0
  25. package/src/ComposeBox.tsx +183 -0
  26. package/src/ComposerTriggers.tsx +9 -3
  27. package/src/InlineText.mdx +64 -0
  28. package/src/InlineText.stories.tsx +86 -0
  29. package/src/InlineText.test.tsx +187 -0
  30. package/src/InlineText.tsx +187 -0
  31. package/src/SelectionToolbar.actions.test.tsx +44 -0
  32. package/src/SelectionToolbar.mdx +1 -0
  33. package/src/SelectionToolbar.tsx +36 -2
  34. package/src/editor.ts +16 -1
  35. package/src/looks.ts +8 -0
  36. package/src/registry/app.test.ts +7 -1
  37. package/src/registry/registry.test.ts +6 -6
  38. package/stories/Choosing.mdx +3 -0
  39. package/dist/widgets-2/fixtures.d.ts +0 -102
  40. package/dist/widgets-2/fixtures.d.ts.map +0 -1
  41. package/dist/widgets-2/places.d.ts +0 -105
  42. package/dist/widgets-2/places.d.ts.map +0 -1
  43. package/dist/widgets-2/proposed.d.ts +0 -130
  44. package/dist/widgets-2/proposed.d.ts.map +0 -1
  45. package/dist/widgets-2.d.ts +0 -12
  46. package/dist/widgets-2.d.ts.map +0 -1
  47. package/dist/widgets-2.js +0 -585
  48. package/dist/widgets-2.js.map +0 -7
  49. package/src/widgets-2/fixtures.ts +0 -161
  50. package/src/widgets-2/places.tsx +0 -316
  51. package/src/widgets-2/proposed.tsx +0 -596
  52. package/src/widgets-2.ts +0 -11
  53. package/stories/widgets-2/ReadMe.mdx +0 -60
  54. package/stories/widgets-2/Widgets.stories.tsx +0 -504
  55. package/stories/widgets-2/today.tsx +0 -352
package/dist/looks.d.ts CHANGED
@@ -51,4 +51,10 @@ export declare const MENU_TRIGGER_CLASSES = "cursor-pointer rounded-md transitio
51
51
  * prefers-reduced-motion. CollapsibleSection's rows, and the rows under a NavItem.
52
52
  */
53
53
  export declare const COLLAPSIBLE_PANEL_CLASSES = "h-[var(--collapsible-panel-height)] overflow-hidden transition-[height] duration-150 ease-out motion-reduce:transition-none data-[starting-style]:h-0 data-[ending-style]:h-0";
54
+ /**
55
+ * The box a message or a comment is written in: Peek's writing box (its
56
+ * `looks.ts`, Katerina 24 September), moved with ComposeBox. Inset, a hairline,
57
+ * 12px in, 16px between the text and the row under it; the focus ring in Signal.
58
+ */
59
+ export declare const WRITING_BOX_CLASSES = "relative bg-bg-inset border border-border-default rounded-lg p-3 flex flex-col gap-4 signal:transition-shadow signal:focus-within:border-border-focus signal:focus-within:shadow-focus-ring";
54
60
  //# sourceMappingURL=looks.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"looks.d.ts","sourceRoot":"","sources":["../src/looks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;;GAEG;AACH,eAAO,MAAM,iBAAiB,2MAA2M,CAAA;AAEzO,wFAAwF;AACxF,eAAO,MAAM,kBAAkB,kDAAkD,CAAA;AAEjF,mEAAmE;AACnE,eAAO,MAAM,sBAAsB,iKAAiK,CAAA;AAEpM,wEAAwE;AACxE,eAAO,MAAM,wBAAwB,4DAA4D,CAAA;AAEjG,kJAAkJ;AAClJ,eAAO,MAAM,kBAAkB;;;CAGrB,CAAA;AAEV,qGAAqG;AACrG,eAAO,MAAM,mBAAmB,uJAAuJ,CAAA;AAEvL,mEAAmE;AACnE,eAAO,MAAM,wBAAwB,qEAAqE,CAAA;AAE1G,iGAAiG;AACjG,eAAO,MAAM,kBAAkB,wDAAwD,CAAA;AAEvF,yDAAyD;AACzD,eAAO,MAAM,iBAAiB,0FAA0F,CAAA;AAExH,qHAAqH;AACrH,eAAO,MAAM,oBAAoB,wEAAwE,CAAA;AAEzG,4FAA4F;AAC5F,eAAO,MAAM,kBAAkB,8EAA8E,CAAA;AAE7G,4IAA4I;AAE5I,eAAO,MAAM,mBAAmB,iCAAiC,CAAA;AACjE,eAAO,MAAM,wBAAwB,gDAAgD,CAAA;AAErF,2FAA2F;AAC3F,eAAO,MAAM,qBAAqB,kEAAkE,CAAA;AAEpG,2IAA2I;AAC3I,eAAO,MAAM,oBAAoB,gGAAgG,CAAA;AAEjI;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,kLAAkL,CAAA"}
1
+ {"version":3,"file":"looks.d.ts","sourceRoot":"","sources":["../src/looks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;;GAEG;AACH,eAAO,MAAM,iBAAiB,2MAA2M,CAAA;AAEzO,wFAAwF;AACxF,eAAO,MAAM,kBAAkB,kDAAkD,CAAA;AAEjF,mEAAmE;AACnE,eAAO,MAAM,sBAAsB,iKAAiK,CAAA;AAEpM,wEAAwE;AACxE,eAAO,MAAM,wBAAwB,4DAA4D,CAAA;AAEjG,kJAAkJ;AAClJ,eAAO,MAAM,kBAAkB;;;CAGrB,CAAA;AAEV,qGAAqG;AACrG,eAAO,MAAM,mBAAmB,uJAAuJ,CAAA;AAEvL,mEAAmE;AACnE,eAAO,MAAM,wBAAwB,qEAAqE,CAAA;AAE1G,iGAAiG;AACjG,eAAO,MAAM,kBAAkB,wDAAwD,CAAA;AAEvF,yDAAyD;AACzD,eAAO,MAAM,iBAAiB,0FAA0F,CAAA;AAExH,qHAAqH;AACrH,eAAO,MAAM,oBAAoB,wEAAwE,CAAA;AAEzG,4FAA4F;AAC5F,eAAO,MAAM,kBAAkB,8EAA8E,CAAA;AAE7G,4IAA4I;AAE5I,eAAO,MAAM,mBAAmB,iCAAiC,CAAA;AACjE,eAAO,MAAM,wBAAwB,gDAAgD,CAAA;AAErF,2FAA2F;AAC3F,eAAO,MAAM,qBAAqB,kEAAkE,CAAA;AAEpG,2IAA2I;AAC3I,eAAO,MAAM,oBAAoB,gGAAgG,CAAA;AAEjI;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,kLAAkL,CAAA;AAExN;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,gMAC+J,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@estiva-app/ui",
3
- "version": "0.72.0-widgets2.2",
3
+ "version": "0.73.0-inline-text.1",
4
4
  "description": "Estiva's design tokens (the contract) and a small set of primitives (a convenience) for every Estiva app.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -29,10 +29,6 @@
29
29
  "types": "./dist/editor.d.ts",
30
30
  "default": "./dist/editor.js"
31
31
  },
32
- "./widgets-2": {
33
- "types": "./dist/widgets-2.d.ts",
34
- "default": "./dist/widgets-2.js"
35
- },
36
32
  "./gates": {
37
33
  "types": "./dist/gates/index.d.ts",
38
34
  "default": "./dist/gates/index.js"
package/registry.json CHANGED
@@ -4,9 +4,9 @@
4
4
  "kind": "package",
5
5
  "repo": "estiva-ui",
6
6
  "package": "@estiva-app/ui",
7
- "packageVersion": "0.72.0-widgets2.2",
8
- "exports": 166,
9
- "typeExports": 155,
7
+ "packageVersion": "0.73.0-inline-text.1",
8
+ "exports": 178,
9
+ "typeExports": 164,
10
10
  "files": null
11
11
  },
12
12
  "storybook": {
@@ -15,6 +15,25 @@
15
15
  "devUrl": "http://localhost:6008"
16
16
  },
17
17
  "entries": [
18
+ {
19
+ "name": "anchorOfSelection",
20
+ "repo": "estiva-ui",
21
+ "kind": "helper",
22
+ "importPath": "@estiva-app/ui/editor",
23
+ "sourceFile": "src/CommentHighlights.ts",
24
+ "purpose": "The anchor a comment on the selection carries, as §13.6 writes it: the selected blocks, and the selected words in one block or the words where it starts and ends across several.",
25
+ "purposeFrom": "comment",
26
+ "props": [],
27
+ "variants": [],
28
+ "ownsBehaviours": [],
29
+ "status": "stable",
30
+ "migrationStage": null,
31
+ "docsId": "primitives-commenthighlights--docs",
32
+ "storyId": "primitives-commenthighlights--default",
33
+ "docPage": null,
34
+ "app": null,
35
+ "looks": []
36
+ },
18
37
  {
19
38
  "name": "appendTableColumn",
20
39
  "repo": "estiva-ui",
@@ -1832,6 +1851,186 @@
1832
1851
  "app": null,
1833
1852
  "looks": []
1834
1853
  },
1854
+ {
1855
+ "name": "commentElement",
1856
+ "repo": "estiva-ui",
1857
+ "kind": "helper",
1858
+ "importPath": "@estiva-app/ui/editor",
1859
+ "sourceFile": "src/CommentHighlights.ts",
1860
+ "purpose": "What a comment's card stands beside: its drawn highlight, or — its words gone — its first block.",
1861
+ "purposeFrom": "comment",
1862
+ "props": [],
1863
+ "variants": [],
1864
+ "ownsBehaviours": [],
1865
+ "status": "stable",
1866
+ "migrationStage": null,
1867
+ "docsId": "primitives-commenthighlights--docs",
1868
+ "storyId": "primitives-commenthighlights--default",
1869
+ "docPage": null,
1870
+ "app": null,
1871
+ "looks": []
1872
+ },
1873
+ {
1874
+ "name": "CommentHighlights",
1875
+ "repo": "estiva-ui",
1876
+ "kind": "component",
1877
+ "importPath": "@estiva-app/ui/editor",
1878
+ "sourceFile": "src/CommentHighlights.ts",
1879
+ "purpose": "Comments drawn where they are in an editor of a block document: on a few words, on a passage across blocks, or on whole blocks, in a light fill, the open one underlined.",
1880
+ "purposeFrom": "page",
1881
+ "props": [],
1882
+ "variants": [],
1883
+ "ownsBehaviours": [],
1884
+ "status": "stable",
1885
+ "migrationStage": null,
1886
+ "docsId": "primitives-commenthighlights--docs",
1887
+ "storyId": "primitives-commenthighlights--default",
1888
+ "docPage": "src/CommentHighlights.mdx",
1889
+ "app": null,
1890
+ "looks": [
1891
+ "cursor-pointer rounded-sm bg-warning-muted underline decoration-warning-default decoration-2 underline-offset-4"
1892
+ ]
1893
+ },
1894
+ {
1895
+ "name": "commentPlace",
1896
+ "repo": "estiva-ui",
1897
+ "kind": "helper",
1898
+ "importPath": "@estiva-app/ui/editor",
1899
+ "sourceFile": "src/CommentHighlights.ts",
1900
+ "purpose": "Where a comment is now: on its words or blocks, beside its blocks with its words gone, or detached.",
1901
+ "purposeFrom": "comment",
1902
+ "props": [],
1903
+ "variants": [],
1904
+ "ownsBehaviours": [],
1905
+ "status": "stable",
1906
+ "migrationStage": null,
1907
+ "docsId": "primitives-commenthighlights--docs",
1908
+ "storyId": "primitives-commenthighlights--default",
1909
+ "docPage": null,
1910
+ "app": null,
1911
+ "looks": []
1912
+ },
1913
+ {
1914
+ "name": "COMPOSE_EDITOR_CLASSES",
1915
+ "repo": "estiva-ui",
1916
+ "kind": "helper",
1917
+ "importPath": "@estiva-app/ui/editor",
1918
+ "sourceFile": "src/ComposeBox.tsx",
1919
+ "purpose": "The editor's own classes inside the box: Peek's, rich text with no outline.",
1920
+ "purposeFrom": "comment",
1921
+ "props": [],
1922
+ "variants": [],
1923
+ "ownsBehaviours": [],
1924
+ "status": "stable",
1925
+ "migrationStage": null,
1926
+ "docsId": "primitives-composebox--docs",
1927
+ "storyId": "primitives-composebox--default",
1928
+ "docPage": null,
1929
+ "app": null,
1930
+ "looks": []
1931
+ },
1932
+ {
1933
+ "name": "ComposeBox",
1934
+ "repo": "estiva-ui",
1935
+ "kind": "component",
1936
+ "importPath": "@estiva-app/ui/editor",
1937
+ "sourceFile": "src/ComposeBox.tsx",
1938
+ "purpose": "The box a message, a reply or a comment is written in: the text, the selection strip over it, what is attached, the tools and Send.",
1939
+ "purposeFrom": "page",
1940
+ "props": [
1941
+ {
1942
+ "name": "editor",
1943
+ "takes": "Editor | null",
1944
+ "required": true,
1945
+ "note": "The app's editor, with `ComposeKeys` in its extensions and `COMPOSE_EDITOR_CLASSES` on it."
1946
+ },
1947
+ {
1948
+ "name": "onSend",
1949
+ "takes": "a handler",
1950
+ "required": true,
1951
+ "note": "Send what is written. The box empties afterwards — unless this returns `false`: nothing was sent, and the words stay."
1952
+ },
1953
+ {
1954
+ "name": "placeholder",
1955
+ "takes": "anything",
1956
+ "required": true,
1957
+ "note": "Said in the empty box: words, and a `Kbd` where a key helps (\"type / for commands\")."
1958
+ },
1959
+ {
1960
+ "name": "tools",
1961
+ "takes": "anything",
1962
+ "required": false,
1963
+ "note": "The tools under the text, left: `ToolbarButton`s (attach, highlight)."
1964
+ },
1965
+ {
1966
+ "name": "attachments",
1967
+ "takes": "anything",
1968
+ "required": false,
1969
+ "note": "What is attached, under the text: `AttachmentCard pending`s."
1970
+ },
1971
+ {
1972
+ "name": "ready",
1973
+ "takes": "true/false",
1974
+ "required": false,
1975
+ "note": "Something to send even with no words — a file ready."
1976
+ },
1977
+ {
1978
+ "name": "holding",
1979
+ "takes": "true/false",
1980
+ "required": false,
1981
+ "note": "Sending waits — a file still uploading."
1982
+ },
1983
+ {
1984
+ "name": "marked",
1985
+ "takes": "true/false",
1986
+ "required": false,
1987
+ "note": "The bar down the text's left: an urgent mention or a highlight, as Peek draws it."
1988
+ },
1989
+ {
1990
+ "name": "selection",
1991
+ "takes": "Pick<SelectionToolbarProps, 'marks' | 'link' | 'actions'>",
1992
+ "required": false,
1993
+ "note": "The selection strip's buttons: its marks, Link, and the app's own (`actions`)."
1994
+ },
1995
+ {
1996
+ "name": "className",
1997
+ "takes": "text",
1998
+ "required": false,
1999
+ "note": null
2000
+ }
2001
+ ],
2002
+ "variants": [],
2003
+ "ownsBehaviours": [],
2004
+ "status": "stable",
2005
+ "migrationStage": null,
2006
+ "docsId": "primitives-composebox--docs",
2007
+ "storyId": "primitives-composebox--default",
2008
+ "docPage": "src/ComposeBox.mdx",
2009
+ "app": null,
2010
+ "looks": [
2011
+ "bg-bg-inset border border-border-default rounded-lg signal:transition-shadow signal:focus-within:border-border-focus signal:focus-within:shadow-focus-ring focus-within:border-border-strong transition-colors",
2012
+ "select-none font-mono text-menu font-normal tracking-wider text-text-muted"
2013
+ ]
2014
+ },
2015
+ {
2016
+ "name": "ComposeKeys",
2017
+ "repo": "estiva-ui",
2018
+ "kind": "component",
2019
+ "importPath": "@estiva-app/ui/editor",
2020
+ "sourceFile": "src/ComposeBox.tsx",
2021
+ "purpose": "Enter sends; Shift+Enter is a new line — in a list a new item (or out of the list, on an empty one), in a quote a new line (or out of it, on an empty one).",
2022
+ "purposeFrom": "comment",
2023
+ "props": [],
2024
+ "variants": [],
2025
+ "ownsBehaviours": [],
2026
+ "status": "stable",
2027
+ "migrationStage": null,
2028
+ "docsId": "primitives-composebox--docs",
2029
+ "storyId": "primitives-composebox--default",
2030
+ "docPage": null,
2031
+ "app": null,
2032
+ "looks": []
2033
+ },
1835
2034
  {
1836
2035
  "name": "ConfirmDialog",
1837
2036
  "repo": "estiva-ui",
@@ -3246,6 +3445,100 @@
3246
3445
  "app": null,
3247
3446
  "looks": []
3248
3447
  },
3448
+ {
3449
+ "name": "InlineText",
3450
+ "repo": "estiva-ui",
3451
+ "kind": "component",
3452
+ "importPath": "@estiva-app/ui/editor",
3453
+ "sourceFile": "src/InlineText.tsx",
3454
+ "purpose": "A page's title, or the line under it, edited where it stands, as in a document: the text is the editor, with no box and nothing moving.",
3455
+ "purposeFrom": "page",
3456
+ "props": [
3457
+ {
3458
+ "name": "value",
3459
+ "takes": "text",
3460
+ "required": true,
3461
+ "note": "The saved text. A new value is loaded when you are not in the field and hold nothing unsaved."
3462
+ },
3463
+ {
3464
+ "name": "placeholder",
3465
+ "takes": "text",
3466
+ "required": true,
3467
+ "note": "What it is for, muted, while it is empty: \"Untitled\", \"Add a summary\"."
3468
+ },
3469
+ {
3470
+ "name": "label",
3471
+ "takes": "text",
3472
+ "required": true,
3473
+ "note": "The accessible name: \"Title\", \"Summary\"."
3474
+ },
3475
+ {
3476
+ "name": "onCommit",
3477
+ "takes": "a handler",
3478
+ "required": true,
3479
+ "note": "Save it: resolve `true` when saved; `false` keeps your text in place."
3480
+ },
3481
+ {
3482
+ "name": "onText",
3483
+ "takes": "a handler",
3484
+ "required": false,
3485
+ "note": "The text as it is being written, on focus and every change; `null` when you leave."
3486
+ },
3487
+ {
3488
+ "name": "onEnter",
3489
+ "takes": "a handler",
3490
+ "required": false,
3491
+ "note": "Enter, after the save has started: where the caret goes next."
3492
+ },
3493
+ {
3494
+ "name": "variant",
3495
+ "takes": "title | summary",
3496
+ "required": false,
3497
+ "note": "`title`: the page's heading. `summary`: the line under it, quieter."
3498
+ },
3499
+ {
3500
+ "name": "clearable",
3501
+ "takes": "true/false",
3502
+ "required": false,
3503
+ "note": "Empty is a value (a summary). Otherwise leaving it empty brings the saved one back (a title)."
3504
+ },
3505
+ {
3506
+ "name": "readOnly",
3507
+ "takes": "true/false",
3508
+ "required": false,
3509
+ "note": "Text only, the same size and place: nobody can change it, or the editor is not ready."
3510
+ },
3511
+ {
3512
+ "name": "className",
3513
+ "takes": "text",
3514
+ "required": false,
3515
+ "note": "Placement only."
3516
+ },
3517
+ {
3518
+ "name": "ref",
3519
+ "takes": "Ref<InlineTextHandle>",
3520
+ "required": false,
3521
+ "note": null
3522
+ }
3523
+ ],
3524
+ "variants": [
3525
+ {
3526
+ "prop": "variant",
3527
+ "values": [
3528
+ "title",
3529
+ "summary"
3530
+ ]
3531
+ }
3532
+ ],
3533
+ "ownsBehaviours": [],
3534
+ "status": "stable",
3535
+ "migrationStage": null,
3536
+ "docsId": "primitives-inlinetext--docs",
3537
+ "storyId": "primitives-inlinetext--default",
3538
+ "docPage": "src/InlineText.mdx",
3539
+ "app": null,
3540
+ "looks": []
3541
+ },
3249
3542
  {
3250
3543
  "name": "InputChip",
3251
3544
  "repo": "estiva-ui",
@@ -3465,6 +3758,25 @@
3465
3758
  "border-border-tooltip-key bg-bg-tooltip-key text-text-tooltip-key signal:bg-bg-tooltip-key ship:bg-bg-tooltip-key"
3466
3759
  ]
3467
3760
  },
3761
+ {
3762
+ "name": "keepSelection",
3763
+ "repo": "estiva-ui",
3764
+ "kind": "helper",
3765
+ "importPath": "@estiva-app/ui/editor",
3766
+ "sourceFile": "src/SelectionToolbar.tsx",
3767
+ "purpose": "Draw the selection as it is now, until `releaseSelection`.",
3768
+ "purposeFrom": "comment",
3769
+ "props": [],
3770
+ "variants": [],
3771
+ "ownsBehaviours": [],
3772
+ "status": "stable",
3773
+ "migrationStage": null,
3774
+ "docsId": "primitives-selectiontoolbar--docs",
3775
+ "storyId": "primitives-selectiontoolbar--default",
3776
+ "docPage": null,
3777
+ "app": null,
3778
+ "looks": []
3779
+ },
3468
3780
  {
3469
3781
  "name": "KeptSelection",
3470
3782
  "repo": "estiva-ui",
@@ -5359,6 +5671,50 @@
5359
5671
  "text-menu uppercase tracking-widest text-text-secondary"
5360
5672
  ]
5361
5673
  },
5674
+ {
5675
+ "name": "quoteText",
5676
+ "repo": "estiva-ui",
5677
+ "kind": "helper",
5678
+ "importPath": "@estiva-app/ui/editor",
5679
+ "sourceFile": "src/CommentHighlights.ts",
5680
+ "purpose": "A quote in one line, as a card says it: its start and end words around an ellipsis.",
5681
+ "purposeFrom": "comment",
5682
+ "props": [
5683
+ {
5684
+ "name": "start",
5685
+ "takes": "text",
5686
+ "required": true,
5687
+ "note": "The selection's words in one block, or its first words when it runs across blocks."
5688
+ },
5689
+ {
5690
+ "name": "end",
5691
+ "takes": "text",
5692
+ "required": false,
5693
+ "note": "Its last words, in the run's last block."
5694
+ },
5695
+ {
5696
+ "name": "prefix",
5697
+ "takes": "text",
5698
+ "required": false,
5699
+ "note": "A few characters before `start`, only when the words repeat in their block."
5700
+ },
5701
+ {
5702
+ "name": "suffix",
5703
+ "takes": "text",
5704
+ "required": false,
5705
+ "note": "A few characters after the end, only when the words repeat in their block."
5706
+ }
5707
+ ],
5708
+ "variants": [],
5709
+ "ownsBehaviours": [],
5710
+ "status": "stable",
5711
+ "migrationStage": null,
5712
+ "docsId": "primitives-commenthighlights--docs",
5713
+ "storyId": "primitives-commenthighlights--default",
5714
+ "docPage": null,
5715
+ "app": null,
5716
+ "looks": []
5717
+ },
5362
5718
  {
5363
5719
  "name": "Rail",
5364
5720
  "repo": "estiva-ui",
@@ -5804,6 +6160,25 @@
5804
6160
  "app": null,
5805
6161
  "looks": []
5806
6162
  },
6163
+ {
6164
+ "name": "releaseSelection",
6165
+ "repo": "estiva-ui",
6166
+ "kind": "helper",
6167
+ "importPath": "@estiva-app/ui/editor",
6168
+ "sourceFile": "src/SelectionToolbar.tsx",
6169
+ "purpose": "Stop drawing it. Nothing kept, nothing dispatched.",
6170
+ "purposeFrom": "comment",
6171
+ "props": [],
6172
+ "variants": [],
6173
+ "ownsBehaviours": [],
6174
+ "status": "stable",
6175
+ "migrationStage": null,
6176
+ "docsId": "primitives-selectiontoolbar--docs",
6177
+ "storyId": "primitives-selectiontoolbar--default",
6178
+ "docPage": null,
6179
+ "app": null,
6180
+ "looks": []
6181
+ },
5807
6182
  {
5808
6183
  "name": "RichText",
5809
6184
  "repo": "estiva-ui",
@@ -6403,6 +6778,12 @@
6403
6778
  "takes": "anything",
6404
6779
  "required": false,
6405
6780
  "note": "More of the strip, after the marks: the app's own `ToolbarButton`s for what is selected — a chip's card, say."
6781
+ },
6782
+ {
6783
+ "name": "actions",
6784
+ "takes": "readonly SelectionAction[]",
6785
+ "required": false,
6786
+ "note": "The app's own buttons after the marks, past a separator: \"Comment\"."
6406
6787
  }
6407
6788
  ],
6408
6789
  "variants": [],
@@ -6436,6 +6817,25 @@
6436
6817
  "app": null,
6437
6818
  "looks": []
6438
6819
  },
6820
+ {
6821
+ "name": "setCommentAnchors",
6822
+ "repo": "estiva-ui",
6823
+ "kind": "helper",
6824
+ "importPath": "@estiva-app/ui/editor",
6825
+ "sourceFile": "src/CommentHighlights.ts",
6826
+ "purpose": "Draw these comments, `active` the one being read.",
6827
+ "purposeFrom": "comment",
6828
+ "props": [],
6829
+ "variants": [],
6830
+ "ownsBehaviours": [],
6831
+ "status": "stable",
6832
+ "migrationStage": null,
6833
+ "docsId": "primitives-commenthighlights--docs",
6834
+ "storyId": "primitives-commenthighlights--default",
6835
+ "docPage": null,
6836
+ "app": null,
6837
+ "looks": []
6838
+ },
6439
6839
  {
6440
6840
  "name": "setReferenceCard",
6441
6841
  "repo": "estiva-ui",
@@ -0,0 +1,61 @@
1
+ import { Meta, Canvas } from '@storybook/addon-docs/blocks'
2
+ import * as CommentHighlightsStories from './CommentHighlights.stories'
3
+
4
+ <Meta of={CommentHighlightsStories} />
5
+
6
+ # CommentHighlights
7
+
8
+ Comments drawn where they are in an editor of a block document: on a few words, on a passage across blocks, or on whole blocks, in a light fill, the open one underlined.
9
+
10
+ <Canvas of={CommentHighlightsStories.Default} />
11
+
12
+ ## When
13
+
14
+ - **A comment on words or blocks of a block document.** SPEC §13.6 anchors a comment to a run of blocks; the words where its selection starts and ends travel with it as a quote and are found again in the run's first and last blocks when the document is drawn.
15
+ - `setCommentAnchors(editor, anchors, active)` draws them; `active` is the one being read.
16
+ - The editor's `commentSelect` event says which highlight was clicked. Where two overlap, the narrower one is.
17
+ - `commentPlace` says where a comment is now: `resolved` (drawn, on its words or on its whole blocks), `reworded` (its blocks are there, its words are not: nothing is drawn) or `detached` (its blocks are gone). A reworded comment's words are shown as what its writer quoted, never as what the page says; neither state is ever shown as a comment on the whole document.
18
+ - `commentElement` is what its card stands beside: the highlight, or the first block of a reworded comment.
19
+ - `anchorOfSelection` is what a comment on the selection carries: the selected blocks, and their words unless whole blocks are selected. `quoteText` puts a quote in one line.
20
+
21
+ <Canvas of={CommentHighlightsStories.NoneOpen} />
22
+
23
+ ## When not
24
+
25
+ - Showing a selection while a field has the focus → **SelectionToolbar**'s kept selection.
26
+
27
+ No alternative: a comment on the whole document has no place to draw. Say where it belongs in words.
28
+
29
+ ## How
30
+
31
+ ```tsx
32
+ import { useEffect } from 'react'
33
+ import { useEditor } from '@tiptap/react'
34
+ import StarterKit from '@tiptap/starter-kit'
35
+ import { BlockId, CommentHighlights, setCommentAnchors } from '@estiva-app/ui/editor'
36
+
37
+ const editor = useEditor({ extensions: [StarterKit, BlockId, CommentHighlights] })
38
+
39
+ // threads: [{ id, blocks: ['b2', 'b3'], quote: { start: 'first words', end: 'last words' } }, …]
40
+ useEffect(() => {
41
+ if (!editor) return
42
+ setCommentAnchors(editor, threads, openId)
43
+ const select = ({ id }: { id: string }) => open(id)
44
+ editor.on('commentSelect', select)
45
+ return () => { editor.off('commentSelect', select) }
46
+ }, [editor, threads, openId])
47
+ ```
48
+
49
+ - **Nothing is written into the document.** The highlight is a decoration: a save never carries it.
50
+ - `BlockId` must be in the editor: the anchors find their blocks by their ids.
51
+ - Words are matched exactly, never trimmed or guessed. Words that repeat in their block are told apart by the few characters around them; without those, the first is drawn.
52
+ - `commentPlace` reads the document as it is: call it again after an edit.
53
+
54
+ ## What it owns
55
+
56
+ | It does | So you never write |
57
+ |---|---|
58
+ | Finds the run and its words again, after every edit | position arithmetic |
59
+ | Draws words inline and whole blocks whole, the open one underlined | a mark in the document |
60
+ | Tells drawn, reworded and detached apart | a "not found" guess |
61
+ | Says which highlight was clicked | a click handler on the text |
@@ -0,0 +1,72 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { useEffect, useState } from 'react'
3
+ import { EditorContent, useEditor } from '@tiptap/react'
4
+ import StarterKit from '@tiptap/starter-kit'
5
+ import { BlockId } from './BlockNodes'
6
+ import { CommentHighlights, commentPlace, setCommentAnchors, type CommentAnchor } from './CommentHighlights'
7
+ import { richTextClassName } from './RichText'
8
+
9
+ /**
10
+ * Comments drawn where they are. Click a highlight: it becomes the open one.
11
+ * Type over highlighted words and the line under the text says where each
12
+ * comment is now.
13
+ */
14
+ const meta = {
15
+ title: 'Primitives/CommentHighlights',
16
+ parameters: { layout: 'padded' },
17
+ } satisfies Meta
18
+
19
+ export default meta
20
+ type Story = StoryObj
21
+
22
+ const DOCUMENT = {
23
+ type: 'doc',
24
+ content: [
25
+ { type: 'heading', attrs: { level: 1, blockId: 'b1' }, content: [{ type: 'text', text: 'Heading' }] },
26
+ { type: 'paragraph', attrs: { blockId: 'b2' }, content: [{ type: 'text', text: 'A highlight is drawn on the words a comment quotes, found again in its block.' }] },
27
+ { type: 'paragraph', attrs: { blockId: 'b3' }, content: [{ type: 'text', text: 'A selection across blocks keeps the words where it starts and where it ends.' }] },
28
+ { type: 'paragraph', attrs: { blockId: 'b4' }, content: [{ type: 'text', text: 'A comment on whole blocks keeps no words, and is drawn on every block of its run.' }] },
29
+ { type: 'paragraph', attrs: { blockId: 'b5' }, content: [{ type: 'text', text: 'Words that changed are not drawn.' }] },
30
+ ],
31
+ }
32
+
33
+ const ANCHORS: CommentAnchor[] = [
34
+ { id: 'one', blocks: ['b2'], quote: { start: 'the words a comment quotes' } },
35
+ { id: 'two', blocks: ['b2', 'b3'], quote: { start: 'found again in its block.', end: 'A selection across blocks' } },
36
+ { id: 'three', blocks: ['b4'] },
37
+ { id: 'four', blocks: ['b5'], quote: { start: 'Items that changed' } },
38
+ { id: 'five', blocks: ['gone'], quote: { start: 'Label' } },
39
+ ]
40
+
41
+ function Highlighted({ initial }: { initial: string | null }) {
42
+ const [active, setActive] = useState<string | null>(initial)
43
+ const [, setVersion] = useState(0)
44
+ const editor = useEditor({
45
+ extensions: [StarterKit, BlockId, CommentHighlights],
46
+ content: DOCUMENT,
47
+ editorProps: { attributes: { class: richTextClassName('default', 'outline-none'), 'aria-label': 'Text' } },
48
+ onUpdate: () => setVersion((v) => v + 1),
49
+ })
50
+ useEffect(() => {
51
+ if (!editor) return
52
+ setCommentAnchors(editor, ANCHORS, active)
53
+ const select = ({ id }: { id: string }) => setActive(id)
54
+ editor.on('commentSelect', select)
55
+ return () => {
56
+ editor.off('commentSelect', select)
57
+ }
58
+ }, [editor, active])
59
+ const states = editor ? ANCHORS.map((a) => `${a.id}: ${commentPlace(editor, a).state}`).join(' · ') : ''
60
+ return (
61
+ <div className="flex max-w-[560px] flex-col gap-3">
62
+ <EditorContent editor={editor} />
63
+ <p className="text-caption text-text-muted">{`Open: ${active ?? 'none'} · ${states}`}</p>
64
+ </div>
65
+ )
66
+ }
67
+
68
+ /** Words, a run across two blocks and a whole block, the open one underlined. Click another to open it. */
69
+ export const Default: Story = { render: () => <Highlighted initial="two" /> }
70
+
71
+ /** Nothing open: every comment in the same light fill. */
72
+ export const NoneOpen: Story = { render: () => <Highlighted initial={null} /> }