@perspective-dev/viewer 5.4.0 → 5.5.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 (142) hide show
  1. package/dist/cdn/perspective-viewer.js +4 -4
  2. package/dist/cdn/perspective-viewer.js.map +2 -2
  3. package/dist/css/blueprint.css +1 -1
  4. package/dist/css/botanical.css +1 -1
  5. package/dist/css/dracula.css +1 -1
  6. package/dist/css/eggplant.css +1 -1
  7. package/dist/css/gruvbox-dark.css +1 -1
  8. package/dist/css/gruvbox.css +1 -1
  9. package/dist/css/icons.css +1 -1
  10. package/dist/css/intl/ar.css +1 -1
  11. package/dist/css/intl/de.css +1 -1
  12. package/dist/css/intl/es.css +1 -1
  13. package/dist/css/intl/fr.css +1 -1
  14. package/dist/css/intl/ja.css +1 -1
  15. package/dist/css/intl/pt.css +1 -1
  16. package/dist/css/intl/zh.css +1 -1
  17. package/dist/css/intl.css +1 -1
  18. package/dist/css/ledger.css +1 -1
  19. package/dist/css/monokai.css +1 -1
  20. package/dist/css/nord.css +1 -1
  21. package/dist/css/phosphor.css +1 -1
  22. package/dist/css/pro-dark.css +1 -1
  23. package/dist/css/pro.css +1 -1
  24. package/dist/css/solarized-dark.css +1 -1
  25. package/dist/css/solarized.css +1 -1
  26. package/dist/css/themes.css +1 -1
  27. package/dist/css/vaporwave.css +1 -1
  28. package/dist/css/velvet.css +1 -1
  29. package/dist/docs/perspective-docs.json +1 -1
  30. package/dist/esm/perspective-viewer.d.ts +2 -0
  31. package/dist/esm/perspective-viewer.inline.js +9 -9
  32. package/dist/esm/perspective-viewer.inline.js.map +3 -3
  33. package/dist/esm/perspective-viewer.js +4 -4
  34. package/dist/esm/perspective-viewer.js.map +2 -2
  35. package/dist/esm/plugin.d.ts +9 -0
  36. package/dist/esm/ts-rs/AddPanelOptions.d.ts +9 -0
  37. package/dist/esm/ts-rs/ExportMethod.d.ts +1 -1
  38. package/dist/esm/ts-rs/GetViewMode.d.ts +4 -0
  39. package/dist/esm/ts-rs/GetViewOptions.d.ts +8 -0
  40. package/dist/esm/ts-rs/RestoreOptions.d.ts +6 -0
  41. package/dist/esm/ts-rs/RestoreWorkspaceOptions.d.ts +9 -0
  42. package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +30 -22
  43. package/dist/wasm/perspective-viewer.d.ts +102 -47
  44. package/dist/wasm/perspective-viewer.js +233 -142
  45. package/dist/wasm/perspective-viewer.wasm +0 -0
  46. package/dist/wasm/perspective-viewer.wasm.d.ts +18 -18
  47. package/package.json +7 -7
  48. package/src/css/column-selector.css +11 -3
  49. package/src/css/column-style.css +162 -0
  50. package/src/css/config-selector.css +1 -1
  51. package/src/css/form/debug.css +9 -0
  52. package/src/css/status-bar.css +11 -0
  53. package/src/css/ui/containers/tabs.css +2 -1
  54. package/src/rust/agent/tools.rs +80 -7
  55. package/src/rust/components/column_selector/active_column.rs +9 -0
  56. package/src/rust/components/column_selector.rs +11 -0
  57. package/src/rust/components/column_settings_sidebar/style_tab/alignment_field.rs +90 -0
  58. package/src/rust/components/column_settings_sidebar/style_tab/font_field.rs +267 -0
  59. package/src/rust/components/column_settings_sidebar/style_tab/primitive_field.rs +1 -1
  60. package/src/rust/components/column_settings_sidebar/style_tab.rs +68 -9
  61. package/src/rust/components/debug_panel.rs +107 -50
  62. package/src/rust/components/export_dropdown.rs +6 -0
  63. package/src/rust/components/main_panel/msg.rs +4 -0
  64. package/src/rust/components/main_panel.rs +14 -0
  65. package/src/rust/components/mod.rs +0 -1
  66. package/src/rust/components/plugin_tab.rs +57 -3
  67. package/src/rust/components/settings_panel.rs +6 -0
  68. package/src/rust/components/status_bar.rs +12 -3
  69. package/src/rust/components/status_indicator.rs +4 -0
  70. package/src/rust/components/viewer/filters.rs +2 -2
  71. package/src/rust/components/viewer/msg.rs +4 -0
  72. package/src/rust/components/viewer/panels.rs +6 -3
  73. package/src/rust/components/viewer/render.rs +1 -0
  74. package/src/rust/components/viewer/wiring.rs +14 -2
  75. package/src/rust/components/viewer.rs +2 -2
  76. package/src/rust/config/column_config_schema.rs +312 -8
  77. package/src/rust/config/datetime_column_style.rs +0 -11
  78. package/src/rust/config/export_method.rs +24 -0
  79. package/src/rust/config/mod.rs +0 -2
  80. package/src/rust/config/options.rs +50 -0
  81. package/src/rust/config/workspace_config.rs +69 -24
  82. package/src/rust/custom_elements/viewer.rs +214 -113
  83. package/src/rust/js/plugin.rs +3 -1
  84. package/src/rust/lib.rs +7 -3
  85. package/src/rust/queries/plugin_column_styles.rs +6 -5
  86. package/src/rust/renderer/plugin_config.rs +200 -41
  87. package/src/rust/renderer/plugin_store.rs +2 -1
  88. package/src/rust/renderer/props.rs +5 -0
  89. package/src/rust/renderer.rs +9 -0
  90. package/src/rust/session/metadata.rs +5 -10
  91. package/src/rust/session/props.rs +4 -0
  92. package/src/rust/session.rs +273 -195
  93. package/src/rust/tasks/copy_export.rs +67 -0
  94. package/src/rust/tasks/create_panel.rs +16 -13
  95. package/src/rust/tasks/pipeline.rs +26 -8
  96. package/src/rust/tasks/restore_panel.rs +129 -30
  97. package/src/rust/tasks/send_column_config.rs +3 -0
  98. package/src/rust/tasks/sync_update_panels.rs +148 -68
  99. package/src/rust/tasks/table_lifecycle.rs +7 -1
  100. package/src/rust/ui/containers/tab_list.rs +1 -1
  101. package/src/rust/ui/form/intl_label.rs +4 -23
  102. package/src/rust/ui/portal.rs +1 -0
  103. package/src/rust/utils/css_vars.rs +13 -0
  104. package/src/rust/{config/datetime_column_style/color_mode.rs → utils/in_flight.rs} +47 -35
  105. package/src/rust/utils/local_fonts.rs +395 -0
  106. package/src/rust/utils/mod.rs +4 -0
  107. package/src/rust/utils/pending_effects.rs +6 -30
  108. package/src/rust/workspace.rs +12 -0
  109. package/src/svg/status_pending.svg +3 -0
  110. package/src/themes/icons.css +1 -0
  111. package/src/themes/intl/ar.css +20 -7
  112. package/src/themes/intl/de.css +20 -7
  113. package/src/themes/intl/es.css +20 -7
  114. package/src/themes/intl/fr.css +20 -7
  115. package/src/themes/intl/ja.css +20 -7
  116. package/src/themes/intl/pt.css +20 -7
  117. package/src/themes/intl/zh.css +20 -7
  118. package/src/themes/intl.css +20 -7
  119. package/src/themes/monokai.css +1 -1
  120. package/src/themes/vaporwave.css +1 -1
  121. package/src/ts/perspective-viewer.ts +2 -0
  122. package/src/ts/plugin.ts +9 -0
  123. package/src/ts/ts-rs/AddPanelOptions.ts +10 -0
  124. package/src/ts/ts-rs/ExportMethod.ts +1 -1
  125. package/src/ts/ts-rs/{DatetimeColorMode.ts → GetViewMode.ts} +4 -1
  126. package/src/ts/ts-rs/GetViewOptions.ts +7 -0
  127. package/src/ts/ts-rs/RestoreOptions.ts +7 -1
  128. package/src/ts/ts-rs/RestoreWorkspaceOptions.ts +10 -0
  129. package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +31 -20
  130. package/dist/esm/ts-rs/DatetimeColorMode.d.ts +0 -1
  131. package/dist/esm/ts-rs/FormatMode.d.ts +0 -1
  132. package/dist/esm/ts-rs/StringColorMode.d.ts +0 -1
  133. package/src/rust/components/string_column_style.rs +0 -120
  134. package/src/rust/config/string_column_style.rs +0 -135
  135. package/src/ts/ts-rs/FormatMode.ts +0 -3
  136. package/src/ts/ts-rs/StringColorMode.ts +0 -3
  137. /package/dist/wasm/snippets/{perspective-viewer-adc6f3f79141112b → perspective-viewer-6813b568219c6763}/inline0.js +0 -0
  138. /package/dist/wasm/snippets/{perspective-viewer-adc6f3f79141112b → perspective-viewer-6813b568219c6763}/inline1.js +0 -0
  139. /package/dist/wasm/snippets/{perspective-viewer-adc6f3f79141112b → perspective-viewer-6813b568219c6763}/inline2.js +0 -0
  140. /package/dist/wasm/snippets/{perspective-viewer-adc6f3f79141112b → perspective-viewer-6813b568219c6763}/inline3.js +0 -0
  141. /package/dist/wasm/snippets/{perspective-viewer-adc6f3f79141112b → perspective-viewer-6813b568219c6763}/inline4.js +0 -0
  142. /package/dist/wasm/snippets/{perspective-viewer-adc6f3f79141112b → perspective-viewer-6813b568219c6763}/inline5.js +0 -0
Binary file
@@ -17,7 +17,7 @@ export const perspectivedebugpluginelement_save: (a: number, b: number) => void;
17
17
  export const perspectivedebugpluginelement_select_mode: (a: number, b: number) => void;
18
18
  export const perspectivedebugpluginelement_update: (a: number, b: number) => number;
19
19
  export const perspectiveviewerelement___get_model: (a: number) => number;
20
- export const perspectiveviewerelement_addPanel: (a: number, b: number) => number;
20
+ export const perspectiveviewerelement_addPanel: (a: number, b: number, c: number) => number;
21
21
  export const perspectiveviewerelement_agentConfig: (a: number, b: number, c: number) => void;
22
22
  export const perspectiveviewerelement_agentPrompt: (a: number, b: number, c: number) => number;
23
23
  export const perspectiveviewerelement_agentReset: (a: number) => number;
@@ -48,7 +48,7 @@ export const perspectiveviewerelement_resetError: (a: number) => number;
48
48
  export const perspectiveviewerelement_resetThemes: (a: number, b: number, c: number) => number;
49
49
  export const perspectiveviewerelement_resize: (a: number, b: number) => number;
50
50
  export const perspectiveviewerelement_restore: (a: number, b: number, c: number) => number;
51
- export const perspectiveviewerelement_restoreWorkspace: (a: number, b: number) => number;
51
+ export const perspectiveviewerelement_restoreWorkspace: (a: number, b: number, c: number) => number;
52
52
  export const perspectiveviewerelement_restyleElement: (a: number) => number;
53
53
  export const perspectiveviewerelement_save: (a: number, b: number) => number;
54
54
  export const perspectiveviewerelement_saveWorkspace: (a: number, b: number) => number;
@@ -157,22 +157,22 @@ export const virtualdataslice_setIntegerCol: (a: number, b: number, c: number, d
157
157
  export const virtualdataslice_setStringCol: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => void;
158
158
  export const virtualserver_handleRequest: (a: number, b: number, c: number) => number;
159
159
  export const virtualserver_new: (a: number, b: number) => void;
160
- export const __wasm_bindgen_func_elem_18575: (a: number, b: number) => void;
161
- export const __wasm_bindgen_func_elem_19286: (a: number, b: number) => void;
162
- export const __wasm_bindgen_func_elem_20199: (a: number, b: number) => void;
163
- export const __wasm_bindgen_func_elem_4417: (a: number, b: number) => void;
164
- export const __wasm_bindgen_func_elem_24952: (a: number, b: number) => void;
165
- export const __wasm_bindgen_func_elem_33110: (a: number, b: number, c: number, d: number) => void;
166
- export const __wasm_bindgen_func_elem_18694: (a: number, b: number, c: number) => void;
167
- export const __wasm_bindgen_func_elem_19291: (a: number, b: number, c: number) => void;
168
- export const __wasm_bindgen_func_elem_7737: (a: number, b: number, c: number) => void;
169
- export const __wasm_bindgen_func_elem_7738: (a: number, b: number, c: number) => void;
170
- export const __wasm_bindgen_func_elem_7739: (a: number, b: number, c: number) => void;
171
- export const __wasm_bindgen_func_elem_7735: (a: number, b: number, c: number) => void;
172
- export const __wasm_bindgen_func_elem_24969: (a: number, b: number, c: number) => void;
173
- export const __wasm_bindgen_func_elem_21238: (a: number, b: number) => number;
174
- export const __wasm_bindgen_func_elem_19290: (a: number, b: number) => void;
175
- export const __wasm_bindgen_func_elem_21239: (a: number, b: number) => void;
160
+ export const __wasm_bindgen_func_elem_18919: (a: number, b: number) => void;
161
+ export const __wasm_bindgen_func_elem_4482: (a: number, b: number) => void;
162
+ export const __wasm_bindgen_func_elem_19628: (a: number, b: number) => void;
163
+ export const __wasm_bindgen_func_elem_20538: (a: number, b: number) => void;
164
+ export const __wasm_bindgen_func_elem_25347: (a: number, b: number) => void;
165
+ export const __wasm_bindgen_func_elem_33507: (a: number, b: number, c: number, d: number) => void;
166
+ export const __wasm_bindgen_func_elem_19039: (a: number, b: number, c: number) => void;
167
+ export const __wasm_bindgen_func_elem_7893: (a: number, b: number, c: number) => void;
168
+ export const __wasm_bindgen_func_elem_7894: (a: number, b: number, c: number) => void;
169
+ export const __wasm_bindgen_func_elem_7895: (a: number, b: number, c: number) => void;
170
+ export const __wasm_bindgen_func_elem_7891: (a: number, b: number, c: number) => void;
171
+ export const __wasm_bindgen_func_elem_19633: (a: number, b: number, c: number) => void;
172
+ export const __wasm_bindgen_func_elem_25364: (a: number, b: number, c: number) => void;
173
+ export const __wasm_bindgen_func_elem_21577: (a: number, b: number) => number;
174
+ export const __wasm_bindgen_func_elem_19632: (a: number, b: number) => void;
175
+ export const __wasm_bindgen_func_elem_21578: (a: number, b: number) => void;
176
176
  export const __wbindgen_export: (a: number, b: number) => number;
177
177
  export const __wbindgen_export2: (a: number, b: number, c: number, d: number) => number;
178
178
  export const __wbindgen_export3: (a: number) => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@perspective-dev/viewer",
3
- "version": "5.4.0",
3
+ "version": "5.5.1",
4
4
  "description": "The `<perspective-viewer>` Custom Element, frontend for Perspective",
5
5
  "keywords": [
6
6
  "perspective",
@@ -56,12 +56,12 @@
56
56
  "dependencies": {
57
57
  "pro_self_extracting_wasm": "0.0.9",
58
58
  "regular-layout": "=0.6.1",
59
- "@perspective-dev/client": "^5.4.0"
59
+ "@perspective-dev/client": "^5.5.1"
60
60
  },
61
61
  "devDependencies": {
62
62
  "lightningcss": "^1.29.0",
63
- "@playwright/test": "=1.58.0",
64
- "@playwright/experimental-ct-react": "=1.58.0",
63
+ "@playwright/test": "=1.62.0",
64
+ "@playwright/experimental-ct-react": "=1.62.0",
65
65
  "@types/react": ">17 <20",
66
66
  "@types/node": ">=22",
67
67
  "@types/react-dom": ">17 <20",
@@ -70,9 +70,9 @@
70
70
  "typedoc": "^0.28.7",
71
71
  "typescript": ">=5 <6",
72
72
  "zx": ">=8 <9",
73
- "@perspective-dev/esbuild-plugin": "^5.4.0",
74
- "@perspective-dev/metadata": "^5.4.0",
75
- "@perspective-dev/test": "^5.4.0"
73
+ "@perspective-dev/test": "^5.5.1",
74
+ "@perspective-dev/esbuild-plugin": "^5.5.1",
75
+ "@perspective-dev/metadata": "^5.5.1"
76
76
  },
77
77
  "scripts": {
78
78
  "build": "node ./build.mjs",
@@ -201,13 +201,15 @@
201
201
  /* Expression column toolbar buttons */
202
202
  span.expression-edit-button,
203
203
  span.expression-delete-button {
204
- padding-top: 1px;
204
+ height: 19px;
205
+ width: 21px;
205
206
  font-family: var(--psp-button--font-family, inherit);
206
207
  cursor: pointer;
207
- padding: 2px 4px 0 4px;
208
- margin-right: 1px;
208
+ padding: 1.5px 2.5px 0;
209
+ margin-right: 2px;
209
210
  margin-left: auto;
210
211
  border-radius: 3px;
212
+ border: 1px solid transparent;
211
213
  &:hover,
212
214
  &.is-editing {
213
215
  background-color: var(--psp--color);
@@ -507,6 +509,12 @@
507
509
  }
508
510
  }
509
511
 
512
+ .column-selector-column.is-modified {
513
+ .expression-edit-button:not(.is-editing) {
514
+ border: 1px dashed var(--psp--color);
515
+ }
516
+ }
517
+
510
518
  #active-columns .column-selector-column.dragdrop-hover,
511
519
  #active-columns.dragdrop-highlight .column-selector-column,
512
520
  .column-selector-column.dragdrop-hover {
@@ -61,6 +61,68 @@
61
61
  }
62
62
  }
63
63
 
64
+ .alignment-grid {
65
+ display: grid;
66
+ flex: 0 1 100%;
67
+ grid-template-columns: repeat(3, 1fr);
68
+ gap: 0px;
69
+ padding: 1px;
70
+ border: 1px solid var(--psp-inactive--color);
71
+ border-radius: 5px;
72
+ }
73
+
74
+ .alignment-cell {
75
+ display: grid;
76
+ place-items: center;
77
+ width: 100%;
78
+ height: 16px;
79
+ padding: 0;
80
+ border: 1px solid transparent;
81
+ border-radius: 4px;
82
+ background: none;
83
+ cursor: pointer;
84
+
85
+ &::before {
86
+ content: "";
87
+ width: 100%;
88
+ height: 100%;
89
+ border-radius: 3px;
90
+ background: #00000020;
91
+ }
92
+
93
+ &[aria-checked="true"]::before {
94
+ background: var(--psp-inactive--color);
95
+ }
96
+
97
+ &:hover:not(:disabled) {
98
+ border-color: var(--psp--color);
99
+ }
100
+
101
+ &:disabled {
102
+ cursor: default;
103
+
104
+ &::before {
105
+ opacity: 0.25;
106
+ }
107
+ }
108
+
109
+ &[data-align^="top"] {
110
+ align-items: start;
111
+ }
112
+
113
+ &[data-align^="bottom"] {
114
+ align-items: end;
115
+ }
116
+
117
+ &[data-align$="left"] {
118
+ justify-items: start;
119
+ }
120
+
121
+ &[data-align$="right"] {
122
+ justify-items: end;
123
+ }
124
+ }
125
+
64
126
  &.no-style {
65
127
  font-style: italic;
66
128
  background-color: var(--psp-placeholder--background);
@@ -211,6 +273,7 @@
211
273
 
212
274
  div.section {
213
275
  display: flex;
276
+ justify-content: space-between;
214
277
  align-items: center;
215
278
  flex: 1 1 100%;
216
279
  }
@@ -537,6 +600,105 @@
537
600
  }
538
601
  }
539
602
 
603
+ .font-style-toggle {
604
+ flex: 0 0 24px;
605
+ height: 24px;
606
+ margin-left: 4px;
607
+ display: inline-flex;
608
+ align-items: center;
609
+ justify-content: center;
610
+ border: 1px solid var(--psp-inactive--color, #666);
611
+ border-radius: 2px;
612
+ cursor: pointer;
613
+ user-select: none;
614
+ font-size: 12px;
615
+ color: var(--psp-inactive--color, #666);
616
+
617
+ &.bold:before {
618
+ content: "B";
619
+ font-weight: bold;
620
+ }
621
+
622
+ &.italic:before {
623
+ content: "I";
624
+ font-style: italic;
625
+ font-family: serif;
626
+ }
627
+
628
+ &:hover {
629
+ color: var(--psp--color, inherit);
630
+ border-color: var(--psp--color, inherit);
631
+ }
632
+
633
+ &[aria-pressed="true"] {
634
+ color: var(--psp--background-color);
635
+ background-color: var(--psp--color);
636
+ border-color: var(--psp--color);
637
+ }
638
+ }
639
+
640
+ .font-group {
641
+ display: flex;
642
+ flex: 1 1 100%;
643
+ flex-wrap: wrap;
644
+ contain: inline-size;
645
+ & > input {
646
+ flex: 1 1 auto;
647
+ }
648
+
649
+ .font-group-family {
650
+ flex: 1 1 100%;
651
+ margin-bottom: 3px;
652
+ }
653
+ }
654
+
655
+ .local-fonts-controls {
656
+ display: flex;
657
+ justify-content: flex-end;
658
+ align-items: center;
659
+ flex: 1 1 100%;
660
+ height: 0;
661
+ margin-top: 10px;
662
+ margin-bottom: -10px;
663
+ margin-right: 24px;
664
+ position: relative;
665
+ z-index: 1;
666
+ font-size: var(--label--font-size, 0.75em);
667
+ color: var(--psp-inactive--color);
668
+
669
+ .local-fonts-request {
670
+ cursor: pointer;
671
+ user-select: none;
672
+ line-height: 15px;
673
+
674
+ &:before {
675
+ font-family: var(--psp-button--font-family, inherit);
676
+ color: var(--psp-inactive--color, #666);
677
+ content: var(
678
+ --psp-label--request-local-fonts--content,
679
+ "Local Fonts"
680
+ );
681
+ }
682
+
683
+ &:hover:before {
684
+ color: var(--psp--color, inherit);
685
+ }
686
+
687
+ &.blocked {
688
+ cursor: help;
689
+
690
+ &:before,
691
+ &:hover:before {
692
+ color: var(--psp-inactive--color, #666);
693
+ content: var(
694
+ --psp-label--local-fonts-blocked--content,
695
+ "No Access"
696
+ );
697
+ }
698
+ }
699
+ }
700
+ }
701
+
540
702
  input[type="color"] {
541
703
  width: 100%;
542
704
  height: 24px;
@@ -408,7 +408,7 @@
408
408
  .sort-icon {
409
409
  display: inline-flex;
410
410
  margin-left: auto;
411
- /* margin-right: 8px; */
411
+ margin-right: 3px;
412
412
  cursor: pointer;
413
413
  font-family: var(--psp-button--font-family, inherit);
414
414
  background-repeat: no-repeat;
@@ -93,6 +93,15 @@
93
93
  flex: 0 1 auto;
94
94
  overflow: hidden;
95
95
  padding: 8px 8px 8px 0;
96
+
97
+ #debug-panel-error {
98
+ flex: 0 0 auto;
99
+ padding-top: 6px;
100
+ font-size: 10px;
101
+ color: var(--psp-error--color);
102
+ white-space: pre-wrap;
103
+ overflow-wrap: anywhere;
104
+ }
96
105
  }
97
106
 
98
107
  #editor {
@@ -210,6 +210,17 @@
210
210
  background-color: transparent;
211
211
  }
212
212
 
213
+ span#status.pending {
214
+ background-color: var(--psp-inactive--color);
215
+ mask-image: var(--psp-icon--status-pending--mask-image);
216
+ -webkit-mask-image: var(--psp-icon--status-pending--mask-image);
217
+ &:before {
218
+ visibility: hidden;
219
+ content: var(--psp-icon--status-pending--mask-image);
220
+ line-height: 0;
221
+ }
222
+ }
223
+
213
224
  span#status.connected,
214
225
  span#status.updating {
215
226
  mask-image: var(--psp-icon--status-ok--mask-image);
@@ -13,7 +13,8 @@
13
13
 
14
14
  :host {
15
15
  #format-tab {
16
- overflow: auto;
16
+ overflow-y: auto;
17
+ overflow-x: hidden;
17
18
  scrollbar-gutter: stable;
18
19
  }
19
20
 
@@ -22,7 +22,7 @@ use wasm_bindgen::prelude::*;
22
22
 
23
23
  use super::client::protocol::{FunctionDef, ToolDef};
24
24
  use super::docs::{DocsBundle, DocsCell};
25
- use crate::config::{ColumnConfigSchema, ControlSpec};
25
+ use crate::config::{Alignment, ColumnConfigSchema, ControlSpec};
26
26
  use crate::custom_elements::viewer::PerspectiveViewerElement;
27
27
  use crate::queries::{
28
28
  get_column_config_schema, get_plugin_config_schema, get_viewer_config, validate_expr,
@@ -628,6 +628,78 @@ fn control_schema_entries(spec: &ControlSpec) -> Vec<(String, Value)> {
628
628
  ControlSpec::String { key, default, .. } => {
629
629
  vec![(key.clone(), json!({ "type": "string", "default": default }))]
630
630
  },
631
+ ControlSpec::Font {
632
+ key,
633
+ default,
634
+ size,
635
+ bold,
636
+ italic,
637
+ } => {
638
+ let mut out = vec![(
639
+ key.clone(),
640
+ json!({
641
+ "type": "string",
642
+ "description": "A CSS font family: a generic keyword (`inherit`, `monospace`, `sans-serif`, `serif`, `system-ui`) or an installed font name",
643
+ "default": default,
644
+ }),
645
+ )];
646
+
647
+ if let Some(size) = size {
648
+ let mut node = json!({
649
+ "type": "number",
650
+ "description": "Font size in CSS pixels",
651
+ "default": size.default,
652
+ });
653
+
654
+ if let Some(min) = size.min {
655
+ node["minimum"] = json!(min);
656
+ }
657
+
658
+ if let Some(max) = size.max {
659
+ node["maximum"] = json!(max);
660
+ }
661
+
662
+ out.push((size.key.clone(), node));
663
+ }
664
+
665
+ for (toggle, description) in [(bold, "Bold text"), (italic, "Italic text")] {
666
+ if let Some(toggle) = toggle {
667
+ out.push((
668
+ toggle.key.clone(),
669
+ json!({ "type": "boolean", "description": description, "default": toggle.default }),
670
+ ));
671
+ }
672
+ }
673
+
674
+ out
675
+ },
676
+ ControlSpec::Alignment {
677
+ key,
678
+ default,
679
+ corners,
680
+ } => {
681
+ let cells: &[Alignment] = if *corners {
682
+ &Alignment::CORNERS
683
+ } else {
684
+ &Alignment::ALL
685
+ };
686
+
687
+ let mut node = json!({
688
+ "type": "string",
689
+ "enum": cells.iter().map(|x| x.as_str()).collect::<Vec<_>>(),
690
+ "description": if *corners {
691
+ "Anchor corner of a 3×3 grid: `top-left`, `top-right`, `bottom-left` or `bottom-right`"
692
+ } else {
693
+ "Anchor cell of a 3×3 grid: a corner `top-left|top-right|bottom-left|bottom-right`, an edge `top|left|right|bottom`, or `center`"
694
+ },
695
+ });
696
+
697
+ if let Some(default) = default {
698
+ node["default"] = json!(default.as_str());
699
+ }
700
+
701
+ vec![(key.clone(), node)]
702
+ },
631
703
  ControlSpec::Color { key, default } => vec![(
632
704
  key.clone(),
633
705
  json!({
@@ -683,10 +755,6 @@ fn control_schema_entries(spec: &ControlSpec) -> Vec<(String, Value)> {
683
755
  "date_format".to_owned(),
684
756
  json!({ "description": "Datetime display format: a style preset or custom format fields" }),
685
757
  )],
686
- ControlSpec::StringFormat => vec![(
687
- "format".to_owned(),
688
- json!({ "description": "String display format" }),
689
- )],
690
758
  ControlSpec::NumberSeriesStyle { .. } => vec![
691
759
  (
692
760
  "chart_type".to_owned(),
@@ -977,7 +1045,7 @@ impl PerspectiveViewerElement {
977
1045
 
978
1046
  let before = self.agent_panel_names();
979
1047
  let js_config = js_sys::JSON::parse(&args.config.to_string())?;
980
- let added = self.addPanel(js_config.unchecked_into());
1048
+ let added = self.addPanel(js_config.unchecked_into(), None);
981
1049
  let result = wasm_bindgen_futures::JsFuture::from(js_sys::Promise::from(added)).await;
982
1050
  let id = match result {
983
1051
  Ok(id) => id
@@ -1048,7 +1116,12 @@ impl PerspectiveViewerElement {
1048
1116
  let columns_config = serde_json::to_value(&viewer_config.columns_config)?;
1049
1117
  let plugin_schema = {
1050
1118
  let view_config = panel.session.get_view_config();
1051
- get_plugin_config_schema(&panel.renderer, &view_config).ok()
1119
+ get_plugin_config_schema(
1120
+ &panel.renderer,
1121
+ &view_config,
1122
+ Some(&viewer_config.panel.plugin_config),
1123
+ )
1124
+ .ok()
1052
1125
  };
1053
1126
 
1054
1127
  let column_schema = match &args.column {
@@ -70,6 +70,10 @@ pub struct ActiveColumnProps {
70
70
  #[prop_or_default]
71
71
  pub is_window: bool,
72
72
 
73
+ /// Whether this column's `columns_config` entry holds overridden keys.
74
+ #[prop_or_default]
75
+ pub is_modified: bool,
76
+
73
77
  #[prop_or_default]
74
78
  pub is_last_column: bool,
75
79
 
@@ -103,6 +107,7 @@ impl PartialEq for ActiveColumnProps {
103
107
  && self.is_editing == rhs.is_editing
104
108
  && self.is_expression == rhs.is_expression
105
109
  && self.is_window == rhs.is_window
110
+ && self.is_modified == rhs.is_modified
106
111
  && self.show_edit_btn == rhs.show_edit_btn
107
112
  && self.col_type == rhs.col_type
108
113
  && self.metadata == rhs.metadata
@@ -219,6 +224,10 @@ impl Component for ActiveColumn {
219
224
  outer_classes.push("dragdrop-hover");
220
225
  }
221
226
 
227
+ if ctx.props().is_modified {
228
+ outer_classes.push("is-modified");
229
+ }
230
+
222
231
  let name = match &ctx.props().name {
223
232
  ActiveColumnState {
224
233
  label,
@@ -67,6 +67,10 @@ pub struct ColumnSelectorProps {
67
67
  /// prop on `SettingsPanelProps`.
68
68
  pub plugin_static_config: Rc<PluginStaticConfig>,
69
69
  pub view_config: PtrEqRc<ViewConfig>,
70
+
71
+ /// Snapshot of the active plugin's `columns_config` bucket, whose
72
+ /// non-empty entries mark their active columns as modified.
73
+ pub columns_config: PtrEqRc<ColumnConfigMap>,
70
74
  pub drag_column: Option<String>,
71
75
 
72
76
  /// Cloned session metadata snapshot — threaded from `SessionProps`
@@ -110,6 +114,7 @@ impl PartialEq for ColumnSelectorProps {
110
114
  && self.named_column_count == rhs.named_column_count
111
115
  && self.plugin_static_config == rhs.plugin_static_config
112
116
  && self.view_config == rhs.view_config
117
+ && self.columns_config == rhs.columns_config
113
118
  && self.drag_column == rhs.drag_column
114
119
  && self.metadata == rhs.metadata
115
120
  && self.selected_theme == rhs.selected_theme
@@ -455,6 +460,11 @@ impl Component for ColumnSelector {
455
460
  name.get_name().is_some() && renderer.can_render_column_styles();
456
461
 
457
462
  let show_edit_btn = is_expression || is_window || can_render_styles;
463
+ let is_modified = name
464
+ .get_name()
465
+ .and_then(|n| ctx.props().columns_config.get(n))
466
+ .is_some_and(|entry| !entry.is_empty());
467
+
458
468
  let on_open_expr_panel = &ctx.props().on_open_expr_panel;
459
469
  html_nested! {
460
470
  <ScrollPanelItem {key} {size_hint}>
@@ -465,6 +475,7 @@ impl Component for ColumnSelector {
465
475
  {is_editing}
466
476
  {is_expression}
467
477
  {is_window}
478
+ {is_modified}
468
479
  {show_edit_btn}
469
480
  {col_type}
470
481
  {is_last_column}
@@ -0,0 +1,90 @@
1
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2
+ // ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3
+ // ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4
+ // ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5
+ // ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6
+ // ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7
+ // ┃ Copyright (c) 2017, the Perspective Authors. ┃
8
+ // ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9
+ // ┃ This file is part of the Perspective library, distributed under the terms ┃
10
+ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
+
13
+ use serde_json::Value;
14
+ use web_sys::MouseEvent;
15
+ use yew::prelude::*;
16
+
17
+ use super::primitive_field::emit;
18
+ use crate::config::{Alignment, ColumnConfigFieldUpdate};
19
+ use crate::ui::OptionalField;
20
+
21
+ #[derive(Properties, PartialEq)]
22
+ pub struct AlignmentFieldProps {
23
+ pub field_key: String,
24
+ pub default: Option<Alignment>,
25
+ pub corners: bool,
26
+ pub current: Option<Alignment>,
27
+ pub on_change: Callback<ColumnConfigFieldUpdate>,
28
+ }
29
+
30
+ /// The `ControlSpec::Alignment` widget: a 3×3 grid of cells whose dot sits
31
+ /// where the cell's token anchors.
32
+ #[function_component]
33
+ pub fn AlignmentField(props: &AlignmentFieldProps) -> Html {
34
+ let selected = props.current.or(props.default);
35
+ let checked = props.current.is_some() && props.current != props.default;
36
+ let on_reset = use_callback(
37
+ (props.field_key.clone(), props.on_change.clone()),
38
+ |_: MouseEvent, (key, on_change)| emit(on_change, key, None),
39
+ );
40
+
41
+ let cells = Alignment::ALL.into_iter().map(|align| {
42
+ let onclick = {
43
+ let key = props.field_key.clone();
44
+ let default = props.default;
45
+ let on_change = props.on_change.clone();
46
+ Callback::from(move |_: MouseEvent| {
47
+ if Some(align) == default {
48
+ emit(&on_change, &key, None);
49
+ } else {
50
+ emit(
51
+ &on_change,
52
+ &key,
53
+ Some(Value::String(align.as_str().to_owned())),
54
+ );
55
+ }
56
+ })
57
+ };
58
+
59
+ let is_selected = selected == Some(align);
60
+ html! {
61
+ <button
62
+ type="button"
63
+ role="radio"
64
+ class={classes!(
65
+ "alignment-cell",
66
+ (is_selected && props.current.is_none()).then_some("is-default")
67
+ )}
68
+ data-align={align.as_str()}
69
+ aria-checked={is_selected.to_string()}
70
+ aria-label={align.humanized()}
71
+ disabled={props.corners && !align.is_corner()}
72
+ {onclick}
73
+ />
74
+ }
75
+ });
76
+
77
+ html! {
78
+ <div class="row">
79
+ <OptionalField label={props.field_key.clone()} on_check={on_reset} {checked}>
80
+ <div
81
+ class="alignment-grid"
82
+ role="radiogroup"
83
+ id={format!("{}-alignment", props.field_key)}
84
+ >
85
+ { for cells }
86
+ </div>
87
+ </OptionalField>
88
+ </div>
89
+ }
90
+ }