@flux-ui/components 3.0.0-next.72 → 3.0.0-next.73

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.
@@ -2,3 +2,4 @@ export { default as createDialogRenderer } from './createDialogRenderer';
2
2
  export { default as createLabelForDateRange } from './createLabelForDateRange';
3
3
  export { default as defineFilter, type FluxFilterDefinitionFactory } from './defineFilter';
4
4
  export { generateMultiOptionsLabel, isFluxFilterOptionHeader, isFluxFilterOptionItem, isResettable, pickFilterCommon } from './filter';
5
+ export { default as sanitizeUrl } from './sanitizeUrl';
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Sanitizes a URL intended for an `href` attribute. Returns `undefined` when the
3
+ * URL uses a dangerous scheme (`javascript:`, `vbscript:`, `data:`) so the attribute
4
+ * is omitted instead of becoming a script-execution or data-injection vector.
5
+ * Safe and relative URLs are returned unchanged.
6
+ */
7
+ export default function (href?: string): string | undefined;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@flux-ui/components",
3
3
  "description": "A set of opiniated UI components.",
4
- "version": "3.0.0-next.72",
4
+ "version": "3.0.0-next.73",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "funding": "https://github.com/sponsors/basmilius",
@@ -57,8 +57,8 @@
57
57
  "dependencies": {
58
58
  "@basmilius/common": "^3.37.0",
59
59
  "@basmilius/utils": "^3.37.0",
60
- "@flux-ui/internals": "3.0.0-next.72",
61
- "@flux-ui/types": "3.0.0-next.72",
60
+ "@flux-ui/internals": "3.0.0-next.73",
61
+ "@flux-ui/types": "3.0.0-next.73",
62
62
  "@fortawesome/fontawesome-common-types": "^7.2.0",
63
63
  "clsx": "^2.1.1",
64
64
  "imask": "^7.6.1",
@@ -134,10 +134,10 @@
134
134
  import { blue500 } from '@flux-ui/internals';
135
135
  import { computed, type ComputedRef, ref, unref, watch } from 'vue';
136
136
  import { useTranslate } from '~flux/components/composable/private';
137
- import CoordinatePicker from './primitive/CoordinatePicker.vue';
138
137
  import FluxFormField from './FluxFormField.vue';
139
138
  import FluxFormInput from './FluxFormInput.vue';
140
139
  import FluxFormSlider from './FluxFormSlider.vue';
140
+ import CoordinatePicker from './primitive/CoordinatePicker.vue';
141
141
  import $style from '~flux/components/css/component/Color.module.scss';
142
142
 
143
143
  const modelValue = defineModel<string | [number, number, number]>({
@@ -3,7 +3,9 @@
3
3
  <label
4
4
  :for="id"
5
5
  :class="$style.formFieldHeader">
6
- <span :class="$style.formFieldLabel">
6
+ <span
7
+ v-if="label"
8
+ :class="$style.formFieldLabel">
7
9
  {{ label }}
8
10
  </span>
9
11
 
@@ -61,7 +63,7 @@
61
63
  readonly error?: string;
62
64
  readonly hint?: string;
63
65
  readonly isOptional?: boolean;
64
- readonly label: string;
66
+ readonly label?: string;
65
67
  readonly maxLength?: number;
66
68
  }>();
67
69
 
@@ -80,9 +80,9 @@
80
80
  import { Comment, computed, onBeforeUnmount, onMounted, provide, Text, toRef, unref, useSlots, useTemplateRef, watch } from 'vue';
81
81
  import { useDisabled, useKanbanInjection } from '~flux/components/composable';
82
82
  import { FluxDisabledInjectionKey } from '~flux/components/data';
83
- import $style from '~flux/components/css/component/Kanban.module.scss';
84
83
  import FluxBadge from './FluxBadge.vue';
85
84
  import FluxIcon from './FluxIcon.vue';
85
+ import $style from '~flux/components/css/component/Kanban.module.scss';
86
86
 
87
87
  const {
88
88
  columnId,
@@ -3,7 +3,7 @@
3
3
  v-if="componentType === 'route'"
4
4
  v-bind="$attrs"
5
5
  v-on="hoverListeners"
6
- :rel="rel"
6
+ :rel="resolvedRel"
7
7
  :target="target"
8
8
  :to="to as any"
9
9
  @click="onClick($event)">
@@ -14,8 +14,8 @@
14
14
  v-else-if="componentType === 'link'"
15
15
  v-bind="$attrs"
16
16
  v-on="hoverListeners"
17
- :href="href"
18
- :rel="rel"
17
+ :href="sanitizeUrl(href)"
18
+ :rel="resolvedRel"
19
19
  :target="target"
20
20
  @click="onClick($event)">
21
21
  <slot/>
@@ -42,7 +42,8 @@
42
42
  lang="ts"
43
43
  setup>
44
44
  import type { FluxPressableType, FluxTo } from '@flux-ui/types';
45
- import type { VNode } from 'vue';
45
+ import { computed, type VNode } from 'vue';
46
+ import { sanitizeUrl } from '~flux/components/util';
46
47
 
47
48
  const emit = defineEmits<{
48
49
  click: [MouseEvent];
@@ -50,7 +51,7 @@
50
51
  mouseleave: [MouseEvent];
51
52
  }>();
52
53
 
53
- defineProps<{
54
+ const {rel, target} = defineProps<{
54
55
  readonly componentType?: FluxPressableType;
55
56
  readonly href?: string;
56
57
  readonly rel?: string;
@@ -62,6 +63,14 @@
62
63
  default(): VNode[];
63
64
  }>();
64
65
 
66
+ const resolvedRel = computed(() => {
67
+ if (rel) {
68
+ return rel;
69
+ }
70
+
71
+ return target === '_blank' ? 'noopener noreferrer' : undefined;
72
+ });
73
+
65
74
  const hoverListeners = {
66
75
  onMouseenter: (evt: MouseEvent) => emit('mouseenter', evt),
67
76
  onMouseleave: (evt: MouseEvent) => emit('mouseleave', evt)
@@ -4,9 +4,9 @@
4
4
  :icon="icon"
5
5
  :title="title"/>
6
6
 
7
- <FluxPaneBody
8
- v-if="message"
9
- v-html="message"/>
7
+ <FluxPaneBody v-if="message">
8
+ {{ message }}
9
+ </FluxPaneBody>
10
10
 
11
11
  <FluxPaneBody v-if="$slots.default">
12
12
  <slot/>
package/src/util/index.ts CHANGED
@@ -2,3 +2,4 @@ export { default as createDialogRenderer } from './createDialogRenderer';
2
2
  export { default as createLabelForDateRange } from './createLabelForDateRange';
3
3
  export { default as defineFilter, type FluxFilterDefinitionFactory } from './defineFilter';
4
4
  export { generateMultiOptionsLabel, isFluxFilterOptionHeader, isFluxFilterOptionItem, isResettable, pickFilterCommon } from './filter';
5
+ export { default as sanitizeUrl } from './sanitizeUrl';
@@ -0,0 +1,40 @@
1
+ const DANGEROUS_PROTOCOL = /^(javascript|vbscript|data):/i;
2
+
3
+ /**
4
+ * Removes control characters (and the Unicode line/paragraph separators) that
5
+ * browsers ignore inside URLs but which can be used to obfuscate the scheme,
6
+ * e.g. `java\tscript:alert(1)`.
7
+ */
8
+ function stripControlChars(value: string): string {
9
+ let out = '';
10
+
11
+ for (let i = 0; i < value.length; i++) {
12
+ const code = value.charCodeAt(i);
13
+
14
+ if (code <= 0x20 || (code >= 0x7f && code <= 0x9f) || code === 0x2028 || code === 0x2029) {
15
+ continue;
16
+ }
17
+
18
+ out += value[i];
19
+ }
20
+
21
+ return out;
22
+ }
23
+
24
+ /**
25
+ * Sanitizes a URL intended for an `href` attribute. Returns `undefined` when the
26
+ * URL uses a dangerous scheme (`javascript:`, `vbscript:`, `data:`) so the attribute
27
+ * is omitted instead of becoming a script-execution or data-injection vector.
28
+ * Safe and relative URLs are returned unchanged.
29
+ */
30
+ export default function (href?: string): string | undefined {
31
+ if (!href) {
32
+ return href;
33
+ }
34
+
35
+ if (DANGEROUS_PROTOCOL.test(stripControlChars(href))) {
36
+ return undefined;
37
+ }
38
+
39
+ return href;
40
+ }