@icij/murmur-next 4.0.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 (296) hide show
  1. package/.github/workflows/deploy-github-pages.yaml +50 -0
  2. package/.storybook/app.scss +14 -0
  3. package/.storybook/doc_variables.scss +20 -0
  4. package/.storybook/main.ts +35 -0
  5. package/.storybook/preview-head.html +2 -0
  6. package/.storybook/preview.ts +32 -0
  7. package/README.md +71 -0
  8. package/deploy.js +15 -0
  9. package/docs/components/ApiTable.vue +171 -0
  10. package/docs/components/App.vue +146 -0
  11. package/docs/components/CollapsibleBlock.vue +122 -0
  12. package/docs/components/DocsHeader.vue +68 -0
  13. package/docs/components/DocsMenu.vue +201 -0
  14. package/docs/components/DocsMenuSection.vue +109 -0
  15. package/docs/components/EditLink.vue +49 -0
  16. package/docs/components/OutboundLink.vue +13 -0
  17. package/docs/components/PalettePresenter.vue +96 -0
  18. package/docs/components/RepositoryLink.vue +28 -0
  19. package/docs/components/SampleCard.vue +119 -0
  20. package/docs/main.js +42 -0
  21. package/docs/pages/components/accordion/doc.md +96 -0
  22. package/docs/pages/components/active-text-truncate/doc.md +44 -0
  23. package/docs/pages/components/advanced-link-form/doc.md +105 -0
  24. package/docs/pages/components/brand/doc.md +30 -0
  25. package/docs/pages/components/brand-expansion/doc.md +70 -0
  26. package/docs/pages/components/confirm-button/doc.md +91 -0
  27. package/docs/pages/components/content-placeholder/doc.md +16 -0
  28. package/docs/pages/components/custom-pagination/doc.md +61 -0
  29. package/docs/pages/components/digits-input/doc.md +28 -0
  30. package/docs/pages/components/donate-form/doc.md +20 -0
  31. package/docs/pages/components/embed-form/doc.md +22 -0
  32. package/docs/pages/components/embeddable-footer/doc.md +60 -0
  33. package/docs/pages/components/follow-us-popover/doc.md +5 -0
  34. package/docs/pages/components/generic-footer/doc.md +21 -0
  35. package/docs/pages/components/generic-header/doc.md +24 -0
  36. package/docs/pages/components/haptic-copy/doc.md +27 -0
  37. package/docs/pages/components/imddb-header/doc.md +23 -0
  38. package/docs/pages/components/ordinal-legend/doc.md +44 -0
  39. package/docs/pages/components/range-picker/doc.md +86 -0
  40. package/docs/pages/components/responsive-iframe/doc.md +13 -0
  41. package/docs/pages/components/scale-legend/doc.md +65 -0
  42. package/docs/pages/components/secret-input/doc.md +12 -0
  43. package/docs/pages/components/selectable-dropdown/doc.md +156 -0
  44. package/docs/pages/components/sharing-options/doc.md +13 -0
  45. package/docs/pages/components/sharing-options-link/doc.md +36 -0
  46. package/docs/pages/components/sign-up-form/doc.md +13 -0
  47. package/docs/pages/components/slide-up-down/doc.md +28 -0
  48. package/docs/pages/components/textured-deck/doc.md +78 -0
  49. package/docs/pages/components/tiny-pagination/doc.md +92 -0
  50. package/docs/pages/datavisualisation/bars/doc.md +110 -0
  51. package/docs/pages/datavisualisation/columns/doc.md +165 -0
  52. package/docs/pages/datavisualisation/lines/doc.md +139 -0
  53. package/docs/pages/datavisualisation/stacked-bar/doc.md +160 -0
  54. package/docs/pages/datavisualisation/stacked-column/doc.md +191 -0
  55. package/docs/pages/getting-started/about-icij/doc.md +13 -0
  56. package/docs/pages/getting-started/custom-bootstrap/doc.md +36 -0
  57. package/docs/pages/getting-started/installation-guide/doc.md +59 -0
  58. package/docs/pages/getting-started/internationalization/doc.md +74 -0
  59. package/docs/pages/maps/choropleth-map/doc.md +420 -0
  60. package/docs/pages/maps/choropleth-map-annotation/doc.md +373 -0
  61. package/docs/pages/maps/symbol-map/doc.md +203 -0
  62. package/docs/pages/structure/breakpoints/doc.md +3 -0
  63. package/docs/pages/structure/grid/doc.md +3 -0
  64. package/docs/pages/utilities/assets/doc.md +138 -0
  65. package/docs/pages/utilities/config/doc.md +52 -0
  66. package/docs/pages/utilities/iframes/doc.md +3 -0
  67. package/docs/pages/visual/colors/doc.md +31 -0
  68. package/docs/pages/visual/iconography/doc.md +56 -0
  69. package/docs/pages/visual/states/doc.md +77 -0
  70. package/docs/pages/visual/themes/doc.md +3 -0
  71. package/docs/pages/visual/typography/doc.md +71 -0
  72. package/docs/routes.js +25 -0
  73. package/docs/store/index.js +21 -0
  74. package/docs/styles/app.scss +36 -0
  75. package/docs/styles/variables.scss +20 -0
  76. package/lib/assets/images/icij-full-white.svg +6 -0
  77. package/lib/assets/images/icij-full.svg +6 -0
  78. package/lib/assets/images/icij.png +0 -0
  79. package/lib/assets/images/icij.svg +46 -0
  80. package/lib/assets/images/icij@2x.png +0 -0
  81. package/lib/assets/images/murmur-dark.png +0 -0
  82. package/lib/assets/images/murmur-dark.svg +79 -0
  83. package/lib/assets/images/murmur-white.png +0 -0
  84. package/lib/assets/images/murmur-white.svg +68 -0
  85. package/lib/components/AccordionStep.vue +128 -0
  86. package/lib/components/AccordionWrapper.vue +138 -0
  87. package/lib/components/ActiveTextTruncate.vue +258 -0
  88. package/lib/components/AdvancedLinkForm.vue +273 -0
  89. package/lib/components/Brand.vue +150 -0
  90. package/lib/components/BrandExpansion.vue +237 -0
  91. package/lib/components/ConfirmButton.vue +204 -0
  92. package/lib/components/ContentPlaceholder.vue +100 -0
  93. package/lib/components/CustomPagination.vue +225 -0
  94. package/lib/components/DigitsInput.vue +180 -0
  95. package/lib/components/DonateForm.vue +367 -0
  96. package/lib/components/EmbedForm.vue +173 -0
  97. package/lib/components/EmbeddableFooter.vue +201 -0
  98. package/lib/components/Fa.js +3 -0
  99. package/lib/components/FollowUsPopover.vue +117 -0
  100. package/lib/components/GenericFooter.vue +218 -0
  101. package/lib/components/GenericHeader.vue +259 -0
  102. package/lib/components/HapticCopy.vue +256 -0
  103. package/lib/components/ImddbHeader.vue +336 -0
  104. package/lib/components/OrdinalLegend.vue +164 -0
  105. package/lib/components/RangePicker.vue +430 -0
  106. package/lib/components/ResponsiveIframe.vue +48 -0
  107. package/lib/components/ScaleLegend.vue +230 -0
  108. package/lib/components/SecretInput.vue +132 -0
  109. package/lib/components/SelectableDropdown.vue +368 -0
  110. package/lib/components/SharingOptions.vue +230 -0
  111. package/lib/components/SharingOptionsLink.vue +259 -0
  112. package/lib/components/SignUpForm.vue +181 -0
  113. package/lib/components/SlideUpDown.vue +131 -0
  114. package/lib/components/TexturedDeck.vue +101 -0
  115. package/lib/components/TinyPagination.vue +268 -0
  116. package/lib/components/index.js +31 -0
  117. package/lib/composables/chart.ts +182 -0
  118. package/lib/composables/resizeObserver.ts +37 -0
  119. package/lib/composables/sendEmail.ts +50 -0
  120. package/lib/config.default.ts +33 -0
  121. package/lib/config.ts +70 -0
  122. package/lib/d3-geo-projection.d.ts +1 -0
  123. package/lib/datavisualisations/BarChart.vue +275 -0
  124. package/lib/datavisualisations/ColumnChart.vue +527 -0
  125. package/lib/datavisualisations/LineChart.vue +274 -0
  126. package/lib/datavisualisations/StackedBarChart.vue +614 -0
  127. package/lib/datavisualisations/StackedColumnChart.vue +640 -0
  128. package/lib/datavisualisations/index.js +5 -0
  129. package/lib/enums.ts +25 -0
  130. package/lib/i18n.ts +16 -0
  131. package/lib/keys.ts +2 -0
  132. package/lib/locales/en.json +140 -0
  133. package/lib/locales/fr.json +117 -0
  134. package/lib/locales/locales/en.json +140 -0
  135. package/lib/locales/locales/fr.json +117 -0
  136. package/lib/main.ts +87 -0
  137. package/lib/maps/ChoroplethMap.vue +825 -0
  138. package/lib/maps/ChoroplethMapAnnotation.vue +336 -0
  139. package/lib/maps/SymbolMap.vue +628 -0
  140. package/lib/maps/index.js +3 -0
  141. package/lib/querystring-es3.d.ts +1 -0
  142. package/lib/shims-bootstrap-vue.d.ts +5 -0
  143. package/lib/shims-tsx.d.ts +11 -0
  144. package/lib/shims-vue.d.ts +14 -0
  145. package/lib/styles/functions.scss +20 -0
  146. package/lib/styles/lib.scss +19 -0
  147. package/lib/styles/mixins.scss +37 -0
  148. package/lib/styles/utilities.scss +18 -0
  149. package/lib/styles/variables.scss +94 -0
  150. package/lib/styles/variables_dark.scss +1 -0
  151. package/lib/types.ts +46 -0
  152. package/lib/utils/animation.ts +24 -0
  153. package/lib/utils/assets.ts +46 -0
  154. package/lib/utils/clipboard.ts +41 -0
  155. package/lib/utils/iframe-resizer.ts +49 -0
  156. package/lib/utils/placeholder.ts +66 -0
  157. package/lib/utils/placeholderTypes.ts +21 -0
  158. package/lib/utils/strings.ts +8 -0
  159. package/loaders/highlight-loader.js +13 -0
  160. package/loaders/markdown-loader.js +91 -0
  161. package/loaders/metadata-loader.js +18 -0
  162. package/loaders/sass-extract-loader.js +14 -0
  163. package/loaders/vue-docgen-loader.js +14 -0
  164. package/package.json +96 -0
  165. package/plugins/MdPluginTypes.ts +10 -0
  166. package/plugins/docs.ts +50 -0
  167. package/plugins/front-matter.ts +36 -0
  168. package/plugins/highlight.ts +27 -0
  169. package/plugins/markdown-it/api-table.ts +25 -0
  170. package/plugins/markdown-it/sample-card.ts +31 -0
  171. package/plugins/plugin-delete.ts +47 -0
  172. package/plugins/plugin-docgen.ts +23 -0
  173. package/plugins/sass-vars.ts +25 -0
  174. package/plugins/vue-docgen.ts +29 -0
  175. package/public/android-chrome-192x192.png +0 -0
  176. package/public/android-chrome-512x512.png +0 -0
  177. package/public/apple-touch-icon.png +0 -0
  178. package/public/assets/img/arrow-bottom.svg +3 -0
  179. package/public/assets/img/texture-brick-black.jpg +0 -0
  180. package/public/assets/img/texture-brick.jpg +0 -0
  181. package/public/assets/img/texture-carbon-black.jpg +0 -0
  182. package/public/assets/img/texture-carbon.jpg +0 -0
  183. package/public/assets/img/texture-crack-black.jpg +0 -0
  184. package/public/assets/img/texture-crack.jpg +0 -0
  185. package/public/assets/img/texture-rock-black.jpg +0 -0
  186. package/public/assets/img/texture-rock.jpg +0 -0
  187. package/public/assets/img/texture-sand-black.jpg +0 -0
  188. package/public/assets/img/texture-sand.jpg +0 -0
  189. package/public/assets/img/texture-silk-black.jpg +0 -0
  190. package/public/assets/img/texture-silk.jpg +0 -0
  191. package/public/assets/topojson/france-departments.json +1 -0
  192. package/public/assets/topojson/paris-arrondissements.json +1 -0
  193. package/public/assets/topojson/world-countries-sans-antarctica.json +1 -0
  194. package/public/favicon-16x16.png +0 -0
  195. package/public/favicon-32x32.png +0 -0
  196. package/public/favicon.ico +0 -0
  197. package/public/site.webmanifest +1 -0
  198. package/stories/assets/code-brackets.svg +1 -0
  199. package/stories/assets/colors.svg +1 -0
  200. package/stories/assets/comments.svg +1 -0
  201. package/stories/assets/direction.svg +1 -0
  202. package/stories/assets/flow.svg +1 -0
  203. package/stories/assets/plugin.svg +1 -0
  204. package/stories/assets/repo.svg +1 -0
  205. package/stories/assets/stackalt.svg +1 -0
  206. package/stories/getting-started/about-icij.mdx +14 -0
  207. package/stories/getting-started/custom-bootstrap.mdx +23 -0
  208. package/stories/getting-started/installation-guide.mdx +62 -0
  209. package/stories/getting-started/internationalization.mdx +63 -0
  210. package/stories/murmur/components/AccordionStep.stories.ts +33 -0
  211. package/stories/murmur/components/AccordionWrapper.stories.ts +69 -0
  212. package/stories/murmur/components/ActiveTextTruncate.stories.ts +32 -0
  213. package/stories/murmur/components/AdvancedLinkForm.stories.ts +77 -0
  214. package/stories/murmur/components/Brand.stories.ts +30 -0
  215. package/stories/murmur/components/BrandExpansion.stories.ts +41 -0
  216. package/stories/murmur/components/ConfirmButton.stories.ts +40 -0
  217. package/stories/murmur/components/ContentPlaceholder.stories.ts +41 -0
  218. package/stories/murmur/components/CustomPagination.stories.ts +42 -0
  219. package/stories/murmur/components/DigitsInput.stories.ts +29 -0
  220. package/stories/murmur/components/DonateForm.stories.ts +29 -0
  221. package/stories/murmur/components/EmbedForm.stories.ts +35 -0
  222. package/stories/murmur/components/EmbeddableFooter.stories.ts +59 -0
  223. package/stories/murmur/components/FollowUsPopover.stories.ts +24 -0
  224. package/stories/murmur/components/GenericFooter.stories.ts +27 -0
  225. package/stories/murmur/components/GenericHeader.stories.ts +27 -0
  226. package/stories/murmur/components/HapticCopy.stories.ts +40 -0
  227. package/stories/murmur/components/ImddbHeader.stories.ts +27 -0
  228. package/stories/murmur/components/OrdinalLegend.stories.ts +49 -0
  229. package/stories/murmur/components/RangePicker.stories.ts +98 -0
  230. package/stories/murmur/components/ResponsiveIframe.stories.ts +24 -0
  231. package/stories/murmur/components/ScaleLegend.stories.ts +65 -0
  232. package/stories/murmur/components/SecretInput.stories.ts +60 -0
  233. package/stories/murmur/components/SelectableDropdown.stories.ts +143 -0
  234. package/stories/murmur/components/SharingOptions.stories.ts +32 -0
  235. package/stories/murmur/components/SharingOptionsLink.stories.ts +53 -0
  236. package/stories/murmur/components/SignUpForm.stories.ts +51 -0
  237. package/stories/murmur/components/SlideUpDown.stories.ts +32 -0
  238. package/stories/murmur/components/TexturedDeck.stories.ts +83 -0
  239. package/stories/murmur/components/TinyPagination.stories.ts +65 -0
  240. package/stories/murmur/datavisualisations/BarChart.stories.ts +54 -0
  241. package/stories/murmur/datavisualisations/ColumnChart.stories.ts +88 -0
  242. package/stories/murmur/datavisualisations/LineChart.stories.ts +139 -0
  243. package/stories/murmur/datavisualisations/StackedBarChart.stories.ts +199 -0
  244. package/stories/murmur/datavisualisations/StackedColumnChart.stories.ts +136 -0
  245. package/stories/murmur/decorators.ts +108 -0
  246. package/stories/murmur/maps/ChoroplethMap.stories.ts +440 -0
  247. package/stories/murmur/maps/ChoroplethMapAnnotation.stories.ts +26 -0
  248. package/stories/murmur/maps/SymbolMap.stories.ts +24 -0
  249. package/stories/murmur/utils.ts +7 -0
  250. package/tests/unit/components/AccordionStep.spec.ts +157 -0
  251. package/tests/unit/components/AccordionWrapper.spec.ts +57 -0
  252. package/tests/unit/components/ActiveTextTruncate.spec.js +30 -0
  253. package/tests/unit/components/AdvancedLinkForm.spec.js +124 -0
  254. package/tests/unit/components/Brand.spec.js +50 -0
  255. package/tests/unit/components/ContentPlaceholder.spec.js +29 -0
  256. package/tests/unit/components/CustomPagination.spec.js +72 -0
  257. package/tests/unit/components/DigitsInput.spec.ts +157 -0
  258. package/tests/unit/components/DonateForm.spec.js +149 -0
  259. package/tests/unit/components/EmbedForm.spec.js +108 -0
  260. package/tests/unit/components/EmbeddableFooter.spec.js +11 -0
  261. package/tests/unit/components/Fa.spec.js +18 -0
  262. package/tests/unit/components/FollowUsPopover.spec.js +29 -0
  263. package/tests/unit/components/GenericFooter.spec.js +29 -0
  264. package/tests/unit/components/GenericHeader.spec.js +104 -0
  265. package/tests/unit/components/HapticCopy.spec.js +123 -0
  266. package/tests/unit/components/ImddbHeader.spec.js +96 -0
  267. package/tests/unit/components/OrdinalLegend.spec.js +120 -0
  268. package/tests/unit/components/RangePicker.spec.ts +87 -0
  269. package/tests/unit/components/ResponsiveIframe.spec.js +20 -0
  270. package/tests/unit/components/ScaleLegend.spec.js +139 -0
  271. package/tests/unit/components/SecretInput.spec.js +81 -0
  272. package/tests/unit/components/SelectableDropdown.spec.js +160 -0
  273. package/tests/unit/components/SharingOptions.spec.js +125 -0
  274. package/tests/unit/components/SharingOptionsLink.spec.js +184 -0
  275. package/tests/unit/components/SignUpForm.spec.js +145 -0
  276. package/tests/unit/components/SlideUpDown.spec.js +59 -0
  277. package/tests/unit/components/TinyPagination.spec.js +46 -0
  278. package/tests/unit/config.spec.js +136 -0
  279. package/tests/unit/datavisualisations/BarChart.spec.js +63 -0
  280. package/tests/unit/datavisualisations/ColumnChart.spec.js +344 -0
  281. package/tests/unit/datavisualisations/LineChart.spec.js +155 -0
  282. package/tests/unit/datavisualisations/StackedBarChart.spec.js +294 -0
  283. package/tests/unit/datavisualisations/StackedColumnChart.spec.js +443 -0
  284. package/tests/unit/i18n.spec.ts +19 -0
  285. package/tests/unit/main.spec.js +82 -0
  286. package/tests/unit/maps/ChoroplethMap.spec.js +214 -0
  287. package/tests/unit/maps/ChoroplethMapAnnotation.spec.ts +186 -0
  288. package/tests/unit/maps/SymbolMap.spec.js +92 -0
  289. package/tests/unit/require.spec.js +22 -0
  290. package/tests/unit/setup.js +13 -0
  291. package/tests/unit/utils/assets.spec.js +61 -0
  292. package/tests/unit/utils/clipboard.spec.js +18 -0
  293. package/tests/unit/utils/iframe-resizer.spec.js +71 -0
  294. package/tsconfig.json +35 -0
  295. package/vite.config.ts +79 -0
  296. package/vitest.config.ts +19 -0
@@ -0,0 +1,430 @@
1
+ <script lang="ts">
2
+ import {defineComponent, VNode, DirectiveBinding, PropType, ref, watch, computed, onBeforeMount, toRef} from 'vue'
3
+ import { faGripLinesVertical } from '@fortawesome/free-solid-svg-icons/faGripLinesVertical'
4
+ import { clamp, get, has, invoke, round } from 'lodash'
5
+
6
+ import Fa, { library } from './Fa'
7
+
8
+ import type { Variant } from '@/types'
9
+
10
+ /**
11
+ * A component to wrap an HTML element with a range picker overlay.
12
+ */
13
+ export default defineComponent({
14
+ name: 'RangePicker',
15
+ components: {
16
+ Fa
17
+ },
18
+ directives: {
19
+ draggable: {
20
+
21
+ mounted(el: HTMLElement, binding: DirectiveBinding, vnode: VNode): void {
22
+ let startX: number, initialClientX: number
23
+ const relative = binding.modifiers?.relative ?? false
24
+
25
+ // Emit an event to the parent component
26
+ function emitEvent({ name, data = null }: { name: string; data?: any }) {
27
+ vnode.el.dispatchEvent(new CustomEvent(name,{detail: data}))
28
+ //const handlers = get(vnode, 'data.on') ?? get(vnode, 'componentOptions.listeners')
29
+ /*if (has(handlers, name)) {
30
+ invoke(handlers, `${name}.fns`, data)
31
+ }*/
32
+ }
33
+
34
+ // Handle the dragging of the element
35
+ function move(event: MouseEvent | TouchEvent) {
36
+ const clientX = event instanceof MouseEvent ? event.clientX : event.touches[0].clientX
37
+ const offset = relative ? el.offsetWidth : 0
38
+ const maxX = binding.instance?.rangeWidth() - offset
39
+ const data = clamp(startX + clientX - initialClientX, 0, maxX)
40
+ emitEvent({ name: 'dragged', data })
41
+ return false
42
+ }
43
+
44
+ // Clean up listeners once the dragging ends
45
+ function end(event: MouseEvent | TouchEvent) {
46
+ emitEvent({ name: 'ended' })
47
+ if (event instanceof MouseEvent) {
48
+ document.removeEventListener('mousemove', move)
49
+ document.removeEventListener('mouseup', end)
50
+ } else {
51
+ document.removeEventListener('touchmove', move)
52
+ document.removeEventListener('touchend', end)
53
+ }
54
+ }
55
+
56
+ // Register listeners when dragging start
57
+ function start(event: MouseEvent | TouchEvent) {
58
+ emitEvent({ name: 'started' })
59
+ startX = el.offsetLeft
60
+ if (event instanceof MouseEvent) {
61
+ initialClientX = event.clientX
62
+ document.addEventListener('mousemove', move)
63
+ document.addEventListener('mouseup', end)
64
+ } else {
65
+ initialClientX = event.touches[0].clientX
66
+ document.addEventListener('touchmove', move)
67
+ document.addEventListener('touchend', end)
68
+ }
69
+ return false
70
+ }
71
+
72
+ // Register the drag and touch event handlers
73
+ el.addEventListener('mousedown', start)
74
+ el.addEventListener('touchstart', start)
75
+ }
76
+ }
77
+ },
78
+ props: {
79
+ /**
80
+ * Initial values of the range bounds. Should contain two numbers.
81
+ * indicating the start and end of the range.
82
+ */
83
+ range: {
84
+ type: Array as unknown as PropType<[number,number]>,
85
+ required: true
86
+ },
87
+ /**
88
+ * Enables hover styling on rows.
89
+ */
90
+ hover: {
91
+ type: Boolean as PropType<boolean>,
92
+ default:false
93
+ },
94
+ /**
95
+ * Offset from the left side of the component
96
+ * where the dragging for the start value begins.
97
+ */
98
+ startOffset: {
99
+ type: [Number,String] as PropType<number|string>,
100
+ default: 0
101
+ },
102
+ /**
103
+ * Offset from the right side of the component where
104
+ * the dragging for the end value ends.
105
+ */
106
+ endOffset: {
107
+ type: [Number,String] as PropType<number|string>,
108
+ default: 0
109
+ },
110
+ /**
111
+ * Number of decimal places to which values should be rounded.
112
+ */
113
+ precision: {
114
+ type: Number as PropType<number>,
115
+ default: 4
116
+ },
117
+ /**
118
+ * Snap increment value. For instance,
119
+ * if snap is 0.1, values will snap to 0, 0.1, 0.2, and so on.
120
+ */
121
+ snap: {
122
+ type: Number as PropType<number>,
123
+ default: 0.0001
124
+ },
125
+ /**
126
+ * Minimum distance between the two range bounds to ensure they
127
+ * don't get too close to each other.
128
+ */
129
+ minDistance: {
130
+ type: Number as PropType<number>,
131
+ default: 0.01
132
+ },
133
+ /**
134
+ * Variant style of the component. Expected to be one
135
+ * of the predefined Bootstrap theme (e.g., 'primary', 'secondary', etc).
136
+ */
137
+ variant: {
138
+ type: String as PropType<Variant>,
139
+ default: 'primary'
140
+ },
141
+ /**
142
+ * Rounds corner edges of the range boundaries. If
143
+ * true, the component will have rounded corners.
144
+ */
145
+ rounded: {
146
+ type: Boolean as PropType<boolean>,
147
+ default: false
148
+ }
149
+ },
150
+ emits:['update:range'],
151
+ setup(props, {emit}){
152
+ onBeforeMount(()=>{
153
+ library.add(faGripLinesVertical)
154
+ })
155
+ const rangePickerBounds = ref<HTMLElement|null>(null)
156
+ const start = toRef(props.range[0] ?? 0)
157
+ const end = toRef(props.range[1] ?? 0)
158
+ const moving = ref( false)
159
+ const resizing = ref( false)
160
+ const disabled = computed(() => {
161
+ return props.range.length < 2
162
+ })
163
+ const overlayStyle = computed((): { left: string; right: string } => {
164
+ return {
165
+ left: `${start.value * 100}%`,
166
+ right: `${(1 - end.value) * 100}%`
167
+ }
168
+ })
169
+ const boundsStyle = computed((): { left: string; right: string } => {
170
+ return {
171
+ left: startOffsetWithUnit.value,
172
+ right: endOffsetWithUnit.value
173
+ }
174
+ })
175
+
176
+ const startOffsetWithUnit = computed((): string => {
177
+ return valueWithUnit(props.startOffset)
178
+ })
179
+ const endOffsetWithUnit = computed((): string => {
180
+ return valueWithUnit(props.endOffset)
181
+ })
182
+
183
+ const startBoundStyle = computed((): { left: string } => {
184
+ return { left: `${start.value * 100}% ` }
185
+ })
186
+
187
+ const endBoundStyle = computed((): { left: string } => {
188
+ return { left: `${end.value * 100}%` }
189
+ })
190
+ const classList = computed((): { [key: string]: boolean } => {
191
+ return {
192
+ [`range-picker--${props.variant}`]: !!props.variant,
193
+ 'range-picker--hover': props.hover,
194
+ 'range-picker--disabled': disabled.value,
195
+ 'range-picker--rounded': props.rounded,
196
+ 'range-picker--resizing': resizing.value,
197
+ 'range-picker--moving': moving.value
198
+ }
199
+ }
200
+ )
201
+
202
+ function toggleMoving(value){
203
+ moving.value = value ?? !moving.value
204
+ }
205
+ function toggleResizing(value) {
206
+ resizing.value = value ?? !resizing.value
207
+ }
208
+ function snapValue(value): number {
209
+ return round(value / props.snap) * props.snap
210
+ }
211
+ function rangeWidth(): number {
212
+ return rangePickerBounds.value?.getBoundingClientRect().width ?? 0
213
+ }
214
+ function dragStartBound({detail: dx}) {
215
+
216
+ const newValue = snapValue(dx / rangeWidth())
217
+ // Ensure start value doesn't get too close to end value
218
+ if (newValue < end.value - props.minDistance) {
219
+ start.value = round(newValue, props.precision)
220
+ /**
221
+ * Update the values of the range (both start and end)
222
+ * @event update
223
+ * @param Number[] New value of the range
224
+ */
225
+ emit('update:range', [start.value, end.value])
226
+ }
227
+ }
228
+ function dragEndBound({detail: dx}) {
229
+ const newValue = snapValue(dx / rangeWidth())
230
+ // Ensure end value doesn't get too close to start value
231
+ if (newValue > start.value + props.minDistance) {
232
+ end.value = round(newValue, props.precision)
233
+ /**
234
+ * Update the values of the range (both start and end)
235
+ * @event update
236
+ * @param Number[] New value of the range
237
+ */
238
+ emit('update:range', [start.value, end.value])
239
+ }
240
+ }
241
+ function dragBounds({detail:dx}) {
242
+ const diff = snapValue(end.value - start.value)
243
+ const newValue = snapValue(dx / rangeWidth())
244
+ start.value = round(newValue, props.precision)
245
+ end.value = round(newValue + diff, props.precision)
246
+ /**
247
+ * Update the values of the range (both start and end)
248
+ * @event update
249
+ * @param Number[] New value of the range
250
+ */
251
+ emit('update:range', [start.value, end.value])
252
+ }
253
+ function valueWithUnit(value: number | string): string {
254
+ return typeof value === 'number' ? `${value}px` : `${value}`
255
+ }
256
+ return {
257
+ rangePickerBounds,
258
+ start,
259
+ end,
260
+ classList,
261
+ disabled,
262
+ overlayStyle,
263
+ boundsStyle,
264
+ rangeWidth,
265
+ startBoundStyle,
266
+ endBoundStyle,
267
+ dragStartBound,
268
+ dragEndBound,
269
+ dragBounds,
270
+ toggleMoving,
271
+ toggleResizing
272
+ }
273
+ }
274
+ })
275
+ </script>
276
+
277
+ <template>
278
+ <div class="range-picker" :class="classList">
279
+ <div class="range-picker__wrapper">
280
+ <slot />
281
+ </div>
282
+ <div v-show="!disabled" ref="rangePickerBounds" class="range-picker__bounds" :style="boundsStyle">
283
+ <div
284
+ v-draggable.relative
285
+ class="range-picker__bounds__overlay"
286
+ :style="overlayStyle"
287
+ @dragged="dragBounds"
288
+ @started="toggleMoving(true)"
289
+ @ended="toggleMoving(false)"
290
+ ></div>
291
+ <button
292
+ v-draggable
293
+ :style="startBoundStyle"
294
+ class="range-picker__bounds__start btn"
295
+ @dragged="dragStartBound"
296
+ @started="toggleResizing(true)"
297
+ @ended="toggleResizing(false)"
298
+ >
299
+ <fa icon="fa-grip-lines-vertical" fixed-width />
300
+ </button>
301
+ <button
302
+ v-draggable
303
+ class="range-picker__bounds__end btn"
304
+ :style="endBoundStyle"
305
+ @dragged="dragEndBound"
306
+ @started="toggleResizing(true)"
307
+ @ended="toggleResizing(false)"
308
+ >
309
+ <fa icon="fa-grip-lines-vertical" fixed-width />
310
+ </button>
311
+ </div>
312
+ </div>
313
+ </template>
314
+
315
+ <style scoped lang="scss">
316
+ @import '../styles/lib';
317
+
318
+ .range-picker {
319
+ min-height: 1rem;
320
+ position: relative;
321
+
322
+ --bg: #{$component-active-bg};
323
+ --fg: #{$component-active-color};
324
+
325
+ @each $color, $value in $theme-colors {
326
+ &--#{$color} {
327
+ --bg: var(--#{$color}, #{$value});
328
+ --fg: #{color-yiq($value)};
329
+ }
330
+ }
331
+
332
+ &--rounded {
333
+ border-radius: $border-radius;
334
+ }
335
+
336
+ &--hover:hover &__bounds:after {
337
+ pointer-events: none;
338
+ content: '';
339
+ z-index: -1;
340
+ width: 100%;
341
+ height: 100%;
342
+ display: block;
343
+ background: var(--bg);
344
+ opacity: 0.1;
345
+ border-radius: inherit;
346
+ }
347
+
348
+ &--moving &__wrapper,
349
+ &--resizing &__wrapper{
350
+ &,
351
+ * {
352
+ pointer-events: none;
353
+ }
354
+ }
355
+
356
+ &--moving,
357
+ &__bounds__overlay,
358
+ &--moving &__bounds__start.btn:not(:disabled):not(.disabled),
359
+ &--moving &__bounds__end.btn:not(:disabled):not(.disabled) {
360
+ cursor: move;
361
+ }
362
+
363
+ &--resizing,
364
+ &--resizing &__bounds__overlay,
365
+ &__bounds__start.btn:not(:disabled):not(.disabled),
366
+ &__bounds__end.btn:not(:disabled):not(.disabled) {
367
+ cursor: col-resize;
368
+ }
369
+
370
+ &__bounds {
371
+ pointer-events: none;
372
+ position: absolute;
373
+ top: 0;
374
+ height: 100%;
375
+ border-radius: inherit;
376
+
377
+ &__overlay {
378
+ pointer-events: all;
379
+ position: absolute;
380
+ top: 0;
381
+ bottom: 0;
382
+ left: 0;
383
+ right: 0;
384
+ display: flex;
385
+ border-radius: inherit;
386
+ border: 1px solid var(--bg);
387
+ overflow: hidden;
388
+
389
+ &:after {
390
+ content: '';
391
+ width: 100%;
392
+ height: 100%;
393
+ display: block;
394
+ background: var(--bg);
395
+ opacity: 0.3;
396
+ }
397
+ }
398
+
399
+ &__start,
400
+ &__end {
401
+ pointer-events: all;
402
+ position: absolute;
403
+ top: 50%;
404
+ transform: translate(-50%, -50%);
405
+ border-radius: 50%;
406
+ font-size: 0.6rem;
407
+ width: 1.2rem;
408
+ height: 1.2rem;
409
+ line-height: 1.2rem;
410
+ padding: 0;
411
+ background: var(--bg);
412
+ color: var(--fg);
413
+ transform: translate(-50%, -50%);
414
+
415
+ &:hover,
416
+ &:active {
417
+ color: var(--fg);
418
+ }
419
+ }
420
+
421
+ &__start {
422
+ left: 0;
423
+ }
424
+
425
+ &__end {
426
+ left: 100%;
427
+ }
428
+ }
429
+ }
430
+ </style>
@@ -0,0 +1,48 @@
1
+ <template>
2
+ <div :id="iframeId" />
3
+ </template>
4
+
5
+ <script lang="ts">
6
+ import { defineComponent } from 'vue'
7
+ import type { Parent } from 'pym.js'
8
+
9
+ import { injectAssets } from '@/utils/assets'
10
+
11
+ let iframeUniqueIdCounter = 0
12
+ type StartsWithIcijIframe = `icij-iframe-${string}`
13
+ type ResponsiveIframeData = { iframeId: StartsWithIcijIframe; pymParent: null | Parent }
14
+
15
+ /**
16
+ * ResponsiveIframe
17
+ */
18
+ export default defineComponent({
19
+ name: 'ResponsiveIframe',
20
+ props: {
21
+ /**
22
+ * URL of the generated iframe code.
23
+ */
24
+ url: {
25
+ type: String,
26
+ required: true
27
+ },
28
+ /**
29
+ * Option to pass to the constructor of the pymParent instance
30
+ */
31
+ options: {
32
+ type: Object,
33
+ default: () => ({})
34
+ }
35
+ },
36
+ data(): ResponsiveIframeData {
37
+ return {
38
+ iframeId: `icij-iframe-${++iframeUniqueIdCounter}`,
39
+ pymParent: null
40
+ }
41
+ },
42
+ async mounted(): Promise<void> {
43
+ await injectAssets('https://pym.nprapps.org/pym.v1.min.js')
44
+ //@ts-ignore
45
+ this.pymParent = new window.pym.Parent(this.iframeId, this.url, this.options)
46
+ }
47
+ })
48
+ </script>
@@ -0,0 +1,230 @@
1
+ <script lang="ts">
2
+ import {isFunction, isString} from 'lodash'
3
+ import * as d3 from 'd3'
4
+ import * as scaleFunctions from 'd3-scale'
5
+ import {defineComponent, PropType, ref, computed, onMounted, watch, nextTick, toRef} from 'vue'
6
+
7
+ type ClassListLegend = { 'scale-legend--has-cursor': boolean }
8
+ // eslint-disable-next-line no-unused-vars
9
+ type ColorScaleFn = (v?: number) => string
10
+
11
+ type ColorScale = ColorScaleFn | string
12
+ // eslint-disable-next-line no-unused-vars
13
+ type WidthScaleFn = (x: number) => string
14
+
15
+ export default defineComponent({
16
+ name: 'ScaleLegend',
17
+ props: {
18
+ width: {
19
+ type: Number,
20
+ default: 150
21
+ },
22
+ height: {
23
+ type: Number,
24
+ default: 16
25
+ },
26
+ cursorValue: {
27
+ type: Number,
28
+ default: null
29
+ },
30
+ max: {
31
+ type: Number,
32
+ default: 100
33
+ },
34
+ min: {
35
+ type: Number,
36
+ default: 0
37
+ },
38
+ colorScale: {
39
+ type: [Function, String] as PropType<ColorScaleFn | string>,
40
+ default: 'scaleLinear',
41
+ validator(colorScale: ColorScale) {
42
+ return isFunction(colorScale) || (colorScale as string) in scaleFunctions
43
+ }
44
+ },
45
+ colorScaleEnd: {
46
+ type: String,
47
+ default() {
48
+ const computedStyle = window.getComputedStyle(document.body)
49
+ return computedStyle.getPropertyValue('--primary') || '#000'
50
+ }
51
+ },
52
+ colorScaleStart: {
53
+ type: String,
54
+ default: '#fff'
55
+ }
56
+ },
57
+ setup(props) {
58
+ const cursorWrapperOffset = ref(0)
59
+ const mounted = ref(false)
60
+ const el = ref<Element | null>(null)
61
+ const classList = computed((): ClassListLegend => {
62
+ return {
63
+ 'scale-legend--has-cursor': hasCursor.value
64
+ }
65
+ })
66
+ const cursorValue = toRef(props.cursorValue)
67
+ onMounted(async () => {
68
+ await nextTick()
69
+ setCursorWrapperOffset()
70
+ setColorScaleCanvas()
71
+ mounted.value = true
72
+ })
73
+
74
+ const cursorLeft = computed((): string => {
75
+ const left = cursorLeftScale.value(props.cursorValue)
76
+ return isNaN(left) ? '0%' : `${left}%`
77
+ })
78
+ const colorScaleBaseCanvas = computed((): HTMLCanvasElement | null => {
79
+ return d3.create('canvas').attr('width', props.width).attr('height', props.height).node()
80
+ })
81
+ const colorScaleContext = computed((): CanvasRenderingContext2D | null => {
82
+ return colorScaleBaseCanvas.value?.getContext('2d') ?? null
83
+ })
84
+ const colorScaleBase64 = computed((): string | undefined => {
85
+ if (mounted.value) {
86
+ return colorScaleBaseCanvas.value?.toDataURL() ?? undefined
87
+ }
88
+ return undefined
89
+ })
90
+ const colorScaleWidthRange = computed((): number[] => {
91
+ return d3.range(1, props.width + 1)
92
+ })
93
+ const hasCursor = computed((): boolean => {
94
+ return props.cursorValue != null // double equal also tests undefined
95
+ })
96
+ const colorScaleFunction = computed((): ColorScaleFn => {
97
+ if (isString(props.colorScale)) {
98
+ // @ts-ignore
99
+ const fn: () => any = scaleFunctions[props.colorScale]
100
+ return fn().domain([props.min, props.max]).range([props.colorScaleStart, props.colorScaleEnd])
101
+ }
102
+ return props.colorScale
103
+ })
104
+ const cursorLeftScale = computed((): d3.ScaleLinear<number, number> => {
105
+ return d3.scaleLinear().domain([props.min, props.max]).range([0, 100]).interpolate(d3.interpolateRound)
106
+ })
107
+ const widthScaleColor = computed((): WidthScaleFn => {
108
+ return (x: number) => {
109
+ const value = widthScale.value(x)
110
+ return colorScaleFunction.value(value)
111
+ }
112
+ })
113
+ const widthScale = computed((): d3.ScaleLinear<number, number> => {
114
+ return d3.scaleLinear().domain([0, props.width]).range([props.min, props.max])
115
+ })
116
+
117
+ const formatNumber = d3.format(',')
118
+
119
+ function setCursorWrapperOffset(): void {
120
+ const cursor = el.value?.querySelector('.scale-legend__cursor')
121
+ if (cursor && el.value) {
122
+ const {x: cursorX, width: cursorWidth} = cursor.getBoundingClientRect()
123
+ const {x: legendX, width: legendWidth} = el.value.getBoundingClientRect()
124
+ const left = legendX - cursorX - 6
125
+ const right = legendX + legendWidth - (cursorX + cursorWidth) + 6
126
+ cursorWrapperOffset.value = Math.max(0, left) || Math.min(0, right)
127
+ } else {
128
+ cursorWrapperOffset.value = 0
129
+ }
130
+ }
131
+
132
+ function setColorScaleCanvas(): void {
133
+ if (!colorScaleContext.value) {
134
+ return
135
+ }
136
+ for (const x of colorScaleWidthRange.value) {
137
+ colorScaleContext.value.fillStyle = widthScaleColor.value(x)
138
+ colorScaleContext.value.fillRect(x, 0, 1, props.height)
139
+ }
140
+ }
141
+
142
+ watch(cursorValue,
143
+ async () => {
144
+ await nextTick()
145
+ setCursorWrapperOffset()
146
+ })
147
+
148
+ return {
149
+ classList,
150
+ colorScaleBase64,
151
+ cursorLeft,
152
+ cursorWrapperOffset,
153
+ formatNumber,
154
+ hasCursor,
155
+ //CD: function below are only uses in unit tests. use callable?
156
+ widthScale,
157
+ colorScaleFunction,
158
+ widthScaleColor,
159
+ }
160
+ }
161
+ })
162
+ </script>
163
+
164
+ <template>
165
+ <div ref="el" :class="classList" class="scale-legend">
166
+ <div class="scale-legend__bound scale-legend__bound--min">
167
+ <slot name="legend-cursor-min" v-bind="{ min }">
168
+ {{ formatNumber(min) }}
169
+ </slot>
170
+ </div>
171
+ <img :height="height" :src="colorScaleBase64" :width="width" class="scale-legend__scale" alt="legend scale"/>
172
+ <div class="scale-legend__bound scale-legend__bound--max">
173
+ <slot name="legend-cursor-max" v-bind="{ max }">
174
+ {{ formatNumber(max) }}
175
+ </slot>
176
+ </div>
177
+ <div v-if="hasCursor" :style="{ left: cursorLeft }" class="scale-legend__cursor">
178
+ <div :style="{ transform: `translateX(${cursorWrapperOffset}px)` }" class="scale-legend__cursor__wrapper">
179
+ <slot name="cursor" v-bind="{ value: cursorValue }">
180
+ {{ formatNumber(cursorValue) }}
181
+ </slot>
182
+ </div>
183
+ </div>
184
+ </div>
185
+ </template>
186
+
187
+ <style lang="scss" scoped>
188
+ @import '../styles/lib';
189
+
190
+ .scale-legend {
191
+ position: relative;
192
+ display: inline-block;
193
+
194
+ &__bound,
195
+ &__cursor {
196
+ position: absolute;
197
+ bottom: 100%;
198
+ font-size: 0.8rem;
199
+
200
+ &--min {
201
+ left: 0;
202
+ }
203
+
204
+ &--max {
205
+ right: 0;
206
+ }
207
+ }
208
+
209
+ .choropleth-map--has-cursor &__bound {
210
+ color: $text-muted;
211
+ opacity: 0.6;
212
+ }
213
+
214
+ &__cursor {
215
+ font-weight: bold;
216
+ transform: translateX(-50%);
217
+ left: 50%;
218
+
219
+ &:after {
220
+ content: '';
221
+ border: 5px solid transparent;
222
+ border-top-color: var(--dark, currentColor);
223
+ position: absolute;
224
+ left: 50%;
225
+ top: 100%;
226
+ transform: translateX(-50%);
227
+ }
228
+ }
229
+ }
230
+ </style>