@gjsify/example-gtk-adwaita-storybook 0.53.0 → 0.55.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.
Files changed (243) hide show
  1. package/dist/browser-main.js +8093 -3042
  2. package/dist/gjs.js +15 -10
  3. package/dist/gjs.node.mjs +10 -5
  4. package/package.json +5 -5
  5. package/src/browser/buttons/color-dialog-button.web.ts +42 -0
  6. package/src/browser/buttons/font-dialog-button.web.ts +42 -0
  7. package/src/browser/buttons/link-button.web.ts +37 -0
  8. package/src/browser/buttons/scale-button.web.ts +36 -0
  9. package/src/browser/buttons/toggle-button.web.ts +35 -0
  10. package/src/browser/controls/check-button.web.ts +38 -0
  11. package/src/browser/controls/gtk-spinner.web.ts +39 -0
  12. package/src/browser/controls/level-bar.web.ts +43 -0
  13. package/src/browser/controls/password-entry.web.ts +36 -0
  14. package/src/browser/controls/progress-bar.web.ts +39 -0
  15. package/src/browser/controls/scale.web.ts +44 -0
  16. package/src/browser/controls/search-entry.web.ts +49 -0
  17. package/src/browser/controls/spin-button.web.ts +40 -0
  18. package/src/browser/controls/switch.web.ts +38 -0
  19. package/src/browser/drawing/drag-icon.web.ts +112 -0
  20. package/src/browser/drawing/drawing-area.web.ts +66 -0
  21. package/src/browser/drawing/gl-area.web.ts +162 -0
  22. package/src/browser/drawing/graphics-offload.web.ts +74 -0
  23. package/src/browser/feedback/dialog.web.ts +76 -0
  24. package/src/browser/feedback/emoji-chooser.web.ts +93 -0
  25. package/src/browser/feedback/gtk-about-dialog.web.ts +105 -0
  26. package/src/browser/feedback/page-setup-unix-dialog.web.ts +93 -0
  27. package/src/browser/feedback/print-unix-dialog.web.ts +111 -0
  28. package/src/browser/feedback/shortcuts-dialog.web.ts +90 -0
  29. package/src/browser/layout/application-window.web.ts +94 -0
  30. package/src/browser/layout/aspect-frame.web.ts +49 -0
  31. package/src/browser/layout/bin.web.ts +41 -0
  32. package/src/browser/layout/breakpoint-bin.web.ts +61 -0
  33. package/src/browser/layout/center-box.web.ts +45 -0
  34. package/src/browser/layout/clamp-scrollable.web.ts +47 -0
  35. package/src/browser/layout/expander.web.ts +52 -0
  36. package/src/browser/layout/fixed.web.ts +51 -0
  37. package/src/browser/layout/frame.web.ts +40 -0
  38. package/src/browser/layout/grid.web.ts +46 -0
  39. package/src/browser/layout/layout-slot.web.ts +60 -0
  40. package/src/browser/layout/multi-layout-view.web.ts +94 -0
  41. package/src/browser/layout/overlay.web.ts +57 -0
  42. package/src/browser/layout/paned.web.ts +69 -0
  43. package/src/browser/layout/popover-bin.web.ts +54 -0
  44. package/src/browser/layout/popover-menu.web.ts +48 -0
  45. package/src/browser/layout/popover.web.ts +61 -0
  46. package/src/browser/layout/revealer.web.ts +53 -0
  47. package/src/browser/layout/scrollbar.web.ts +54 -0
  48. package/src/browser/layout/scrolled-window.web.ts +52 -0
  49. package/src/browser/layout/separator.web.ts +47 -0
  50. package/src/browser/layout/viewport.web.ts +51 -0
  51. package/src/browser/layout/window.web.ts +60 -0
  52. package/src/browser/media/image.web.ts +39 -0
  53. package/src/browser/media/inscription.web.ts +52 -0
  54. package/src/browser/media/media-controls.web.ts +65 -0
  55. package/src/browser/media/picture.web.ts +56 -0
  56. package/src/browser/media/video.web.ts +56 -0
  57. package/src/browser/navigation/popover-menu-bar.web.ts +40 -0
  58. package/src/browser/presentation/calendar.web.ts +49 -0
  59. package/src/browser/presentation/column-view.web.ts +45 -0
  60. package/src/browser/presentation/flow-box.web.ts +52 -0
  61. package/src/browser/presentation/grid-view.web.ts +37 -0
  62. package/src/browser/presentation/list-box.web.ts +48 -0
  63. package/src/browser/presentation/list-view.web.ts +36 -0
  64. package/src/browser/presentation/tree-expander.web.ts +68 -0
  65. package/src/browser/presentation/window-handle.web.ts +42 -0
  66. package/src/browser/rows/preferences-row.web.ts +43 -0
  67. package/src/browser/stories.ts +158 -0
  68. package/src/browser/text/editable-label.web.ts +45 -0
  69. package/src/browser/text/search-bar.web.ts +45 -0
  70. package/src/browser/text/text-view.web.ts +54 -0
  71. package/src/browser/text/text.web.ts +48 -0
  72. package/src/browser/view-switching/notebook.web.ts +67 -0
  73. package/src/browser/view-switching/stack-sidebar.web.ts +68 -0
  74. package/src/browser/view-switching/stack-switcher.web.ts +70 -0
  75. package/src/browser/view-switching/stack.web.ts +72 -0
  76. package/src/browser/view-switching/tab-bar.web.ts +105 -0
  77. package/src/browser/view-switching/tab-button.web.ts +52 -0
  78. package/src/browser/view-switching/tab-overview.web.ts +87 -0
  79. package/src/browser/view-switching/view-switcher-sidebar.web.ts +77 -0
  80. package/src/browser/windows/action-bar.web.ts +66 -0
  81. package/src/browser/windows/gtk-application-window.web.ts +67 -0
  82. package/src/browser/windows/gtk-header-bar.web.ts +61 -0
  83. package/src/browser/windows/gtk-window.web.ts +58 -0
  84. package/src/browser/windows/window-controls.web.ts +54 -0
  85. package/src/buttons/color-dialog-button.meta.ts +16 -0
  86. package/src/buttons/color-dialog-button.story.ts +49 -0
  87. package/src/buttons/font-dialog-button.meta.ts +29 -0
  88. package/src/buttons/font-dialog-button.story.ts +52 -0
  89. package/src/buttons/link-button.meta.ts +18 -0
  90. package/src/buttons/link-button.story.ts +48 -0
  91. package/src/buttons/scale-button.meta.ts +34 -0
  92. package/src/buttons/scale-button.story.ts +50 -0
  93. package/src/buttons/toggle-button.meta.ts +17 -0
  94. package/src/buttons/toggle-button.story.ts +46 -0
  95. package/src/controls/check-button.meta.ts +19 -0
  96. package/src/controls/check-button.story.ts +56 -0
  97. package/src/controls/gtk-spinner.meta.ts +24 -0
  98. package/src/controls/gtk-spinner.story.ts +53 -0
  99. package/src/controls/level-bar.meta.ts +39 -0
  100. package/src/controls/level-bar.story.ts +58 -0
  101. package/src/controls/password-entry.meta.ts +18 -0
  102. package/src/controls/password-entry.story.ts +54 -0
  103. package/src/controls/progress-bar.meta.ts +20 -0
  104. package/src/controls/progress-bar.story.ts +53 -0
  105. package/src/controls/scale.meta.ts +47 -0
  106. package/src/controls/scale.story.ts +66 -0
  107. package/src/controls/search-entry.meta.ts +18 -0
  108. package/src/controls/search-entry.story.ts +57 -0
  109. package/src/controls/spin-button.meta.ts +40 -0
  110. package/src/controls/spin-button.story.ts +55 -0
  111. package/src/controls/switch.meta.ts +18 -0
  112. package/src/controls/switch.story.ts +50 -0
  113. package/src/drawing/drag-icon.meta.ts +39 -0
  114. package/src/drawing/drag-icon.story.ts +117 -0
  115. package/src/drawing/drawing-area.meta.ts +45 -0
  116. package/src/drawing/drawing-area.story.ts +78 -0
  117. package/src/drawing/gl-area.meta.ts +30 -0
  118. package/src/drawing/gl-area.story.ts +113 -0
  119. package/src/drawing/graphics-offload.meta.ts +39 -0
  120. package/src/drawing/graphics-offload.story.ts +73 -0
  121. package/src/feedback/dialog.meta.ts +25 -0
  122. package/src/feedback/dialog.story.ts +78 -0
  123. package/src/feedback/emoji-chooser.meta.ts +10 -0
  124. package/src/feedback/emoji-chooser.story.ts +98 -0
  125. package/src/feedback/gtk-about-dialog.meta.ts +29 -0
  126. package/src/feedback/gtk-about-dialog.story.ts +90 -0
  127. package/src/feedback/page-setup-unix-dialog.meta.ts +37 -0
  128. package/src/feedback/page-setup-unix-dialog.story.ts +70 -0
  129. package/src/feedback/print-unix-dialog.meta.ts +30 -0
  130. package/src/feedback/print-unix-dialog.story.ts +93 -0
  131. package/src/feedback/shortcuts-dialog.meta.ts +12 -0
  132. package/src/feedback/shortcuts-dialog.story.ts +79 -0
  133. package/src/layout/application-window.meta.ts +16 -0
  134. package/src/layout/application-window.story.ts +97 -0
  135. package/src/layout/aspect-frame.meta.ts +19 -0
  136. package/src/layout/aspect-frame.story.ts +62 -0
  137. package/src/layout/bin.meta.ts +15 -0
  138. package/src/layout/bin.story.ts +56 -0
  139. package/src/layout/breakpoint-bin.meta.ts +38 -0
  140. package/src/layout/breakpoint-bin.story.ts +62 -0
  141. package/src/layout/center-box.meta.ts +26 -0
  142. package/src/layout/center-box.story.ts +54 -0
  143. package/src/layout/clamp-scrollable.meta.ts +30 -0
  144. package/src/layout/clamp-scrollable.story.ts +51 -0
  145. package/src/layout/expander.meta.ts +30 -0
  146. package/src/layout/expander.story.ts +62 -0
  147. package/src/layout/fixed.meta.ts +16 -0
  148. package/src/layout/fixed.story.ts +54 -0
  149. package/src/layout/frame.meta.ts +24 -0
  150. package/src/layout/frame.story.ts +51 -0
  151. package/src/layout/grid.meta.ts +45 -0
  152. package/src/layout/grid.story.ts +80 -0
  153. package/src/layout/layout-slot.meta.ts +22 -0
  154. package/src/layout/layout-slot.story.ts +68 -0
  155. package/src/layout/multi-layout-view.meta.ts +31 -0
  156. package/src/layout/multi-layout-view.story.ts +101 -0
  157. package/src/layout/overlay.meta.ts +42 -0
  158. package/src/layout/overlay.story.ts +72 -0
  159. package/src/layout/paned.meta.ts +45 -0
  160. package/src/layout/paned.story.ts +70 -0
  161. package/src/layout/popover-bin.meta.ts +29 -0
  162. package/src/layout/popover-bin.story.ts +66 -0
  163. package/src/layout/popover-menu.meta.ts +54 -0
  164. package/src/layout/popover-menu.story.ts +119 -0
  165. package/src/layout/popover.meta.ts +38 -0
  166. package/src/layout/popover.story.ts +66 -0
  167. package/src/layout/revealer.meta.ts +44 -0
  168. package/src/layout/revealer.story.ts +79 -0
  169. package/src/layout/scrollbar.meta.ts +33 -0
  170. package/src/layout/scrollbar.story.ts +61 -0
  171. package/src/layout/scrolled-window.meta.ts +57 -0
  172. package/src/layout/scrolled-window.story.ts +74 -0
  173. package/src/layout/separator.meta.ts +25 -0
  174. package/src/layout/separator.story.ts +53 -0
  175. package/src/layout/viewport.meta.ts +19 -0
  176. package/src/layout/viewport.story.ts +53 -0
  177. package/src/layout/window.meta.ts +13 -0
  178. package/src/layout/window.story.ts +66 -0
  179. package/src/media/image.meta.ts +24 -0
  180. package/src/media/image.story.ts +48 -0
  181. package/src/media/inscription.meta.ts +45 -0
  182. package/src/media/inscription.story.ts +64 -0
  183. package/src/media/media-controls.meta.ts +30 -0
  184. package/src/media/media-controls.story.ts +58 -0
  185. package/src/media/picture.meta.ts +42 -0
  186. package/src/media/picture.story.ts +81 -0
  187. package/src/media/video.meta.ts +32 -0
  188. package/src/media/video.story.ts +57 -0
  189. package/src/metas.ts +79 -0
  190. package/src/navigation/popover-menu-bar.meta.ts +51 -0
  191. package/src/navigation/popover-menu-bar.story.ts +72 -0
  192. package/src/presentation/calendar.meta.ts +28 -0
  193. package/src/presentation/calendar.story.ts +64 -0
  194. package/src/presentation/column-view.meta.ts +30 -0
  195. package/src/presentation/column-view.story.ts +73 -0
  196. package/src/presentation/flow-box.meta.ts +51 -0
  197. package/src/presentation/flow-box.story.ts +72 -0
  198. package/src/presentation/grid-view.meta.ts +28 -0
  199. package/src/presentation/grid-view.story.ts +64 -0
  200. package/src/presentation/list-box.meta.ts +55 -0
  201. package/src/presentation/list-box.story.ts +68 -0
  202. package/src/presentation/list-view.meta.ts +38 -0
  203. package/src/presentation/list-view.story.ts +73 -0
  204. package/src/presentation/tree-expander.meta.ts +24 -0
  205. package/src/presentation/tree-expander.story.ts +105 -0
  206. package/src/presentation/window-handle.meta.ts +27 -0
  207. package/src/presentation/window-handle.story.ts +53 -0
  208. package/src/rows/preferences-row.meta.ts +15 -0
  209. package/src/rows/preferences-row.story.ts +50 -0
  210. package/src/text/editable-label.meta.ts +17 -0
  211. package/src/text/editable-label.story.ts +47 -0
  212. package/src/text/search-bar.meta.ts +17 -0
  213. package/src/text/search-bar.story.ts +48 -0
  214. package/src/text/text-view.meta.ts +49 -0
  215. package/src/text/text-view.story.ts +69 -0
  216. package/src/text/text.meta.ts +21 -0
  217. package/src/text/text.story.ts +50 -0
  218. package/src/view-switching/notebook.meta.ts +44 -0
  219. package/src/view-switching/notebook.story.ts +77 -0
  220. package/src/view-switching/stack-sidebar.meta.ts +50 -0
  221. package/src/view-switching/stack-sidebar.story.ts +85 -0
  222. package/src/view-switching/stack-switcher.meta.ts +53 -0
  223. package/src/view-switching/stack-switcher.story.ts +95 -0
  224. package/src/view-switching/stack.meta.ts +53 -0
  225. package/src/view-switching/stack.story.ts +95 -0
  226. package/src/view-switching/tab-bar.meta.ts +22 -0
  227. package/src/view-switching/tab-bar.story.ts +85 -0
  228. package/src/view-switching/tab-button.meta.ts +14 -0
  229. package/src/view-switching/tab-button.story.ts +52 -0
  230. package/src/view-switching/tab-overview.meta.ts +21 -0
  231. package/src/view-switching/tab-overview.story.ts +86 -0
  232. package/src/view-switching/view-switcher-sidebar.meta.ts +42 -0
  233. package/src/view-switching/view-switcher-sidebar.story.ts +73 -0
  234. package/src/windows/action-bar.meta.ts +27 -0
  235. package/src/windows/action-bar.story.ts +65 -0
  236. package/src/windows/gtk-application-window.meta.ts +27 -0
  237. package/src/windows/gtk-application-window.story.ts +89 -0
  238. package/src/windows/gtk-header-bar.meta.ts +39 -0
  239. package/src/windows/gtk-header-bar.story.ts +76 -0
  240. package/src/windows/gtk-window.meta.ts +30 -0
  241. package/src/windows/gtk-window.story.ts +64 -0
  242. package/src/windows/window-controls.meta.ts +42 -0
  243. package/src/windows/window-controls.story.ts +61 -0
@@ -0,0 +1,57 @@
1
+ // Gtk.Video — a play surface with an overlay and a self-hiding controls bar.
2
+ // original implementation.
3
+
4
+ import Gtk from 'gi://Gtk?version=4.0';
5
+ import GObject from 'gi://GObject?version=2.0';
6
+ import { type StoryArgs, type StoryMeta, type StoryModule, StoryWidget } from '@gjsify/storybook';
7
+ import { videoMeta } from './video.meta.js';
8
+
9
+ /** Story: a Gtk.Video at the reader's width, with no stream attached. */
10
+ export class VideoStory extends StoryWidget {
11
+ private _video: Gtk.Video | null = null;
12
+
13
+ static {
14
+ GObject.registerClass({ GTypeName: 'AdwStorybookVideo' }, VideoStory);
15
+ }
16
+
17
+ constructor() {
18
+ super(StoryWidget.fromMeta(VideoStory.getMetadata(), 'Default'));
19
+ }
20
+
21
+ static getMetadata(): StoryMeta {
22
+ return { ...videoMeta, component: Gtk.Video.$gtype };
23
+ }
24
+
25
+ initialize(): void {
26
+ // NO `set_media_stream`. A stream needs a backend behind it — gstreamer — and the
27
+ // overlay's first arm is the one a stream-less video shows anyway: `media-eject` is
28
+ // what `gtk_video_update_overlay_icon` draws when there is no stream at all
29
+ // (gtkvideo.c:629). So the story shows the state every GtkVideo starts in, which is
30
+ // the state a browser's `<video>` cannot reach.
31
+ this._video = new Gtk.Video({ halign: Gtk.Align.CENTER, valign: Gtk.Align.CENTER });
32
+ this._apply();
33
+ this.addContent(this._video);
34
+ }
35
+
36
+ updateArgs(_args: StoryArgs): void {
37
+ this._apply();
38
+ }
39
+
40
+ private _apply(): void {
41
+ if (!this._video) return;
42
+ const width = this.args.width as number;
43
+ this._video.set_size_request(width, Math.round((width * 9) / 16));
44
+ this._video.autoplay = this.args.autoplay as boolean;
45
+ this._video.loop = this.args.loop as boolean;
46
+ // Graphics offload is a rendering hint with no visible effect until something is
47
+ // actually drawn, so this control exists to show that the property takes the write
48
+ // rather than to show a difference.
49
+ this._video.graphics_offload = this.args.graphicsOffload
50
+ ? Gtk.GraphicsOffloadEnabled.ENABLED
51
+ : Gtk.GraphicsOffloadEnabled.DISABLED;
52
+ }
53
+ }
54
+
55
+ GObject.type_ensure(VideoStory.$gtype);
56
+
57
+ export const VideoStories: StoryModule = { stories: [VideoStory] };
package/src/metas.ts CHANGED
@@ -7,29 +7,90 @@
7
7
  export * from './buttons/button-content.meta.js';
8
8
  export * from './buttons/button-styles.meta.js';
9
9
  export * from './buttons/menu-button.meta.js';
10
+ export * from './buttons/color-dialog-button.meta.js';
11
+ export * from './buttons/font-dialog-button.meta.js';
12
+ export * from './buttons/link-button.meta.js';
10
13
  export * from './buttons/split-button.meta.js';
14
+ export * from './buttons/scale-button.meta.js';
15
+ export * from './buttons/toggle-button.meta.js';
11
16
  export * from './buttons/toggle-group.meta.js';
17
+ export * from './controls/check-button.meta.js';
12
18
  export * from './controls/drop-down.meta.js';
13
19
  export * from './controls/entry.meta.js';
20
+ export * from './controls/gtk-spinner.meta.js';
21
+ export * from './controls/level-bar.meta.js';
22
+ export * from './controls/password-entry.meta.js';
23
+ export * from './controls/progress-bar.meta.js';
24
+ export * from './controls/scale.meta.js';
25
+ export * from './controls/search-entry.meta.js';
26
+ export * from './controls/spin-button.meta.js';
27
+ export * from './controls/switch.meta.js';
28
+ export * from './drawing/drag-icon.meta.js';
29
+ export * from './drawing/drawing-area.meta.js';
30
+ export * from './drawing/gl-area.meta.js';
31
+ export * from './drawing/graphics-offload.meta.js';
14
32
  export * from './feedback/about-dialog.meta.js';
15
33
  export * from './feedback/alert-dialog.meta.js';
34
+ export * from './feedback/dialog.meta.js';
35
+ export * from './feedback/emoji-chooser.meta.js';
36
+ export * from './feedback/gtk-about-dialog.meta.js';
37
+ export * from './feedback/page-setup-unix-dialog.meta.js';
16
38
  export * from './feedback/preferences-dialog.meta.js';
39
+ export * from './feedback/print-unix-dialog.meta.js';
40
+ export * from './feedback/shortcuts-dialog.meta.js';
17
41
  export * from './feedback/toast.meta.js';
42
+ export * from './layout/bin.meta.js';
43
+ export * from './layout/breakpoint-bin.meta.js';
44
+ export * from './layout/clamp-scrollable.meta.js';
18
45
  export * from './layout/clamp.meta.js';
46
+ export * from './layout/expander.meta.js';
19
47
  export * from './layout/header-bar.meta.js';
48
+ export * from './layout/layout-slot.meta.js';
49
+ export * from './layout/multi-layout-view.meta.js';
50
+ export * from './layout/overlay.meta.js';
51
+ export * from './layout/paned.meta.js';
52
+ export * from './layout/popover-bin.meta.js';
53
+ export * from './layout/popover-menu.meta.js';
54
+ export * from './layout/popover.meta.js';
55
+ export * from './layout/revealer.meta.js';
56
+ export * from './layout/scrollbar.meta.js';
57
+ export * from './layout/scrolled-window.meta.js';
58
+ export * from './layout/separator.meta.js';
59
+ export * from './layout/aspect-frame.meta.js';
60
+ export * from './layout/center-box.meta.js';
61
+ export * from './layout/fixed.meta.js';
62
+ export * from './layout/frame.meta.js';
63
+ export * from './layout/grid.meta.js';
20
64
  export * from './layout/toolbar-view.meta.js';
65
+ export * from './layout/viewport.meta.js';
66
+ export * from './layout/application-window.meta.js';
67
+ export * from './layout/window.meta.js';
21
68
  export * from './layout/wrap-box.meta.js';
69
+ export * from './media/image.meta.js';
70
+ export * from './media/inscription.meta.js';
71
+ export * from './media/media-controls.meta.js';
72
+ export * from './media/picture.meta.js';
73
+ export * from './media/video.meta.js';
22
74
  export * from './navigation/bottom-sheet.meta.js';
23
75
  export * from './navigation/navigation-split-view.meta.js';
24
76
  export * from './navigation/navigation-view.meta.js';
25
77
  export * from './navigation/overlay-split-view.meta.js';
78
+ export * from './navigation/popover-menu-bar.meta.js';
26
79
  export * from './navigation/sidebar.meta.js';
27
80
  export * from './overview/widgets.meta.js';
28
81
  export * from './presentation/avatar.meta.js';
29
82
  export * from './presentation/banner.meta.js';
83
+ export * from './presentation/calendar.meta.js';
84
+ export * from './presentation/column-view.meta.js';
85
+ export * from './presentation/flow-box.meta.js';
86
+ export * from './presentation/grid-view.meta.js';
87
+ export * from './presentation/list-box.meta.js';
88
+ export * from './presentation/list-view.meta.js';
30
89
  export * from './presentation/shortcut-label.meta.js';
31
90
  export * from './presentation/spinner.meta.js';
32
91
  export * from './presentation/status-page.meta.js';
92
+ export * from './presentation/tree-expander.meta.js';
93
+ export * from './presentation/window-handle.meta.js';
33
94
  export * from './presentation/window-title.meta.js';
34
95
  export * from './rows/action-row.meta.js';
35
96
  export * from './rows/button-row.meta.js';
@@ -38,10 +99,28 @@ export * from './rows/entry-row.meta.js';
38
99
  export * from './rows/expander-row.meta.js';
39
100
  export * from './rows/password-entry-row.meta.js';
40
101
  export * from './rows/preferences-group.meta.js';
102
+ export * from './rows/preferences-row.meta.js';
41
103
  export * from './rows/spin-row.meta.js';
42
104
  export * from './rows/switch-row.meta.js';
105
+ export * from './text/editable-label.meta.js';
106
+ export * from './text/search-bar.meta.js';
107
+ export * from './text/text-view.meta.js';
108
+ export * from './text/text.meta.js';
43
109
  export * from './view-switching/carousel.meta.js';
44
110
  export * from './view-switching/inline-view-switcher.meta.js';
111
+ export * from './view-switching/notebook.meta.js';
112
+ export * from './view-switching/stack-sidebar.meta.js';
113
+ export * from './view-switching/stack-switcher.meta.js';
114
+ export * from './view-switching/stack.meta.js';
115
+ export * from './view-switching/tab-bar.meta.js';
116
+ export * from './view-switching/tab-button.meta.js';
117
+ export * from './view-switching/tab-overview.meta.js';
118
+ export * from './view-switching/view-switcher-sidebar.meta.js';
45
119
  export * from './view-switching/tab-view.meta.js';
46
120
  export * from './view-switching/view-switcher-bar.meta.js';
47
121
  export * from './view-switching/view-switcher.meta.js';
122
+ export * from './windows/action-bar.meta.js';
123
+ export * from './windows/gtk-application-window.meta.js';
124
+ export * from './windows/gtk-header-bar.meta.js';
125
+ export * from './windows/gtk-window.meta.js';
126
+ export * from './windows/window-controls.meta.js';
@@ -0,0 +1,51 @@
1
+ // Shared, renderer-agnostic metadata for the Popover Menu Bar story. Imported by the GTK
2
+ // renderer (popover-menu-bar.story.ts) and the browser renderer
3
+ // (browser/navigation/popover-menu-bar.web.ts), so both expose identical controls.
4
+
5
+ import { ControlType, type StoryMeta } from '@gjsify/stories';
6
+
7
+ /**
8
+ * A menu bar's model: SUBMENUS ONLY at the top level. `GtkPopoverMenuBar` builds one item
9
+ * per toplevel submenu link and warns "Don't know how to handle this item" for anything
10
+ * else (gtkpopovermenubar.c:422-468) — which is why there is no bare item here.
11
+ */
12
+ export const POPOVER_MENU_BAR_ITEMS = [
13
+ {
14
+ label: 'File',
15
+ submenu: [
16
+ { label: 'New Window', accel: '<Control>N' },
17
+ { label: 'New Tab', accel: '<Control><Shift>N' },
18
+ { label: 'Open…', accel: '<Control>O' },
19
+ ],
20
+ },
21
+ {
22
+ label: 'Edit',
23
+ submenu: [
24
+ { label: 'Undo', accel: '<Control>Z' },
25
+ { label: 'Redo', accel: '<Control>Y' },
26
+ ],
27
+ },
28
+ { label: 'View', submenu: [{ label: 'Zoom In', accel: '<Control>plus' }, { label: 'Zoom Out' }] },
29
+ { label: 'Help', submenu: [{ label: 'Keyboard Shortcuts' }, { label: 'About' }] },
30
+ ] as const;
31
+
32
+ export const popoverMenuBarMeta: StoryMeta = {
33
+ title: 'Navigation/Popover Menu Bar',
34
+ description:
35
+ 'Gtk.PopoverMenuBar — a horizontal bar of items that pop up menus when clicked. Clicking an ' +
36
+ 'item OPENS its menu; hovering only SELECTS, and the item lights up (.active) exactly ' +
37
+ 'while its menu is up. Left and Right walk the bar and wrap.',
38
+ controls: [
39
+ {
40
+ name: 'position',
41
+ label: 'Menu position',
42
+ type: ControlType.SELECT,
43
+ options: [
44
+ { label: 'Below', value: 'bottom' },
45
+ { label: 'Above', value: 'top' },
46
+ ],
47
+ defaultValue: 'bottom',
48
+ },
49
+ { name: 'showLast', label: 'Include Help', type: ControlType.BOOLEAN, defaultValue: true },
50
+ ],
51
+ };
@@ -0,0 +1,72 @@
1
+ // Gtk.PopoverMenuBar — a bar of items that pop up menus.
2
+ // original implementation.
3
+
4
+ import Gio from 'gi://Gio?version=2.0';
5
+ import Gtk from 'gi://Gtk?version=4.0';
6
+ import GObject from 'gi://GObject?version=2.0';
7
+ import { type StoryArgs, type StoryMeta, type StoryModule, StoryWidget } from '@gjsify/storybook';
8
+ import { POPOVER_MENU_BAR_ITEMS, popoverMenuBarMeta } from './popover-menu-bar.meta.js';
9
+
10
+ /**
11
+ * Story: a Gtk.PopoverMenuBar over four submenus.
12
+ *
13
+ * `GtkPopoverMenuBar.new_from_model` (gtkpopovermenubar.c:686-703) is the ONLY way to make
14
+ * one — there is no constructor that takes children — so the model is the whole story. Each
15
+ * item's popover is `GtkPopoverMenu.new_from_model_full (…, GTK_POPOVER_MENU_NESTED)`
16
+ * (:435), pinned below the item with `has-arrow` off, which `tracker_insert` does for us.
17
+ */
18
+ export class PopoverMenuBarStory extends StoryWidget {
19
+ private _bar: Gtk.PopoverMenuBar | null = null;
20
+
21
+ static {
22
+ GObject.registerClass({ GTypeName: 'AdwStorybookPopoverMenuBar' }, PopoverMenuBarStory);
23
+ }
24
+
25
+ constructor() {
26
+ super(StoryWidget.fromMeta(PopoverMenuBarStory.getMetadata(), 'Default'));
27
+ }
28
+
29
+ static getMetadata(): StoryMeta {
30
+ return { ...popoverMenuBarMeta, component: Gtk.PopoverMenuBar.$gtype };
31
+ }
32
+
33
+ initialize(): void {
34
+ this._bar = Gtk.PopoverMenuBar.new_from_model(this._buildMenu());
35
+ this._bar.halign = Gtk.Align.START;
36
+ this._apply();
37
+ this.addContent(this._bar);
38
+ }
39
+
40
+ updateArgs(_args: StoryArgs): void {
41
+ this._apply();
42
+ }
43
+
44
+ private _buildMenu(): Gio.Menu {
45
+ const menu = new Gio.Menu();
46
+ const entries = this.args.showLast === false ? POPOVER_MENU_BAR_ITEMS.slice(0, 3) : POPOVER_MENU_BAR_ITEMS;
47
+ for (const entry of entries) {
48
+ const submenu = new Gio.Menu();
49
+ for (const item of entry.submenu)
50
+ submenu.append(item.label, `app.${item.label.toLowerCase().replace(/[^a-z0-9]+/g, '-')}`);
51
+ menu.append_submenu(entry.label, submenu);
52
+ }
53
+ return menu;
54
+ }
55
+
56
+ private _apply(): void {
57
+ if (!this._bar) return;
58
+ // Every item's popover is pinned `GTK_POS_BOTTOM` by `tracker_insert`, so this is the
59
+ // one thing about it an application actually changes: walk the bar's children and
60
+ // re-point the popover each one owns. `GtkPopoverMenuBarItem` is private, so the
61
+ // popover is reached through the widget tree rather than through a typed accessor.
62
+ const above = this.args.position === 'top';
63
+ for (let child = this._bar.get_first_child(); child !== null; child = child.get_next_sibling()) {
64
+ const popover = (child as unknown as { get_first_child(): Gtk.Popover | null }).get_first_child();
65
+ popover?.set_position(above ? Gtk.PositionType.TOP : Gtk.PositionType.BOTTOM);
66
+ }
67
+ }
68
+ }
69
+
70
+ GObject.type_ensure(PopoverMenuBarStory.$gtype);
71
+
72
+ export const PopoverMenuBarStories: StoryModule = { stories: [PopoverMenuBarStory] };
@@ -0,0 +1,28 @@
1
+ // Shared, renderer-agnostic metadata for the Calendar story. Imported by the GTK renderer
2
+ // (calendar.story.ts) and the browser renderer (browser/presentation/calendar.web.ts), so
3
+ // both expose identical controls.
4
+
5
+ import { ControlType, type StoryMeta } from '@gjsify/stories';
6
+
7
+ /**
8
+ * The date both renderings start from, in the ISO form `GDateTime` serialises to. A fixed
9
+ * date rather than "today", so a screenshot of the story is the same on every machine —
10
+ * which is the same trade the `dir` reading in `roving-focus.ts` makes.
11
+ */
12
+ export const CALENDAR_STORY_DATE = '2026-10-02';
13
+
14
+ /** The days both renderings mark, so the tint appears at the same place everywhere. */
15
+ export const CALENDAR_STORY_MARKS = [14, 25];
16
+
17
+ export const calendarMeta: StoryMeta = {
18
+ title: 'Presentation/Calendar',
19
+ description:
20
+ 'Gtk.Calendar — a six-by-seven month grid with four navigation arrows. The arrows clamp the way ' +
21
+ 'g_date_time_add_months does, so paging from the 31st lands on the 28th, and a marked day is a day ' +
22
+ 'NUMBER of the shown month, so it survives a month that has no such day.',
23
+ controls: [
24
+ { name: 'showHeading', label: 'Heading', type: ControlType.BOOLEAN, defaultValue: true },
25
+ { name: 'showDayNames', label: 'Day names', type: ControlType.BOOLEAN, defaultValue: true },
26
+ { name: 'showWeekNumbers', label: 'Week numbers', type: ControlType.BOOLEAN, defaultValue: true },
27
+ ],
28
+ };
@@ -0,0 +1,64 @@
1
+ // Gtk.Calendar — the month grid, with the marks and the four arrows.
2
+ // original implementation.
3
+
4
+ import GLib from 'gi://GLib?version=2.0';
5
+ import Gtk from 'gi://Gtk?version=4.0';
6
+ import GObject from 'gi://GObject?version=2.0';
7
+ import { type StoryArgs, type StoryMeta, type StoryModule, StoryWidget } from '@gjsify/storybook';
8
+ import { CALENDAR_STORY_DATE, CALENDAR_STORY_MARKS, calendarMeta } from './calendar.meta.js';
9
+
10
+ /** Story: a Gtk.Calendar on a fixed date, so a screenshot is the same on every machine. */
11
+ export class CalendarStory extends StoryWidget {
12
+ private _calendar: Gtk.Calendar | null = null;
13
+ private _marked = false;
14
+
15
+ static {
16
+ GObject.registerClass({ GTypeName: 'AdwStorybookCalendar' }, CalendarStory);
17
+ }
18
+
19
+ constructor() {
20
+ super(StoryWidget.fromMeta(CalendarStory.getMetadata(), 'Default'));
21
+ }
22
+
23
+ static getMetadata(): StoryMeta {
24
+ return { ...calendarMeta, component: Gtk.Calendar.$gtype };
25
+ }
26
+
27
+ initialize(): void {
28
+ this._calendar = new Gtk.Calendar({ hexpand: false });
29
+ // `GtkCalendar:date` holds a GDateTime; the deprecated year/month/day halves are the
30
+ // same write, and a template cannot construct one, so the story sets the object.
31
+ this._calendar.date = parseIso(CALENDAR_STORY_DATE);
32
+ this._apply();
33
+ this.addContent(this._calendar);
34
+ }
35
+
36
+ updateArgs(_args: StoryArgs): void {
37
+ this._apply();
38
+ }
39
+
40
+ private _apply(): void {
41
+ if (!this._calendar) return;
42
+ // Both default TRUE and one defaults FALSE (gtkcalendar.c:440-460), so the args write
43
+ // the values rather than the absences.
44
+ this._calendar.show_heading = this.args.showHeading as boolean;
45
+ this._calendar.show_day_names = this.args.showDayNames as boolean;
46
+ this._calendar.show_week_numbers = this.args.showWeekNumbers as boolean;
47
+
48
+ // `mark_day()` is IDEMPOTENT and the table is 31 slots, so marking twice is silent
49
+ // (gtkcalendar.c:1657) — but clearing first is what makes an arg change visible at all.
50
+ if (this._marked) this._calendar.clear_marks();
51
+ for (const day of CALENDAR_STORY_MARKS) this._calendar.mark_day(day);
52
+ this._marked = true;
53
+ }
54
+ }
55
+
56
+ /** `YYYY-MM-DD` → a local `GDateTime`, which is what `GtkCalendar:date` holds. */
57
+ function parseIso(iso: string): GLib.DateTime {
58
+ const [year, month, day] = iso.split('-').map(Number);
59
+ return GLib.DateTime.new_local(year!, month!, day!, 0, 0, 0);
60
+ }
61
+
62
+ GObject.type_ensure(CalendarStory.$gtype);
63
+
64
+ export const CalendarStories: StoryModule = { stories: [CalendarStory] };
@@ -0,0 +1,30 @@
1
+ // Shared, renderer-agnostic metadata for the Column View story. Imported by the GTK
2
+ // renderer (column-view.story.ts) and the browser renderer
3
+ // (browser/presentation/column-view.web.ts), so both expose identical controls.
4
+
5
+ import { ControlType, type StoryMeta } from '@gjsify/stories';
6
+
7
+ /** Two-field items, which is what makes a two-column table out of the portable list. */
8
+ export const COLUMN_VIEW_ITEMS = [
9
+ { value: 'de', label: 'German' },
10
+ { value: 'fr', label: 'French' },
11
+ { value: 'nl', label: 'Dutch' },
12
+ { value: 'pt', label: 'Portuguese' },
13
+ ];
14
+
15
+ export const columnViewMeta: StoryMeta = {
16
+ title: 'Presentation/Column View',
17
+ description:
18
+ 'Gtk.ColumnView — a list view with a header and one cell per column. Clicking a sortable header ' +
19
+ 'sets the sort and inverts it on the next click; the view itself reorders nothing.',
20
+ controls: [
21
+ { name: 'showRowSeparators', label: 'Row separators', type: ControlType.BOOLEAN, defaultValue: true },
22
+ {
23
+ name: 'showColumnSeparators',
24
+ label: 'Column separators',
25
+ type: ControlType.BOOLEAN,
26
+ defaultValue: false,
27
+ },
28
+ { name: 'reorderable', label: 'Drag columns to reorder', type: ControlType.BOOLEAN, defaultValue: false },
29
+ ],
30
+ };
@@ -0,0 +1,73 @@
1
+ // Gtk.ColumnView — a list view with a header and one cell per column.
2
+ // original implementation.
3
+
4
+ import Gtk from 'gi://Gtk?version=4.0';
5
+ import GObject from 'gi://GObject?version=2.0';
6
+ import { type StoryArgs, type StoryMeta, type StoryModule, StoryWidget } from '@gjsify/storybook';
7
+ import { COLUMN_VIEW_ITEMS, columnViewMeta } from './column-view.meta.js';
8
+
9
+ /**
10
+ * Story: a Gtk.ColumnView of two columns over a Gtk.StringList of labels.
11
+ *
12
+ * The model is a string list, as the browser rendering's is, so both draw the same two
13
+ * fields: the Name column shows the string and the Code column the two-letter id the
14
+ * story holds beside it. A richer item type would need a GObject per row, which is the
15
+ * authoring step the portable list exists to avoid.
16
+ */
17
+ export class ColumnViewStory extends StoryWidget {
18
+ private _view: Gtk.ColumnView | null = null;
19
+
20
+ static {
21
+ GObject.registerClass({ GTypeName: 'AdwStorybookColumnView' }, ColumnViewStory);
22
+ }
23
+
24
+ constructor() {
25
+ super(StoryWidget.fromMeta(ColumnViewStory.getMetadata(), 'Default'));
26
+ }
27
+
28
+ static getMetadata(): StoryMeta {
29
+ return { ...columnViewMeta, component: Gtk.ColumnView.$gtype };
30
+ }
31
+
32
+ initialize(): void {
33
+ const model = Gtk.StringList.new(COLUMN_VIEW_ITEMS.map((item) => item.label));
34
+ this._view = new Gtk.ColumnView({ model: Gtk.SingleSelection.new(model), vexpand: true });
35
+ this._view.append_column(this._column('Name', (item) => item.label));
36
+ this._view.append_column(this._column('Code', (item) => item.value));
37
+
38
+ const scroller = new Gtk.ScrolledWindow({ child: this._view, heightRequest: 200, hexpand: true });
39
+ this._apply();
40
+ this.addContent(scroller);
41
+ }
42
+
43
+ updateArgs(_args: StoryArgs): void {
44
+ this._apply();
45
+ }
46
+
47
+ private _apply(): void {
48
+ if (!this._view) return;
49
+ this._view.showRowSeparators = this.args.showRowSeparators as boolean;
50
+ this._view.showColumnSeparators = this.args.showColumnSeparators as boolean;
51
+ this._view.reorderable = this.args.reorderable as boolean;
52
+ }
53
+
54
+ /** One column, with the factory that reads the field this column draws. */
55
+ private _column(title: string, read: (item: { value: string; label: string }) => string): Gtk.ColumnViewColumn {
56
+ const factory = new Gtk.SignalListItemFactory();
57
+ factory.connect('setup', (_factory, item: GObject.Object) => {
58
+ (item as Gtk.ListItem).child = new Gtk.Label({ xalign: 0 });
59
+ });
60
+ factory.connect('bind', (_factory, item: GObject.Object) => {
61
+ const listItem = item as Gtk.ListItem;
62
+ const label = listItem.child as Gtk.Label;
63
+ const text = (listItem.item as Gtk.StringObject).string;
64
+ const entry = COLUMN_VIEW_ITEMS.find((candidate) => candidate.label === text);
65
+ label.label = entry === undefined ? text : read(entry);
66
+ });
67
+ return new Gtk.ColumnViewColumn({ title, factory, expand: true });
68
+ }
69
+ }
70
+
71
+ GObject.type_ensure(ColumnViewStory.$gtype);
72
+
73
+ export const ColumnViewStories: StoryModule = { stories: [ColumnViewStory] };
@@ -0,0 +1,51 @@
1
+ // Shared, renderer-agnostic metadata for the Flow Box story. Imported by the GTK renderer
2
+ // (flow-box.story.ts) and the browser renderer (browser/presentation/flow-box.web.ts), so
3
+ // both expose identical controls.
4
+
5
+ import { ControlType, type StoryMeta } from '@gjsify/stories';
6
+
7
+ /** The cells both renderings build — more than a full line, so the reflow is visible. */
8
+ export const FLOW_BOX_CELLS = ['Red', 'Orange', 'Yellow', 'Green', 'Blue', 'Purple', 'Teal', 'Grey'] as const;
9
+
10
+ /** The four `Gtk.SelectionMode` values, under the nicks the shared selection module uses. */
11
+ export const FLOW_BOX_SELECTION_MODES = [
12
+ { label: 'Single', value: 'single' },
13
+ { label: 'Multiple', value: 'multiple' },
14
+ { label: 'Browse', value: 'browse' },
15
+ { label: 'None', value: 'none' },
16
+ ];
17
+
18
+ export const flowBoxMeta: StoryMeta = {
19
+ title: 'Presentation/Flow Box',
20
+ description:
21
+ 'Gtk.FlowBox — the same selectable rows a list box holds, reflowed into as many per line as the ' +
22
+ 'width fits. HORIZONTAL is the default here, unlike every GtkOrientable list base; the arrows move ' +
23
+ 'by visual position, so the cross-line key lands in the same column of the next line.',
24
+ controls: [
25
+ {
26
+ name: 'selectionMode',
27
+ label: 'Selection',
28
+ type: ControlType.SELECT,
29
+ options: FLOW_BOX_SELECTION_MODES,
30
+ defaultValue: 'multiple',
31
+ },
32
+ {
33
+ name: 'orientation',
34
+ label: 'Orientation',
35
+ type: ControlType.SELECT,
36
+ options: [
37
+ { label: 'Horizontal', value: 'horizontal' },
38
+ { label: 'Vertical', value: 'vertical' },
39
+ ],
40
+ defaultValue: 'horizontal',
41
+ },
42
+ {
43
+ name: 'maxChildrenPerLine',
44
+ label: 'Max children per line',
45
+ type: ControlType.NUMBER,
46
+ defaultValue: 3,
47
+ },
48
+ { name: 'homogeneous', label: 'Homogeneous', type: ControlType.BOOLEAN, defaultValue: false },
49
+ { name: 'rowSpacing', label: 'Row spacing', type: ControlType.NUMBER, defaultValue: 6 },
50
+ ],
51
+ };
@@ -0,0 +1,72 @@
1
+ // Gtk.FlowBox — the same selectable rows a list box holds, reflowed into as many per line
2
+ // as the width fits.
3
+ // original implementation.
4
+
5
+ import Gtk from 'gi://Gtk?version=4.0';
6
+ import GObject from 'gi://GObject?version=2.0';
7
+ import { type StoryArgs, type StoryMeta, type StoryModule, StoryWidget } from '@gjsify/storybook';
8
+ import { FLOW_BOX_CELLS, flowBoxMeta } from './flow-box.meta.js';
9
+
10
+ /** Story: a Gtk.FlowBox of GtkFlowBoxChild cells, its line arithmetic driven by args. */
11
+ export class FlowBoxStory extends StoryWidget {
12
+ private _box: Gtk.FlowBox | null = null;
13
+
14
+ static {
15
+ GObject.registerClass({ GTypeName: 'AdwStorybookFlowBox' }, FlowBoxStory);
16
+ }
17
+
18
+ constructor() {
19
+ super(StoryWidget.fromMeta(FlowBoxStory.getMetadata(), 'Default'));
20
+ }
21
+
22
+ static getMetadata(): StoryMeta {
23
+ return { ...flowBoxMeta, component: Gtk.FlowBox.$gtype };
24
+ }
25
+
26
+ initialize(): void {
27
+ this._box = new Gtk.FlowBox({ hexpand: true, vexpand: true });
28
+ // `GtkFlowBoxChild` is a Bin, so the wrapped widget goes through `set_child()` — the
29
+ // wrapper the markup form writes as `<gtk-flow-box-child>`.
30
+ for (const name of FLOW_BOX_CELLS) {
31
+ this._box.append(new Gtk.FlowBoxChild({ child: new Gtk.Label({ label: name }) }));
32
+ }
33
+ this._apply();
34
+ this.addContent(this._box);
35
+ }
36
+
37
+ updateArgs(_args: StoryArgs): void {
38
+ this._apply();
39
+ }
40
+
41
+ private _apply(): void {
42
+ if (!this._box) return;
43
+ this._box.selection_mode = this._selectionMode();
44
+ // HORIZONTAL is the default (gtkflowbox.c:3981) and the default the meta starts from,
45
+ // so the arg only has to write the property, not its absence.
46
+ this._box.orientation =
47
+ (this.args.orientation as string) === 'vertical' ? Gtk.Orientation.VERTICAL : Gtk.Orientation.HORIZONTAL;
48
+ // The ParamSpec minimum is 1 (gtkflowbox.c:3767-3770), so 0 from a cleared arg is
49
+ // clamped the way the setter would clamp it rather than silently wrapping everything
50
+ // onto one line.
51
+ this._box.max_children_per_line = Math.max(1, Math.trunc(this.args.maxChildrenPerLine as number));
52
+ this._box.homogeneous = this.args.homogeneous as boolean;
53
+ this._box.row_spacing = Math.max(0, Math.trunc(this.args.rowSpacing as number));
54
+ }
55
+
56
+ private _selectionMode(): Gtk.SelectionMode {
57
+ switch (this.args.selectionMode as string) {
58
+ case 'single':
59
+ return Gtk.SelectionMode.SINGLE;
60
+ case 'browse':
61
+ return Gtk.SelectionMode.BROWSE;
62
+ case 'none':
63
+ return Gtk.SelectionMode.NONE;
64
+ default:
65
+ return Gtk.SelectionMode.MULTIPLE;
66
+ }
67
+ }
68
+ }
69
+
70
+ GObject.type_ensure(FlowBoxStory.$gtype);
71
+
72
+ export const FlowBoxStories: StoryModule = { stories: [FlowBoxStory] };
@@ -0,0 +1,28 @@
1
+ // Shared, renderer-agnostic metadata for the Grid View story. Imported by the GTK
2
+ // renderer (grid-view.story.ts) and the browser renderer
3
+ // (browser/presentation/grid-view.web.ts), so both expose identical controls.
4
+
5
+ import { ControlType, type StoryMeta } from '@gjsify/stories';
6
+
7
+ import { LIST_VIEW_SELECTION_MODES } from './list-view.meta.js';
8
+
9
+ /** Enough items that a column-count change is visible as a reflow rather than as a resize. */
10
+ export const GRID_VIEW_ITEMS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August'] as const;
11
+
12
+ export const gridViewMeta: StoryMeta = {
13
+ title: 'Presentation/Grid View',
14
+ description:
15
+ 'Gtk.GridView — the same model and factory as a list view, reflowed into as many equal columns as ' +
16
+ 'the width fits, between min-columns and max-columns.',
17
+ controls: [
18
+ { name: 'maxColumns', label: 'Max columns', type: ControlType.NUMBER, defaultValue: 4 },
19
+ { name: 'minColumns', label: 'Min columns', type: ControlType.NUMBER, defaultValue: 1 },
20
+ {
21
+ name: 'selectionMode',
22
+ label: 'Selection',
23
+ type: ControlType.SELECT,
24
+ options: LIST_VIEW_SELECTION_MODES,
25
+ defaultValue: 'single',
26
+ },
27
+ ],
28
+ };