@ulu/frontend-vue 0.5.16 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/dist/components/elements/UluBadge.vue.d.ts +2 -0
  2. package/dist/components/elements/UluBadge.vue.d.ts.map +1 -1
  3. package/dist/components/elements/UluBadge.vue.js +30 -21
  4. package/dist/components/elements/UluButton.vue.d.ts +4 -0
  5. package/dist/components/elements/UluButton.vue.d.ts.map +1 -1
  6. package/dist/components/elements/UluButton.vue.js +31 -16
  7. package/dist/components/elements/UluIcon.vue.js +21 -36
  8. package/dist/components/forms/UluFormCheckbox.vue.d.ts +3 -19
  9. package/dist/components/forms/UluFormCheckbox.vue.d.ts.map +1 -1
  10. package/dist/components/forms/UluFormCheckbox.vue.js +10 -31
  11. package/dist/components/forms/UluFormFile.vue.d.ts +3 -25
  12. package/dist/components/forms/UluFormFile.vue.d.ts.map +1 -1
  13. package/dist/components/forms/UluFormFile.vue.js +11 -49
  14. package/dist/components/forms/UluFormItem.vue.d.ts +23 -8
  15. package/dist/components/forms/UluFormItem.vue.d.ts.map +1 -1
  16. package/dist/components/forms/UluFormItem.vue.js +126 -29
  17. package/dist/components/forms/UluFormLabel.vue.d.ts +24 -0
  18. package/dist/components/forms/UluFormLabel.vue.d.ts.map +1 -0
  19. package/dist/components/forms/UluFormLabel.vue.js +34 -0
  20. package/dist/components/forms/UluFormRadio.vue.d.ts +7 -25
  21. package/dist/components/forms/UluFormRadio.vue.d.ts.map +1 -1
  22. package/dist/components/forms/UluFormRadio.vue.js +11 -37
  23. package/dist/components/forms/UluFormSelect.vue.d.ts +7 -23
  24. package/dist/components/forms/UluFormSelect.vue.d.ts.map +1 -1
  25. package/dist/components/forms/UluFormSelect.vue.js +24 -43
  26. package/dist/components/forms/UluFormText.vue.d.ts +5 -23
  27. package/dist/components/forms/UluFormText.vue.d.ts.map +1 -1
  28. package/dist/components/forms/UluFormText.vue.js +10 -38
  29. package/dist/components/forms/UluFormTextarea.vue.d.ts +5 -23
  30. package/dist/components/forms/UluFormTextarea.vue.d.ts.map +1 -1
  31. package/dist/components/forms/UluFormTextarea.vue.js +10 -37
  32. package/dist/components/forms/UluSearchForm.vue.d.ts +24 -3
  33. package/dist/components/forms/UluSearchForm.vue.d.ts.map +1 -1
  34. package/dist/components/forms/UluSearchForm.vue.js +67 -22
  35. package/dist/components/index.d.ts +1 -0
  36. package/dist/components/systems/facets/UluFacetsFilterSelects.vue.d.ts.map +1 -1
  37. package/dist/components/systems/facets/UluFacetsFilterSelects.vue.js +21 -22
  38. package/dist/components/systems/facets/useFacets.d.ts.map +1 -1
  39. package/dist/components/systems/scroll-anchors/useScrollAnchorSection.d.ts.map +1 -1
  40. package/dist/components/systems/scroll-anchors/useScrollAnchorSections.d.ts.map +1 -1
  41. package/dist/components/systems/scroll-anchors/useScrollAnchors.d.ts.map +1 -1
  42. package/dist/components/utils/UluAction.vue.d.ts +2 -0
  43. package/dist/components/utils/UluAction.vue.d.ts.map +1 -1
  44. package/dist/components/utils/UluAction.vue.js +9 -5
  45. package/dist/components/visualizations/UluProgressBar.vue.d.ts +2 -2
  46. package/dist/composables/useBreakpointManager.d.ts.map +1 -1
  47. package/dist/composables/useDocumentTitle.d.ts.map +1 -1
  48. package/dist/composables/useIcon.d.ts +3 -0
  49. package/dist/composables/useIcon.d.ts.map +1 -1
  50. package/dist/composables/useModifiers.d.ts.map +1 -1
  51. package/dist/composables/usePagination.d.ts.map +1 -1
  52. package/dist/composables/useRequiredInject.d.ts.map +1 -1
  53. package/dist/composables/useTableData.d.ts.map +1 -1
  54. package/dist/composables/useUluFloating.d.ts.map +1 -1
  55. package/dist/composables/useWindowResize.d.ts.map +1 -1
  56. package/dist/index.js +130 -128
  57. package/dist/mcp-data.json +17685 -0
  58. package/dist/plugins/breakpoints/index.d.ts.map +1 -1
  59. package/dist/plugins/core/index.d.ts.map +1 -1
  60. package/dist/plugins/core/index.js +17 -16
  61. package/dist/plugins/modals/api.d.ts.map +1 -1
  62. package/dist/plugins/modals/index.d.ts.map +1 -1
  63. package/dist/plugins/modals/useModals.d.ts.map +1 -1
  64. package/dist/plugins/popovers/defaults.d.ts.map +1 -1
  65. package/dist/plugins/popovers/index.d.ts.map +1 -1
  66. package/dist/plugins/popovers/useTooltip.d.ts.map +1 -1
  67. package/dist/plugins/popovers/useTooltipFollow.d.ts.map +1 -1
  68. package/dist/plugins/toast/index.d.ts.map +1 -1
  69. package/dist/plugins/toast/store.d.ts.map +1 -1
  70. package/dist/plugins/toast/useToast.d.ts.map +1 -1
  71. package/dist/resolver.d.ts.map +1 -1
  72. package/dist/utils/dom.d.ts +3 -0
  73. package/dist/utils/dom.d.ts.map +1 -1
  74. package/dist/utils/props.d.ts +10 -0
  75. package/dist/utils/props.d.ts.map +1 -1
  76. package/dist/utils/props.js +8 -2
  77. package/dist/utils/router.d.ts +12 -15
  78. package/dist/utils/router.d.ts.map +1 -1
  79. package/lib/components/elements/UluBadge.vue +16 -5
  80. package/lib/components/elements/UluButton.vue +18 -3
  81. package/lib/components/elements/UluIcon.vue +8 -26
  82. package/lib/components/forms/UluForm.vue +25 -25
  83. package/lib/components/forms/UluFormCheckbox.vue +11 -25
  84. package/lib/components/forms/UluFormFieldset.vue +6 -6
  85. package/lib/components/forms/UluFormFile.vue +10 -40
  86. package/lib/components/forms/UluFormItem.vue +150 -39
  87. package/lib/components/forms/UluFormLabel.vue +30 -0
  88. package/lib/components/forms/UluFormRadio.vue +15 -34
  89. package/lib/components/forms/UluFormSelect.vue +19 -24
  90. package/lib/components/forms/UluFormText.vue +7 -25
  91. package/lib/components/forms/UluFormTextarea.vue +7 -25
  92. package/lib/components/forms/UluSearchForm.vue +67 -19
  93. package/lib/components/forms/UluSelectableMenu.vue +62 -62
  94. package/lib/components/index.js +4 -0
  95. package/lib/components/systems/facets/UluFacetsFilterSelects.vue +11 -14
  96. package/lib/components/systems/facets/useFacets.js +3 -0
  97. package/lib/components/systems/index.js +3 -0
  98. package/lib/components/systems/scroll-anchors/useScrollAnchorSection.js +3 -0
  99. package/lib/components/systems/scroll-anchors/useScrollAnchorSections.js +3 -0
  100. package/lib/components/systems/scroll-anchors/useScrollAnchors.js +3 -0
  101. package/lib/components/utils/UluAction.vue +6 -2
  102. package/lib/composables/useBreakpointManager.js +3 -0
  103. package/lib/composables/useDocumentTitle.js +3 -0
  104. package/lib/composables/useIcon.js +3 -0
  105. package/lib/composables/useModifiers.js +3 -1
  106. package/lib/composables/usePagination.js +3 -0
  107. package/lib/composables/useRequiredInject.js +3 -0
  108. package/lib/composables/useTableData.js +3 -0
  109. package/lib/composables/useUluFloating.js +3 -0
  110. package/lib/composables/useWindowResize.js +3 -0
  111. package/lib/index.js +1 -1
  112. package/lib/meta.js +1 -1
  113. package/lib/plugins/breakpoints/index.js +3 -0
  114. package/lib/plugins/core/index.js +4 -2
  115. package/lib/plugins/index.js +1 -1
  116. package/lib/plugins/modals/api.js +3 -0
  117. package/lib/plugins/modals/index.js +2 -3
  118. package/lib/plugins/modals/useModals.js +3 -0
  119. package/lib/plugins/popovers/defaults.js +3 -0
  120. package/lib/plugins/popovers/index.js +3 -0
  121. package/lib/plugins/popovers/useTooltip.js +3 -0
  122. package/lib/plugins/popovers/useTooltipFollow.js +3 -0
  123. package/lib/plugins/toast/defaults.js +3 -0
  124. package/lib/plugins/toast/index.js +3 -0
  125. package/lib/plugins/toast/store.js +3 -0
  126. package/lib/plugins/toast/useToast.js +3 -0
  127. package/lib/resolver.js +3 -0
  128. package/lib/utils/dom.js +3 -0
  129. package/lib/utils/index.js +3 -0
  130. package/lib/utils/props.js +17 -0
  131. package/lib/utils/router.js +10 -10
  132. package/lib/vite.js +3 -0
  133. package/package.json +19 -6
@@ -1,49 +1,160 @@
1
1
  <template>
2
2
  <div
3
3
  class="form-theme__item"
4
- :class="[{
5
- 'is-danger': error,
6
- 'is-warning': warning,
7
- 'form-theme__item--align-top': alignTop,
8
- 'form-theme__item--text': text,
9
- 'form-theme__item--file': file,
10
- 'form-theme__item--select': select,
11
- 'form-theme__item--textarea': textarea
12
- }]"
4
+ :class="[
5
+ resolvedModifiers,
6
+ {
7
+ 'is-danger': hasError,
8
+ 'is-warning': hasWarning
9
+ }
10
+ ]"
13
11
  >
12
+ <UluFormLabel
13
+ v-if="!isLabelAfter && hasLabel"
14
+ :id="internalId"
15
+ :labelHidden="labelHidden"
16
+ :required="required"
17
+ >
18
+ <slot name="label">{{ label }}</slot>
19
+ </UluFormLabel>
20
+
14
21
  <slot />
22
+
23
+ <UluFormLabel
24
+ v-if="isLabelAfter && hasLabel"
25
+ :id="internalId"
26
+ :labelHidden="labelHidden"
27
+ :required="required"
28
+ >
29
+ <slot name="label">{{ label }}</slot>
30
+ </UluFormLabel>
31
+
32
+ <UluFormMessage
33
+ v-if="description || $slots.description"
34
+ :id="descriptionId"
35
+ >
36
+ <slot name="description">{{ description }}</slot>
37
+ </UluFormMessage>
38
+
39
+ <UluFormMessage
40
+ v-if="errorMessage || $slots.errorMessage"
41
+ :id="errorId"
42
+ error
43
+ >
44
+ <slot name="errorMessage">{{ errorMessage }}</slot>
45
+ </UluFormMessage>
46
+
47
+ <UluFormMessage
48
+ v-if="warningMessage || $slots.warningMessage"
49
+ :id="warningId"
50
+ warning
51
+ >
52
+ <slot name="warningMessage">{{ warningMessage }}</slot>
53
+ </UluFormMessage>
15
54
  </div>
16
55
  </template>
17
56
 
18
57
  <script setup>
19
- defineProps({
20
- /**
21
- * If true, applies the error state styles.
22
- */
23
- error: Boolean,
24
- /**
25
- * If true, applies the warning state styles.
26
- */
27
- warning: Boolean,
28
- /**
29
- * If true, aligns the item to the top.
30
- */
31
- alignTop: Boolean,
32
- /**
33
- * If true, applies the text item styles.
34
- */
35
- text: Boolean,
36
- /**
37
- * If true, applies the file item styles.
38
- */
39
- file: Boolean,
40
- /**
41
- * If true, applies the select item styles.
42
- */
43
- select: Boolean,
44
- /**
45
- * If true, applies the textarea item styles.
46
- */
47
- textarea: Boolean
48
- });
58
+ import { provide, computed, useSlots } from "vue";
59
+ import { newId } from "../../utils/dom.js";
60
+ import { useModifiers } from "../../composables/useModifiers.js";
61
+ import UluFormMessage from "./UluFormMessage.vue";
62
+ import UluFormLabel from "./UluFormLabel.vue";
63
+
64
+ const props = defineProps({
65
+ /**
66
+ * The layout variant for this form item (e.g., 'text', 'select', 'textarea', 'checkbox', 'radio', 'file').
67
+ * This determines the layout and BEM styling of the item container.
68
+ */
69
+ layout: String,
70
+ /**
71
+ * The ID to use for the form field. If not provided, a unique ID is generated.
72
+ */
73
+ fieldId: String,
74
+ /**
75
+ * The label for the form field.
76
+ */
77
+ label: String,
78
+ /**
79
+ * If true, the label will be visually hidden.
80
+ */
81
+ labelHidden: Boolean,
82
+ /**
83
+ * The description text for the form field.
84
+ */
85
+ description: String,
86
+ /**
87
+ * The error message text.
88
+ */
89
+ errorMessage: String,
90
+ /**
91
+ * The warning message text.
92
+ */
93
+ warningMessage: String,
94
+ /**
95
+ * If true, the field will be marked as required.
96
+ */
97
+ required: Boolean,
98
+ /**
99
+ * If true, applies the error state styles.
100
+ */
101
+ error: Boolean,
102
+ /**
103
+ * If true, applies the warning state styles.
104
+ */
105
+ warning: Boolean,
106
+ /**
107
+ * If true, aligns the item to the top.
108
+ */
109
+ alignTop: Boolean,
110
+ /**
111
+ * Additional BEM modifiers for the form item.
112
+ */
113
+ modifiers: [String, Array]
114
+ });
115
+
116
+ const slots = useSlots();
117
+
118
+ const { resolvedModifiers } = useModifiers({
119
+ props,
120
+ baseClass: "form-theme__item",
121
+ internal: computed(() => ({
122
+ [props.layout]: props.layout,
123
+ "align-top": props.alignTop
124
+ }))
125
+ });
126
+
127
+ const internalId = computed(() => props.fieldId || newId());
128
+ const descriptionId = computed(() => `${ internalId.value }-desc`);
129
+ const errorId = computed(() => `${ internalId.value }-error`);
130
+ const warningId = computed(() => `${ internalId.value }-warn`);
131
+
132
+ const hasLabel = computed(() => !!props.label || !!slots.label);
133
+ const hasError = computed(() => props.error || !!props.errorMessage || !!slots.errorMessage);
134
+ const hasWarning = computed(() => props.warning || !!props.warningMessage || !!slots.warningMessage);
135
+
136
+ const isLabelAfter = computed(() => ["checkbox", "radio"].includes(props.layout));
137
+
138
+ const fieldAttrs = computed(() => {
139
+ const attrs = {
140
+ id: internalId.value
141
+ };
142
+
143
+ const describedBy = [];
144
+ if (props.description || slots.description) describedBy.push(descriptionId.value);
145
+ if (props.errorMessage || slots.errorMessage) describedBy.push(errorId.value);
146
+ if (props.warningMessage || slots.warningMessage) describedBy.push(warningId.value);
147
+
148
+ if (describedBy.length > 0) {
149
+ attrs["aria-describedby"] = describedBy.join(" ");
150
+ }
151
+
152
+ if (hasError.value) {
153
+ attrs["aria-invalid"] = "true";
154
+ }
155
+
156
+ return attrs;
157
+ });
158
+
159
+ provide("uluFormFieldAttrs", fieldAttrs);
49
160
  </script>
@@ -0,0 +1,30 @@
1
+ <template>
2
+ <label
3
+ :for="id"
4
+ :class="{ 'hidden-visually': labelHidden }"
5
+ >
6
+ <slot></slot><UluFormRequiredChar v-if="required" />
7
+ </label>
8
+ </template>
9
+
10
+ <script setup>
11
+ import UluFormRequiredChar from "./UluFormRequiredChar.vue";
12
+
13
+ defineProps({
14
+ /**
15
+ * The ID of the input this label is associated with.
16
+ */
17
+ id: {
18
+ type: String,
19
+ required: true
20
+ },
21
+ /**
22
+ * If true, the label will be visually hidden.
23
+ */
24
+ labelHidden: Boolean,
25
+ /**
26
+ * If true, appends the required character.
27
+ */
28
+ required: Boolean
29
+ });
30
+ </script>
@@ -1,48 +1,29 @@
1
1
  <template>
2
2
  <input
3
3
  type="radio"
4
- :id="id"
5
- :name="name"
4
+ v-bind="fieldAttrs"
6
5
  :value="value"
7
6
  :checked="modelValue === value"
8
7
  @change="$emit('update:modelValue', value)"
9
- :required="required"
10
8
  >
11
- <label :for="id">
12
- <slot>
13
- {{ label }}<UluFormRequiredChar v-if="required" />
14
- </slot>
15
- </label>
16
9
  </template>
17
10
 
18
11
  <script setup>
19
- import { newId } from "../../utils/dom.js";
20
- import UluFormRequiredChar from "./UluFormRequiredChar.vue";
12
+ import { inject, computed } from "vue";
21
13
 
22
- defineProps({
23
- /**
24
- * The label for the radio button.
25
- */
26
- label: String,
27
- /**
28
- * The value of the selected radio button in the group (for v-model).
29
- */
30
- modelValue: [String, Number],
31
- /**
32
- * The value of this radio button.
33
- */
34
- value: [String, Number],
35
- /**
36
- * The name of the radio button group.
37
- */
38
- name: String,
39
- /**
40
- * If true, the field will be required.
41
- */
42
- required: Boolean
43
- });
14
+ const props = defineProps({
15
+ /**
16
+ * The value of the selected radio button in the group (for v-model).
17
+ */
18
+ modelValue: [String, Number, Boolean],
19
+ /**
20
+ * The value of this radio button.
21
+ */
22
+ value: [String, Number, Boolean]
23
+ });
44
24
 
45
- defineEmits(['update:modelValue']);
25
+ defineEmits(["update:modelValue"]);
46
26
 
47
- const id = newId();
27
+ const injectedAttrs = inject("uluFormFieldAttrs", null);
28
+ const fieldAttrs = computed(() => injectedAttrs ? injectedAttrs.value : {});
48
29
  </script>
@@ -1,16 +1,16 @@
1
1
  <template>
2
- <label :class="{ 'hidden-visually' : labelHidden }" :for="id">
3
- <slot name="label">
4
- {{ label }}<UluFormRequiredChar v-if="required" />
5
- </slot>
6
- </label>
7
2
  <select
8
- :id="id"
3
+ v-bind="fieldAttrs"
9
4
  :value="modelValue"
10
5
  @input="$emit('update:modelValue', $event.target.value)"
11
- :required="required"
12
6
  >
13
- <option disabled value="">Please select one</option>
7
+ <option
8
+ v-if="placeholder !== false"
9
+ disabled
10
+ value=""
11
+ >
12
+ {{ placeholder || "Please select one" }}
13
+ </option>
14
14
  <option v-for="(option, index) in options" :key="index" :value="option.value">
15
15
  {{ option.text }}
16
16
  </option>
@@ -18,33 +18,28 @@
18
18
  </template>
19
19
 
20
20
  <script setup>
21
- import { newId } from "../../utils/dom.js";
22
- import UluFormRequiredChar from "./UluFormRequiredChar.vue";
21
+ import { inject, computed } from "vue";
23
22
 
24
- defineProps({
25
- /**
26
- * The label for the select input.
27
- */
28
- label: String,
23
+ const props = defineProps({
29
24
  /**
30
25
  * The value of the select input (for v-model).
31
26
  */
32
- modelValue: String,
27
+ modelValue: [String, Number, Array],
33
28
  /**
34
29
  * An array of options for the select input. Each option should be an object with `value` and `text` properties.
35
30
  */
36
31
  options: Array,
37
32
  /**
38
- * If true, the label will be visually hidden.
39
- */
40
- labelHidden: Boolean,
41
- /**
42
- * If true, the field will be required.
33
+ * The text for the default disabled option. Pass false to hide it.
43
34
  */
44
- required: Boolean
35
+ placeholder: {
36
+ type: [String, Boolean],
37
+ default: "Please select one"
38
+ }
45
39
  });
46
40
 
47
- defineEmits(['update:modelValue']);
41
+ defineEmits(["update:modelValue"]);
48
42
 
49
- const id = newId();
43
+ const injectedAttrs = inject("uluFormFieldAttrs", null);
44
+ const fieldAttrs = computed(() => injectedAttrs ? injectedAttrs.value : {});
50
45
  </script>
@@ -1,42 +1,24 @@
1
1
  <template>
2
- <label :class="{ 'hidden-visually' : labelHidden }" :for="id">
3
- <slot name="label">
4
- {{ label }}<UluFormRequiredChar v-if="required" />
5
- </slot>
6
- </label>
7
2
  <input
8
3
  type="text"
4
+ v-bind="fieldAttrs"
9
5
  :value="modelValue"
10
6
  @input="$emit('update:modelValue', $event.target.value)"
11
- :id="id"
12
- :required="required"
13
7
  >
14
8
  </template>
15
9
 
16
10
  <script setup>
17
- import { newId } from "../../utils/dom.js";
18
- import UluFormRequiredChar from "./UluFormRequiredChar.vue";
11
+ import { inject, computed } from "vue";
19
12
 
20
- defineProps({
21
- /**
22
- * The label for the text input.
23
- */
24
- label: String,
13
+ const props = defineProps({
25
14
  /**
26
15
  * The value of the text input (for v-model).
27
16
  */
28
- modelValue: String,
29
- /**
30
- * If true, the label will be visually hidden.
31
- */
32
- labelHidden: Boolean,
33
- /**
34
- * If true, the field will be required.
35
- */
36
- required: Boolean
17
+ modelValue: [String, Number]
37
18
  });
38
19
 
39
- defineEmits(['update:modelValue']);
20
+ defineEmits(["update:modelValue"]);
40
21
 
41
- const id = newId();
22
+ const injectedAttrs = inject("uluFormFieldAttrs", null);
23
+ const fieldAttrs = computed(() => injectedAttrs ? injectedAttrs.value : {});
42
24
  </script>
@@ -1,41 +1,23 @@
1
1
  <template>
2
- <label :class="{ 'hidden-visually' : labelHidden }" :for="id">
3
- <slot name="label">
4
- {{ label }}<UluFormRequiredChar v-if="required" />
5
- </slot>
6
- </label>
7
2
  <textarea
3
+ v-bind="fieldAttrs"
8
4
  :value="modelValue"
9
5
  @input="$emit('update:modelValue', $event.target.value)"
10
- :id="id"
11
- :required="required"
12
6
  ></textarea>
13
7
  </template>
14
8
 
15
9
  <script setup>
16
- import { newId } from "../../utils/dom.js";
17
- import UluFormRequiredChar from "./UluFormRequiredChar.vue";
10
+ import { inject, computed } from "vue";
18
11
 
19
- defineProps({
20
- /**
21
- * The label for the textarea.
22
- */
23
- label: String,
12
+ const props = defineProps({
24
13
  /**
25
14
  * The value of the textarea (for v-model).
26
15
  */
27
- modelValue: String,
28
- /**
29
- * If true, the label will be visually hidden.
30
- */
31
- labelHidden: Boolean,
32
- /**
33
- * If true, the field will be required.
34
- */
35
- required: Boolean
16
+ modelValue: [String, Number]
36
17
  });
37
18
 
38
- defineEmits(['update:modelValue']);
19
+ defineEmits(["update:modelValue"]);
39
20
 
40
- const id = newId();
21
+ const injectedAttrs = inject("uluFormFieldAttrs", null);
22
+ const fieldAttrs = computed(() => injectedAttrs ? injectedAttrs.value : {});
41
23
  </script>
@@ -1,32 +1,80 @@
1
1
  <template>
2
- <div class="form-theme search-form type-small">
3
- <div class="search-form__field">
4
- <label class="hidden-visually">Search</label>
5
- <input
6
- class="search-form__input"
7
- type="text"
8
- id="example-input"
9
- :placeholder="placeholder"
10
- >
2
+ <form class="form-theme" @submit.prevent="onSubmit">
3
+ <div class="input-group input-group--joined">
4
+ <div class="input-group__item input-group__item--field">
5
+ <label :for="inputId" class="hidden-visually">{{ label }}</label>
6
+ <input
7
+ type="search"
8
+ :id="inputId"
9
+ class="input-group__input"
10
+ :placeholder="placeholder"
11
+ :value="modelValue"
12
+ @input="$emit('update:modelValue', $event.target.value)"
13
+ >
14
+ </div>
15
+ <div class="input-group__item">
16
+ <slot name="submit">
17
+ <UluButton
18
+ class="input-group__button"
19
+ v-bind="submitButtonProps"
20
+ />
21
+ </slot>
22
+ </div>
11
23
  </div>
12
- <button
13
- class="search-form__submit button button--primary"
14
- aria-label="Submit Search"
15
- >
16
- <UluIcon icon="type:search" />
17
- </button>
18
- </div>
24
+ </form>
19
25
  </template>
20
26
 
21
27
  <script setup>
22
- import UluIcon from "../elements/UluIcon.vue";
23
- defineProps({
28
+ import { computed } from "vue";
29
+ import { newId } from "../../utils/dom.js";
30
+ import UluButton from "../elements/UluButton.vue";
31
+
32
+ const props = defineProps({
33
+ /**
34
+ * The search input value (for v-model).
35
+ */
36
+ modelValue: {
37
+ type: String,
38
+ default: ""
39
+ },
24
40
  /**
25
41
  * The placeholder text for the search input.
26
42
  */
27
43
  placeholder: {
28
44
  type: String,
29
45
  default: "Titles, keyword…"
30
- }
46
+ },
47
+ /**
48
+ * The visually hidden label for the search input.
49
+ */
50
+ label: {
51
+ type: String,
52
+ default: "Search"
53
+ },
54
+ /**
55
+ * Props to pass to the default UluButton component (used for submit button)
56
+ * - Alternately use 'submit' slot
57
+ */
58
+ submitButtonProps: {
59
+ type: Object,
60
+ default: () => ({
61
+ type: "submit",
62
+ primary: true,
63
+ icon: "type:search",
64
+ ariaLabel: "Submit Search"
65
+ })
66
+ },
67
+ /**
68
+ * Optional ID for the input element. If not provided, a unique ID is generated.
69
+ */
70
+ id: String
31
71
  });
72
+
73
+ const emit = defineEmits(["update:modelValue", "submit"]);
74
+
75
+ const inputId = computed(() => props.id || newId());
76
+
77
+ const onSubmit = () => {
78
+ emit("submit", props.modelValue);
79
+ };
32
80
  </script>