@bagelink/vue 1.19.1 → 1.20.3

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 (61) hide show
  1. package/dist/components/Dropdown.vue.d.ts +73 -48
  2. package/dist/components/Dropdown.vue.d.ts.map +1 -1
  3. package/dist/components/Icon/Icon.vue.d.ts.map +1 -1
  4. package/dist/components/dataTable/DataTable.vue.d.ts.map +1 -1
  5. package/dist/components/form/inputs/RichText/components/EditorToolbar.vue.d.ts +1 -0
  6. package/dist/components/form/inputs/RichText/components/EditorToolbar.vue.d.ts.map +1 -1
  7. package/dist/components/form/inputs/RichText/composables/useCommands.d.ts.map +1 -1
  8. package/dist/components/form/inputs/RichText/composables/useEditorKeyboard.d.ts.map +1 -1
  9. package/dist/components/form/inputs/RichText/config.d.ts.map +1 -1
  10. package/dist/components/form/inputs/RichText/index.vue.d.ts +6 -0
  11. package/dist/components/form/inputs/RichText/index.vue.d.ts.map +1 -1
  12. package/dist/components/form/inputs/RichText/richTextTypes.d.ts +1 -0
  13. package/dist/components/form/inputs/RichText/richTextTypes.d.ts.map +1 -1
  14. package/dist/components/form/inputs/RichText/utils/commands.d.ts.map +1 -1
  15. package/dist/components/form/inputs/RichText/utils/contentMetrics.d.ts +7 -0
  16. package/dist/components/form/inputs/RichText/utils/contentMetrics.d.ts.map +1 -0
  17. package/dist/components/form/inputs/RichText/utils/image.d.ts +17 -0
  18. package/dist/components/form/inputs/RichText/utils/image.d.ts.map +1 -0
  19. package/dist/components/form/inputs/RichText/utils/sanitizePaste.d.ts +15 -0
  20. package/dist/components/form/inputs/RichText/utils/sanitizePaste.d.ts.map +1 -0
  21. package/dist/components/form/inputs/RichText/utils/selection.d.ts +1 -1
  22. package/dist/components/form/inputs/RichText/utils/selection.d.ts.map +1 -1
  23. package/dist/components/form/inputs/RichText/utils/table.d.ts.map +1 -1
  24. package/dist/components/form/inputs/RichText/utils/video.d.ts +23 -0
  25. package/dist/components/form/inputs/RichText/utils/video.d.ts.map +1 -0
  26. package/dist/components/form/inputs/SelectBtn.vue.d.ts +28 -1
  27. package/dist/components/form/inputs/SelectBtn.vue.d.ts.map +1 -1
  28. package/dist/components/form/inputs/Upload/UploadInput.vue.d.ts +2 -0
  29. package/dist/components/form/inputs/Upload/UploadInput.vue.d.ts.map +1 -1
  30. package/dist/components/form/inputs/Upload/useFileUpload.d.ts +1 -0
  31. package/dist/components/form/inputs/Upload/useFileUpload.d.ts.map +1 -1
  32. package/dist/core.css +2 -2
  33. package/dist/index.cjs +218 -130
  34. package/dist/index.css +1 -1
  35. package/dist/index.mjs +18604 -17549
  36. package/dist/style.css +2 -2
  37. package/package.json +1 -1
  38. package/src/components/Dropdown.vue +48 -7
  39. package/src/components/FilterQuery.vue +1 -1
  40. package/src/components/Icon/Icon.vue +8 -0
  41. package/src/components/dataTable/DataTable.vue +6 -3
  42. package/src/components/form/inputs/RichText/components/EditorToolbar.vue +238 -8
  43. package/src/components/form/inputs/RichText/composables/useCommands.ts +23 -5
  44. package/src/components/form/inputs/RichText/composables/useEditorKeyboard.ts +28 -0
  45. package/src/components/form/inputs/RichText/config.ts +17 -14
  46. package/src/components/form/inputs/RichText/index.vue +354 -463
  47. package/src/components/form/inputs/RichText/richTextTypes.ts +1 -0
  48. package/src/components/form/inputs/RichText/utils/commands.ts +437 -24
  49. package/src/components/form/inputs/RichText/utils/contentMetrics.ts +24 -0
  50. package/src/components/form/inputs/RichText/utils/image.ts +136 -0
  51. package/src/components/form/inputs/RichText/utils/sanitizePaste.ts +110 -0
  52. package/src/components/form/inputs/RichText/utils/selection.ts +32 -0
  53. package/src/components/form/inputs/RichText/utils/table.ts +77 -8
  54. package/src/components/form/inputs/RichText/utils/video.ts +270 -0
  55. package/src/components/form/inputs/SelectBtn.vue +126 -15
  56. package/src/components/form/inputs/TelInput.vue +1 -1
  57. package/src/components/form/inputs/Upload/UploadInput.vue +3 -2
  58. package/src/components/form/inputs/Upload/useFileUpload.ts +2 -0
  59. package/src/styles/colors.css +5 -2
  60. package/src/styles/core.css +1 -0
  61. package/src/styles/rich-content.css +109 -0
@@ -1,6 +1,8 @@
1
1
  <script setup lang="ts">
2
- import type { Option } from '../../../index'
3
- import { Btn, Icon, resolveI18n } from '../../../index'
2
+ import type { IconType, Option } from '../../../types'
3
+ import Btn from '../../Btn.vue'
4
+ import Icon from '../../Icon/Icon.vue'
5
+ import { resolveI18n } from '../../../i18n'
4
6
  import { computed } from 'vue'
5
7
  import { getOptionIcon, getOptionLabel, getOptionValue } from '../../../utils/options'
6
8
  import type { BagelInputShellProps } from './bagelInputShell'
@@ -15,12 +17,38 @@ const props = withDefaults(
15
17
  thin?: boolean
16
18
  size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
17
19
  multiselect?: boolean
20
+ disabled?: boolean
21
+ /** Show a check icon on selected options. */
22
+ check?: boolean
23
+ /** Inactive-state icon in the reserved check slot. Defaults to a minus. */
24
+ inactiveCheckIcon?: IconType
25
+ /** Legacy compact preset. Prefer explicit height/fontSize in new usage. */
26
+ compact?: boolean
27
+ /** Exact option height; numbers are pixels. Overrides thin/size height. */
28
+ height?: number | string
29
+ mobileHeight?: number | string
30
+ /** Option font size; numbers are pixels. */
31
+ fontSize?: number | string
32
+ mobileFontSize?: number | string
33
+ /** Check icon size, matching Icon's size API (numbers are rem). */
34
+ checkSize?: number | string
35
+ mobileCheckSize?: number | string
36
+ /** Remove the border from each option button. */
37
+ flat?: boolean
38
+ /** Selected option text tone or CSS color. Defaults to white. */
39
+ activeColor?: string
40
+ /** Selected option background tone or CSS color. Defaults to the input active color. */
41
+ activeBgColor?: string
42
+ /** Unselected option text tone or CSS color. */
43
+ inactiveColor?: string
44
+ /** Unselected option background tone or CSS color. */
45
+ inactiveBgColor?: string
18
46
  /** Render only each option's icon (label becomes a tooltip). */
19
47
  iconOnly?: boolean
20
48
  /** Background applied to the options area only; the field label stays outside it. */
21
49
  bgColor?: string
22
50
  } & BagelInputShellProps>(),
23
- { multiselect: false, iconOnly: false },
51
+ { multiselect: false, iconOnly: false, inactiveCheckIcon: 'remove' },
24
52
  )
25
53
 
26
54
  const { shellClass, shellStyle } = useBagelInputShell(() => ({
@@ -37,11 +65,29 @@ const hasValue = computed(() => {
37
65
  })
38
66
 
39
67
  /** Theme token names resolve to Bagel CSS variables; raw CSS values pass through. */
40
- const resolvedBgColor = computed(() => {
41
- const color = props.bgColor
68
+ const resolvedBgColor = computed(() => resolveColor(props.bgColor))
69
+
70
+ function resolveColor(color?: string) {
42
71
  if (!color) { return undefined }
43
- return /^[a-z][a-z0-9-]*$/i.test(color) ? `var(--bgl-${color})` : color
44
- })
72
+ if (['transparent', 'inherit', 'currentColor'].includes(color)) { return color }
73
+ const tone = color.replace(/^--bgl-|^bgl-/, '')
74
+ return /^[a-z][a-z0-9-]*$/i.test(tone) ? `var(--bgl-${tone})` : color
75
+ }
76
+
77
+ function cssLength(value?: number | string) {
78
+ return typeof value === 'number' ? `${value}px` : value
79
+ }
80
+
81
+ const optionStyle = computed(() => ({
82
+ '--bgl-select-btn-active-color': resolveColor(props.activeColor),
83
+ '--bgl-select-btn-active-bg': resolveColor(props.activeBgColor),
84
+ '--bgl-select-btn-inactive-color': resolveColor(props.inactiveColor),
85
+ '--bgl-select-btn-inactive-bg': resolveColor(props.inactiveBgColor),
86
+ '--bgl-select-btn-height': cssLength(props.height),
87
+ '--bgl-select-btn-mobile-height': cssLength(props.mobileHeight),
88
+ '--bgl-select-btn-font-size': cssLength(props.fontSize),
89
+ '--bgl-select-btn-mobile-font-size': cssLength(props.mobileFontSize),
90
+ }))
45
91
 
46
92
  type OptionValue = string | number | boolean
47
93
 
@@ -64,7 +110,17 @@ function isSelected(val: OptionValue) {
64
110
 
65
111
  <template>
66
112
  <div
67
- class="bagel-input SelectBtn" :class="[shellClass, { 'has-value': hasValue }]" :style="shellStyle"
113
+ class="bagel-input SelectBtn"
114
+ :class="[shellClass, {
115
+ 'has-value': hasValue,
116
+ 'is-compact': compact,
117
+ 'is-flat': flat,
118
+ 'has-custom-height': height != null,
119
+ 'has-custom-font-size': fontSize != null,
120
+ 'has-mobile-height': mobileHeight != null,
121
+ 'has-mobile-font-size': mobileFontSize != null,
122
+ }]"
123
+ :style="[shellStyle, optionStyle]"
68
124
  >
69
125
  <label v-if="label || required" >
70
126
  {{ resolveI18n(label) }}<span v-if="required"> *</span>
@@ -77,24 +133,46 @@ function isSelected(val: OptionValue) {
77
133
  <Btn
78
134
  v-for="option in options" :key="String(getOptionValue(option))" :thin="thin"
79
135
  :size="size"
136
+ :disabled="disabled"
80
137
  type="button"
81
- :class="isSelected(getOptionValue(option)!) ? 'color-white' : 'bg-transparent'"
138
+ :class="[
139
+ isSelected(getOptionValue(option)!) ? 'is-selected' : 'is-unselected',
140
+ { 'bg-transparent': !isSelected(getOptionValue(option)!) && !inactiveBgColor },
141
+ ]"
142
+ :aria-pressed="isSelected(getOptionValue(option)!)"
82
143
  :style="isSelected(getOptionValue(option)!)
83
144
  ? {
84
- backgroundColor: 'var(--bgl-input-label-active-color, var(--bgl-primary))',
85
- borderColor: 'var(--bgl-input-label-active-color, var(--bgl-primary))',
86
- color: 'white',
145
+ backgroundColor: 'var(--bgl-select-btn-active-bg, var(--bgl-input-label-active-color, var(--bgl-primary)))',
146
+ borderColor: 'var(--bgl-select-btn-active-bg, var(--bgl-input-label-active-color, var(--bgl-primary)))',
147
+ color: 'var(--bgl-select-btn-active-color, white)',
87
148
  }
88
149
  : {
89
- color: 'var(--bgl-input-label-active-color, var(--bgl-border-color))',
150
+ color: 'var(--bgl-select-btn-inactive-color, var(--bgl-input-label-active-color, var(--bgl-border-color)))',
90
151
  borderColor: 'var(--bgl-input-label-active-color, var(--bgl-border-color))',
152
+ backgroundColor: 'var(--bgl-select-btn-inactive-bg, transparent)',
91
153
  }"
92
154
  class="border radius-2 SelectBtn-padding"
93
155
  v-tooltip="iconOnly && getOptionIcon(option) ? getOptionLabel(option) : undefined"
94
156
  @click="toggleOption(getOptionValue(option)!)"
95
157
  >
96
- <Icon v-if="getOptionIcon(option)" :icon="getOptionIcon(option)!" weight="300" />
97
- <p v-if="!(iconOnly && getOptionIcon(option))">{{ getOptionLabel(option) }}</p>
158
+ <span :class="check ? 'flex gap-025' : 'contents'">
159
+ <span
160
+ v-if="check"
161
+ class="flex-center"
162
+ aria-hidden="true"
163
+ :style="{ opacity: isSelected(getOptionValue(option)!) ? 1 : inactiveCheckIcon ? 0.5 : 0 }"
164
+ >
165
+ <Icon
166
+ :icon="isSelected(getOptionValue(option)!) ? 'check' : inactiveCheckIcon ?? 'check'"
167
+ :transition="false"
168
+ weight="500"
169
+ :size="checkSize ?? 0.8"
170
+ :mobile-size="mobileCheckSize"
171
+ />
172
+ </span>
173
+ <Icon v-if="getOptionIcon(option)" :icon="getOptionIcon(option)!" weight="300" />
174
+ <span v-if="!(iconOnly && getOptionIcon(option))">{{ getOptionLabel(option) }}</span>
175
+ </span>
98
176
  </Btn>
99
177
  </div>
100
178
  <p v-if="error" class="txt-12 color-error mt-025">
@@ -114,6 +192,20 @@ function isSelected(val: OptionValue) {
114
192
  padding: 0 var(--bgl-select-btn-padding, 0.7rem);
115
193
  }
116
194
 
195
+ .SelectBtn.is-compact {
196
+ --bgl-input-font-size: 12px;
197
+ --bgl-input-height: 30px;
198
+ --bgl-btn-height: 30px;
199
+ }
200
+
201
+ .SelectBtn.is-compact .SelectBtn-padding {
202
+ line-height: 1;
203
+ }
204
+
205
+ .SelectBtn.is-flat .SelectBtn-padding {
206
+ border: none !important;
207
+ }
208
+
117
209
  .select-btn-items {
118
210
  width: fit-content;
119
211
  max-width: 100%;
@@ -149,4 +241,23 @@ function isSelected(val: OptionValue) {
149
241
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--bgl-input-label-active-color, var(--bgl-primary)) 12%, transparent);
150
242
  }
151
243
 
244
+ .SelectBtn.has-custom-height .SelectBtn-padding {
245
+ height: var(--bgl-select-btn-height);
246
+ line-height: var(--bgl-select-btn-height);
247
+ }
248
+
249
+ .SelectBtn.has-custom-font-size .SelectBtn-padding {
250
+ font-size: var(--bgl-select-btn-font-size);
251
+ }
252
+
253
+ @media screen and (max-width: 910px) {
254
+ .SelectBtn.has-mobile-height .SelectBtn-padding {
255
+ height: var(--bgl-select-btn-mobile-height);
256
+ line-height: var(--bgl-select-btn-mobile-height);
257
+ }
258
+
259
+ .SelectBtn.has-mobile-font-size .SelectBtn-padding {
260
+ font-size: var(--bgl-select-btn-mobile-font-size);
261
+ }
262
+ }
152
263
  </style>
@@ -386,7 +386,7 @@ background: transparent;
386
386
  }
387
387
 
388
388
  .tel-input input {
389
- background: transparent;
389
+ background: transparent !important;
390
390
  text-align: left;
391
391
  flex: 1;
392
392
  }
@@ -19,7 +19,7 @@ const props = withDefaults(defineProps<UploadInputProps>(), {
19
19
  disabled: false,
20
20
  })
21
21
 
22
- const emit = defineEmits(['update:modelValue', 'addFileStart'])
22
+ const emit = defineEmits(['update:modelValue', 'addFileStart', 'uploadError'])
23
23
 
24
24
  const { $t } = useI18n()
25
25
  const slots = useSlots()
@@ -43,7 +43,8 @@ const {
43
43
  await flushQueue()
44
44
  updateModelValue()
45
45
  },
46
- onChange: updateModelValue
46
+ onChange: updateModelValue,
47
+ onUploadError: (error, file) => emit('uploadError', error, file),
47
48
  })
48
49
 
49
50
  // Normalize each entry to a path-key/URL string. Legacy/migrated data may store
@@ -13,6 +13,7 @@ interface UseFileUploadProps {
13
13
  upload?: (file: File) => Promise<string>
14
14
  onFileQueued?: (files: QueueFile[]) => void
15
15
  onChange?: () => void
16
+ onUploadError?: (error: unknown, file: File) => void
16
17
  }
17
18
 
18
19
  export function useFileUpload(props: UseFileUploadProps = {}) {
@@ -126,6 +127,7 @@ export function useFileUpload(props: UseFileUploadProps = {}) {
126
127
  uploadedPathKeys.push(data.path_key)
127
128
  } catch (error) {
128
129
  console.error('Error uploading file:', error)
130
+ props.onUploadError?.(error, file.file)
129
131
  }
130
132
  }
131
133
 
@@ -10,7 +10,8 @@
10
10
 
11
11
  .bg-input-white input,
12
12
  .bg-input-white textarea,
13
- .bg-input-white .selectinput-btn {
13
+ .bg-input-white .selectinput-btn,
14
+ .bg-input-white .tel-input {
14
15
  background: var(--bgl-white) !important;
15
16
  }
16
17
 
@@ -666,7 +667,9 @@
666
667
  border-color: var(--bgl-gray) !important;
667
668
  }
668
669
 
669
- .color-input-gray input {
670
+ .color-input-gray input,
671
+ .color-input-gray .tel-input,
672
+ .color-input-gray .dial-code {
670
673
  color: var(--bgl-gray) !important;
671
674
  }
672
675
 
@@ -25,6 +25,7 @@
25
25
  @import "appearance.css";
26
26
  @import "gradients.css";
27
27
  @import "color-variants.css";
28
+ @import "rich-content.css";
28
29
 
29
30
  .bgl_icon-font { font-family: 'Material Symbols Outlined', serif !important; }
30
31
  .bgl_icon-font.fa-brands { font-family: 'Font Awesome 6 Brands', serif !important; }
@@ -0,0 +1,109 @@
1
+ /* Public rendering contract for HTML produced by RichText.
2
+ `.bgl-rich-content` is the preferred wrapper. The attribute selectors also
3
+ keep media responsive when saved HTML is rendered without a wrapper. */
4
+
5
+ :where(.bgl-rich-content, .rich-content, .richtxt) {
6
+ display: flow-root;
7
+ }
8
+
9
+ :where(.bgl-rich-content, .rich-content, .richtxt) table {
10
+ display: block;
11
+ max-width: 100%;
12
+ overflow-x: auto;
13
+ border-collapse: collapse;
14
+ }
15
+
16
+ figure[data-media-type='image'],
17
+ figure[data-media-type='video'],
18
+ figure:has(> iframe[data-media-type='embed']) {
19
+ box-sizing: border-box;
20
+ max-width: 100%;
21
+ margin-block: 1.5rem;
22
+ margin-inline: 0;
23
+ text-align: center;
24
+ }
25
+
26
+ figure[data-media-type='image'] > a {
27
+ display: block;
28
+ }
29
+
30
+ figure[data-media-type='image'] img,
31
+ figure[data-media-type='video'] video {
32
+ display: block;
33
+ width: 100%;
34
+ max-width: 100%;
35
+ height: auto;
36
+ }
37
+
38
+ figure.align-full {
39
+ float: none;
40
+ width: 100% !important;
41
+ max-width: 100%;
42
+ margin-inline: 0;
43
+ }
44
+
45
+ figure.align-start,
46
+ figure.align-end {
47
+ width: 50% !important;
48
+ max-width: 50%;
49
+ margin-block: 0.25em 0.75em;
50
+ }
51
+
52
+ figure.align-start {
53
+ float: inline-start;
54
+ margin-inline: 0 1.25em;
55
+ }
56
+
57
+ figure.align-end {
58
+ float: inline-end;
59
+ margin-inline: 1.25em 0;
60
+ }
61
+
62
+ .video-responsive {
63
+ position: relative;
64
+ width: 100%;
65
+ height: 0;
66
+ overflow: hidden;
67
+ }
68
+
69
+ .video-responsive > iframe {
70
+ position: absolute;
71
+ inset: 0;
72
+ width: 100%;
73
+ height: 100%;
74
+ border: 0;
75
+ }
76
+
77
+ figure:has(> iframe[data-media-type='embed']) > iframe {
78
+ display: block;
79
+ width: 100%;
80
+ max-width: 100%;
81
+ }
82
+
83
+ figure[data-media-type] figcaption,
84
+ figure:has(> iframe[data-media-type='embed']) figcaption {
85
+ padding-block-start: 0.5rem;
86
+ color: var(--bgl-gray);
87
+ font-size: 0.875rem;
88
+ line-height: 1.4;
89
+ text-align: center;
90
+ }
91
+
92
+ figure[data-media-type] figcaption .alt-text {
93
+ color: var(--bgl-text-color);
94
+ font-weight: 600;
95
+ }
96
+
97
+ figure[data-media-type] figcaption .photo-credit {
98
+ color: inherit;
99
+ }
100
+
101
+ @media screen and (max-width: 600px) {
102
+ figure.align-start,
103
+ figure.align-end {
104
+ float: none;
105
+ width: 100% !important;
106
+ max-width: 100%;
107
+ margin-inline: 0;
108
+ }
109
+ }