@shamar/adonis 0.1.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 (134) hide show
  1. package/assets/admin.css +2 -0
  2. package/assets/alpine.min.js +5 -0
  3. package/assets/input.css +2286 -0
  4. package/assets/shamar-ui.js +3658 -0
  5. package/dist/assets-controller.d.ts +12 -0
  6. package/dist/assets-controller.d.ts.map +1 -0
  7. package/dist/assets-controller.js +32 -0
  8. package/dist/assets-controller.js.map +1 -0
  9. package/dist/config.d.ts +79 -0
  10. package/dist/config.d.ts.map +1 -0
  11. package/dist/config.js +38 -0
  12. package/dist/config.js.map +1 -0
  13. package/dist/configure.d.ts +21 -0
  14. package/dist/configure.d.ts.map +1 -0
  15. package/dist/configure.js +20 -0
  16. package/dist/configure.js.map +1 -0
  17. package/dist/context.d.ts +22 -0
  18. package/dist/context.d.ts.map +1 -0
  19. package/dist/context.js +2 -0
  20. package/dist/context.js.map +1 -0
  21. package/dist/controller.d.ts +31 -0
  22. package/dist/controller.d.ts.map +1 -0
  23. package/dist/controller.js +45 -0
  24. package/dist/controller.js.map +1 -0
  25. package/dist/controllers/admin_controller.d.ts +72 -0
  26. package/dist/controllers/admin_controller.d.ts.map +1 -0
  27. package/dist/controllers/admin_controller.js +1075 -0
  28. package/dist/controllers/admin_controller.js.map +1 -0
  29. package/dist/discover.d.ts +7 -0
  30. package/dist/discover.d.ts.map +1 -0
  31. package/dist/discover.js +50 -0
  32. package/dist/discover.js.map +1 -0
  33. package/dist/form-state.d.ts +41 -0
  34. package/dist/form-state.d.ts.map +1 -0
  35. package/dist/form-state.js +74 -0
  36. package/dist/form-state.js.map +1 -0
  37. package/dist/index.d.ts +10 -0
  38. package/dist/index.d.ts.map +1 -0
  39. package/dist/index.js +10 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/middleware/require_api_key_middleware.d.ts +30 -0
  42. package/dist/middleware/require_api_key_middleware.d.ts.map +1 -0
  43. package/dist/middleware/require_api_key_middleware.js +59 -0
  44. package/dist/middleware/require_api_key_middleware.js.map +1 -0
  45. package/dist/provider.d.ts +9 -0
  46. package/dist/provider.d.ts.map +1 -0
  47. package/dist/provider.js +66 -0
  48. package/dist/provider.js.map +1 -0
  49. package/dist/resources/api_key_resource.d.ts +35 -0
  50. package/dist/resources/api_key_resource.d.ts.map +1 -0
  51. package/dist/resources/api_key_resource.js +225 -0
  52. package/dist/resources/api_key_resource.js.map +1 -0
  53. package/dist/routes.d.ts +5 -0
  54. package/dist/routes.d.ts.map +1 -0
  55. package/dist/routes.js +97 -0
  56. package/dist/routes.js.map +1 -0
  57. package/dist/runtime.d.ts +27 -0
  58. package/dist/runtime.d.ts.map +1 -0
  59. package/dist/runtime.js +74 -0
  60. package/dist/runtime.js.map +1 -0
  61. package/dist/shamar/auth.d.ts +30 -0
  62. package/dist/shamar/auth.d.ts.map +1 -0
  63. package/dist/shamar/auth.js +218 -0
  64. package/dist/shamar/auth.js.map +1 -0
  65. package/dist/shamar/branding.d.ts +24 -0
  66. package/dist/shamar/branding.d.ts.map +1 -0
  67. package/dist/shamar/branding.js +91 -0
  68. package/dist/shamar/branding.js.map +1 -0
  69. package/dist/shamar/display-relations.d.ts +7 -0
  70. package/dist/shamar/display-relations.d.ts.map +1 -0
  71. package/dist/shamar/display-relations.js +89 -0
  72. package/dist/shamar/display-relations.js.map +1 -0
  73. package/dist/shamar/list-headers.d.ts +38 -0
  74. package/dist/shamar/list-headers.d.ts.map +1 -0
  75. package/dist/shamar/list-headers.js +218 -0
  76. package/dist/shamar/list-headers.js.map +1 -0
  77. package/dist/shamar/list-query.d.ts +229 -0
  78. package/dist/shamar/list-query.d.ts.map +1 -0
  79. package/dist/shamar/list-query.js +564 -0
  80. package/dist/shamar/list-query.js.map +1 -0
  81. package/dist/shamar/menu.d.ts +48 -0
  82. package/dist/shamar/menu.d.ts.map +1 -0
  83. package/dist/shamar/menu.js +93 -0
  84. package/dist/shamar/menu.js.map +1 -0
  85. package/dist/shamar/paths.d.ts +5 -0
  86. package/dist/shamar/paths.d.ts.map +1 -0
  87. package/dist/shamar/paths.js +16 -0
  88. package/dist/shamar/paths.js.map +1 -0
  89. package/dist/shamar/relation-fields.d.ts +54 -0
  90. package/dist/shamar/relation-fields.d.ts.map +1 -0
  91. package/dist/shamar/relation-fields.js +94 -0
  92. package/dist/shamar/relation-fields.js.map +1 -0
  93. package/dist/shamar/resource-actions.d.ts +9 -0
  94. package/dist/shamar/resource-actions.d.ts.map +1 -0
  95. package/dist/shamar/resource-actions.js +42 -0
  96. package/dist/shamar/resource-actions.js.map +1 -0
  97. package/dist/shamar/view-context.d.ts +79 -0
  98. package/dist/shamar/view-context.d.ts.map +1 -0
  99. package/dist/shamar/view-context.js +74 -0
  100. package/dist/shamar/view-context.js.map +1 -0
  101. package/dist/types.d.ts +15 -0
  102. package/dist/types.d.ts.map +1 -0
  103. package/dist/types.js +2 -0
  104. package/dist/types.js.map +1 -0
  105. package/package.json +67 -0
  106. package/resources/views/shamar/dashboard.edge +24 -0
  107. package/resources/views/shamar/form.edge +87 -0
  108. package/resources/views/shamar/index.edge +149 -0
  109. package/resources/views/shamar/partials/boolean-value.edge +25 -0
  110. package/resources/views/shamar/partials/csrf.edge +1 -0
  111. package/resources/views/shamar/partials/detail-entry.edge +72 -0
  112. package/resources/views/shamar/partials/dialog-host.edge +125 -0
  113. package/resources/views/shamar/partials/field-affix-icon.edge +23 -0
  114. package/resources/views/shamar/partials/field-input.edge +585 -0
  115. package/resources/views/shamar/partials/field-relation-checkbox.edge +104 -0
  116. package/resources/views/shamar/partials/field-relation-m2m.edge +110 -0
  117. package/resources/views/shamar/partials/field-relation-m2o.edge +116 -0
  118. package/resources/views/shamar/partials/field-relation-table.edge +139 -0
  119. package/resources/views/shamar/partials/form-container.edge +82 -0
  120. package/resources/views/shamar/partials/icon.edge +14 -0
  121. package/resources/views/shamar/partials/list-row.edge +73 -0
  122. package/resources/views/shamar/partials/list-toolbar.edge +269 -0
  123. package/resources/views/shamar/partials/nav-mobile.edge +21 -0
  124. package/resources/views/shamar/partials/nav.edge +58 -0
  125. package/resources/views/shamar/partials/page-heading.edge +130 -0
  126. package/resources/views/shamar/partials/pagination.edge +73 -0
  127. package/resources/views/shamar/partials/record-pager.edge +37 -0
  128. package/resources/views/shamar/partials/schema-node.edge +289 -0
  129. package/resources/views/shamar/partials/shell-close.edge +20 -0
  130. package/resources/views/shamar/partials/shell-open.edge +51 -0
  131. package/resources/views/shamar/partials/toast.edge +5 -0
  132. package/resources/views/shamar/partials/topbar.edge +115 -0
  133. package/resources/views/shamar/show.edge +17 -0
  134. package/stubs/config/shamar.stub +29 -0
@@ -0,0 +1,585 @@
1
+ @if(field.type === 'hidden')
2
+ <input
3
+ type="hidden"
4
+ name="{{ field.name }}"
5
+ x-model="state['{{ field.name }}']"
6
+ />
7
+ @else
8
+ @let(fieldLabelText = field.label ?? humanizeLabel(field.name))
9
+ @let(hasTextPrefix = !!(field.prefix || field.prefixIcon))
10
+ @let(hasTextSuffix = !!(field.suffix || field.suffixIcon))
11
+ @let(hasActionSuffix = !!(field.copyable || (field.revealable && field.type === 'password')))
12
+ @let(prefixInline = field.inlinePrefix !== false)
13
+ @let(suffixInline = field.inlineSuffix !== false)
14
+ @let(showInlinePrefix = hasTextPrefix && prefixInline)
15
+ @let(showInlineSuffix = (hasTextSuffix && suffixInline) || hasActionSuffix)
16
+ @let(showStackedPrefix = hasTextPrefix && !prefixInline)
17
+ @let(showStackedSuffix = hasTextSuffix && !suffixInline)
18
+ @let(useGroup = showInlinePrefix || showInlineSuffix)
19
+ <div class="shamar-field {{ alignmentTextClass(field.alignment) }}" data-field-type="{{ field.type }}" data-field-name="{{ field.name }}">
20
+ @if(!((field.type === 'boolean' || field.type === 'checkbox') && field.inline))
21
+ <div class="shamar-field__label-row">
22
+ <label for="field-{{ field.name }}" class="shamar-field__label">
23
+ <span x-text='fieldLabel({{ jsonAttr(field.name) }}, {{ jsonAttr(fieldLabelText) }})'>{{ fieldLabelText }}</span>
24
+ <span class="shamar-field__required" x-show="isRequired('{{ field.name }}')" x-cloak>*</span>
25
+ </label>
26
+ @if(field.hint)
27
+ <span
28
+ class="shamar-field__hint"
29
+ x-text='fieldMeta[{{ jsonAttr(field.name) }}]?.hint || {{ jsonAttr(field.hint) }}'
30
+ >{{ field.hint }}</span>
31
+ @endif
32
+ </div>
33
+ @endif
34
+
35
+ @if(field.type === 'boolean')
36
+ <label class="shamar-toggle{{ field.inline ? ' shamar-toggle--inline' : '' }}" for="field-{{ field.name }}" :class="{ 'is-disabled': isDisabled('{{ field.name }}') }">
37
+ <input
38
+ id="field-{{ field.name }}"
39
+ type="checkbox"
40
+ name="{{ field.name }}"
41
+ value="true"
42
+ class="shamar-toggle__input"
43
+ x-model="state['{{ field.name }}']"
44
+ @change="onFieldChange('{{ field.name }}')"
45
+ :disabled="isDisabled('{{ field.name }}')"
46
+ {{ field.required ? 'required' : '' }}
47
+ />
48
+ <span class="shamar-toggle__track" aria-hidden="true">
49
+ <span class="shamar-toggle__thumb"></span>
50
+ </span>
51
+ @if(field.inline)
52
+ <span class="shamar-toggle__text">
53
+ <span class="shamar-toggle__label">
54
+ <span x-text='fieldLabel({{ jsonAttr(field.name) }}, {{ jsonAttr(fieldLabelText) }})'>{{ fieldLabelText }}</span>
55
+ @if(field.required)
56
+ <span class="shamar-field__required">*</span>
57
+ @endif
58
+ </span>
59
+ @if(field.hint)
60
+ <span
61
+ class="shamar-field__hint"
62
+ x-text='fieldMeta[{{ jsonAttr(field.name) }}]?.hint || {{ jsonAttr(field.hint) }}'
63
+ >{{ field.hint }}</span>
64
+ @endif
65
+ </span>
66
+ @endif
67
+ </label>
68
+ @elseif(field.type === 'checkbox')
69
+ <label class="shamar-checkbox{{ field.inline ? ' shamar-checkbox--inline' : '' }}" for="field-{{ field.name }}" :class="{ 'is-disabled': isDisabled('{{ field.name }}') }">
70
+ <input
71
+ id="field-{{ field.name }}"
72
+ type="checkbox"
73
+ name="{{ field.name }}"
74
+ value="true"
75
+ class="shamar-checkbox__input"
76
+ x-model="state['{{ field.name }}']"
77
+ @change="onFieldChange('{{ field.name }}')"
78
+ :disabled="isDisabled('{{ field.name }}')"
79
+ {{ field.required ? 'required' : '' }}
80
+ />
81
+ @if(field.inline)
82
+ <span class="shamar-checkbox__text">
83
+ <span class="shamar-checkbox__label">
84
+ <span x-text='fieldLabel({{ jsonAttr(field.name) }}, {{ jsonAttr(fieldLabelText) }})'>{{ fieldLabelText }}</span>
85
+ @if(field.required)
86
+ <span class="shamar-field__required">*</span>
87
+ @endif
88
+ </span>
89
+ @if(field.hint)
90
+ <span
91
+ class="shamar-field__hint"
92
+ x-text='fieldMeta[{{ jsonAttr(field.name) }}]?.hint || {{ jsonAttr(field.hint) }}'
93
+ >{{ field.hint }}</span>
94
+ @endif
95
+ </span>
96
+ @endif
97
+ </label>
98
+ @elseif(field.type === 'radio')
99
+ <div class="shamar-radio{{ field.inline ? ' shamar-radio--inline' : '' }}">
100
+ @if(relationUi && relationUi[field.name] && relationUi[field.name].options)
101
+ @each(option in relationUi[field.name].options)
102
+ <label class="shamar-radio__option">
103
+ <input
104
+ type="radio"
105
+ name="{{ field.name }}"
106
+ value="{{ option.id }}"
107
+ x-model="state['{{ field.name }}']"
108
+ @change="onFieldChange('{{ field.name }}')"
109
+ :disabled="isDisabled('{{ field.name }}')"
110
+ {{ field.required ? 'required' : '' }}
111
+ />
112
+ <span>{{ option.label }}</span>
113
+ </label>
114
+ @endeach
115
+ @if(relationUi[field.name].options.length === 0)
116
+ <p class="text-xs text-body-subtle">No options available.</p>
117
+ @endif
118
+ @else
119
+ @each(option in field.options)
120
+ <label class="shamar-radio__option">
121
+ <input
122
+ type="radio"
123
+ name="{{ field.name }}"
124
+ value="{{ option.value }}"
125
+ x-model="state['{{ field.name }}']"
126
+ @change="onFieldChange('{{ field.name }}')"
127
+ :disabled="isDisabled('{{ field.name }}')"
128
+ {{ field.required ? 'required' : '' }}
129
+ />
130
+ <span>{{ option.label }}</span>
131
+ </label>
132
+ @endeach
133
+ @endif
134
+ </div>
135
+ @elseif(field.type === 'relation' || field.type === 'checkboxList' || field.type === 'relationTable')
136
+ @if(!(relationUi && relationUi[field.name]))
137
+ <p class="text-xs text-fg-danger">Relation field "{{ field.name }}" is missing UI config.</p>
138
+ @elseif(relationUi[field.name].widget === 'table' || field.type === 'relationTable')
139
+ @!component('shamar::partials/field-relation-table', {
140
+ field: field,
141
+ relationConfig: relationUi[field.name],
142
+ })
143
+ @elseif(relationUi[field.name].widget === 'checkboxList' || field.type === 'checkboxList')
144
+ @!component('shamar::partials/field-relation-checkbox', {
145
+ field: field,
146
+ relationConfig: relationUi[field.name],
147
+ })
148
+ @elseif(relationUi[field.name].kind === 'belongsTo' && !field.multiple)
149
+ @!component('shamar::partials/field-relation-m2o', {
150
+ field: field,
151
+ relationConfig: relationUi[field.name],
152
+ })
153
+ @else
154
+ @!component('shamar::partials/field-relation-m2m', {
155
+ field: field,
156
+ relationConfig: relationUi[field.name],
157
+ })
158
+ @endif
159
+ @elseif(field.type === 'tags')
160
+ <div class="shamar-tags">
161
+ <div class="shamar-tags__list">
162
+ <template x-for="(tag, tagIndex) in (Array.isArray(state['{{ field.name }}']) ? state['{{ field.name }}'] : [])" :key="tagIndex">
163
+ <span class="shamar-tags__chip">
164
+ <span x-text="tag"></span>
165
+ <button
166
+ type="button"
167
+ class="shamar-tags__remove"
168
+ @click="state['{{ field.name }}'] = (state['{{ field.name }}'] || []).filter((_, i) => i !== tagIndex); onFieldChange('{{ field.name }}')"
169
+ aria-label="Remove"
170
+ >&times;</button>
171
+ </span>
172
+ </template>
173
+ </div>
174
+ <input
175
+ type="text"
176
+ class="shamar-input"
177
+ :placeholder='fieldMeta[{{ jsonAttr(field.name) }}]?.placeholder || {{ jsonAttr(field.placeholder ?? "Add tag…") }}'
178
+ @keydown.enter.prevent="
179
+ const v = String($event.target.value || '').trim();
180
+ if (!v) return;
181
+ const cur = Array.isArray(state['{{ field.name }}']) ? [...state['{{ field.name }}']] : [];
182
+ if (!cur.includes(v)) { state['{{ field.name }}'] = [...cur, v]; onFieldChange('{{ field.name }}'); }
183
+ $event.target.value = '';
184
+ "
185
+ :disabled="isDisabled('{{ field.name }}')"
186
+ />
187
+ <template x-for="(tag, tagIndex) in (Array.isArray(state['{{ field.name }}']) ? state['{{ field.name }}'] : [])" :key="'h'+tagIndex">
188
+ <input type="hidden" name="{{ field.name }}[]" :value="tag" />
189
+ </template>
190
+ </div>
191
+ @elseif(field.type === 'color')
192
+ <div class="shamar-color">
193
+ <input
194
+ id="field-{{ field.name }}"
195
+ type="color"
196
+ class="shamar-color__swatch"
197
+ x-model="state['{{ field.name }}']"
198
+ @change="onFieldChange('{{ field.name }}')"
199
+ :disabled="isDisabled('{{ field.name }}')"
200
+ {{ field.required ? 'required' : '' }}
201
+ />
202
+ <input
203
+ type="text"
204
+ name="{{ field.name }}"
205
+ class="shamar-input shamar-color__value"
206
+ x-model="state['{{ field.name }}']"
207
+ readonly
208
+ tabindex="-1"
209
+ :disabled="isDisabled('{{ field.name }}')"
210
+ placeholder="#000000"
211
+ aria-label="Selected color"
212
+ />
213
+ </div>
214
+ @elseif(field.type === 'textarea')
215
+ <textarea
216
+ id="field-{{ field.name }}"
217
+ name="{{ field.name }}"
218
+ rows="{{ field.rows ?? 4 }}"
219
+ {{ field.required ? 'required' : '' }}
220
+ {{ field.readonly ? 'readonly' : '' }}
221
+ {{{ fieldInputAttrs(field) }}}
222
+ x-model="state['{{ field.name }}']"
223
+ @input="onFieldInput('{{ field.name }}')"
224
+ @change="onFieldChange('{{ field.name }}')"
225
+ @blur="onFieldBlur('{{ field.name }}')"
226
+ :disabled="isDisabled('{{ field.name }}')"
227
+ :placeholder='fieldMeta[{{ jsonAttr(field.name) }}]?.placeholder || {{ jsonAttr(field.placeholder ?? "") }}'
228
+ class="shamar-input{{ formErrors && formErrors[field.name] ? ' is-invalid' : '' }}"
229
+ ></textarea>
230
+ @if(field.maxLength)
231
+ <p class="shamar-field__counter" x-text="String(state['{{ field.name }}'] ?? '').length + ' / {{ field.maxLength }}'"></p>
232
+ @endif
233
+ @elseif(field.type === 'select')
234
+ @if(field.nativeSelect)
235
+ <select
236
+ id="field-{{ field.name }}"
237
+ name="{{ field.name }}{{ field.multiple ? '[]' : '' }}"
238
+ {{ field.multiple ? 'multiple' : '' }}
239
+ {{{ fieldInputAttrs(field) }}}
240
+ x-model="state['{{ field.name }}']"
241
+ @change="onFieldChange('{{ field.name }}')"
242
+ :disabled="isDisabled('{{ field.name }}')"
243
+ :required="isRequired('{{ field.name }}')"
244
+ class="shamar-select{{ formErrors && formErrors[field.name] ? ' is-invalid' : '' }}"
245
+ >
246
+ @if(!field.multiple && field.selectablePlaceholder !== false)
247
+ <option value="">{{ field.placeholder ?? 'Select...' }}</option>
248
+ @endif
249
+ @each(option in field.options)
250
+ <option value="{{ option.value }}">{{ option.label }}</option>
251
+ @endeach
252
+ </select>
253
+ @else
254
+ <div
255
+ class="shamar-combobox{{ field.multiple ? ' shamar-combobox--multiple' : '' }}{{ formErrors && formErrors[field.name] ? ' is-invalid' : '' }}"
256
+ :class="{ 'is-open': open, 'is-disabled': disabled }"
257
+ x-data="shamarCombobox({
258
+ name: {{ jsonAttr(field.name) }},
259
+ multiple: {{ field.multiple ? 'true' : 'false' }},
260
+ options: {{ jsonAttr(field.options ?? []) }},
261
+ placeholder: {{ jsonAttr(field.placeholder ?? 'Select...') }},
262
+ selectablePlaceholder: {{ field.selectablePlaceholder !== false ? 'true' : 'false' }},
263
+ getValue: () => state[{{ jsonAttr(field.name) }}],
264
+ setValue: (v) => { state[{{ jsonAttr(field.name) }}] = v; onFieldChange({{ jsonAttr(field.name) }}); },
265
+ isDisabled: () => isDisabled({{ jsonAttr(field.name) }}),
266
+ })"
267
+ @focusout="onFocusOut($event)"
268
+ >
269
+ <div
270
+ class="shamar-combobox__control"
271
+ @click="onControlClick()"
272
+ role="combobox"
273
+ :aria-expanded="open.toString()"
274
+ aria-haspopup="listbox"
275
+ :aria-disabled="disabled.toString()"
276
+ >
277
+ @if(field.multiple)
278
+ <div class="shamar-combobox__chips">
279
+ <template x-for="opt in selectedOptions" :key="opt.value">
280
+ <span class="shamar-combobox__chip">
281
+ <span class="shamar-combobox__chip-label" x-text="opt.label"></span>
282
+ <button
283
+ type="button"
284
+ class="shamar-combobox__chip-remove"
285
+ @click.stop="remove(opt.value)"
286
+ :disabled="disabled"
287
+ :aria-label="'Remove ' + opt.label"
288
+ >
289
+ <svg viewBox="0 0 20 20" fill="currentColor" width="12" height="12" aria-hidden="true">
290
+ <path d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" />
291
+ </svg>
292
+ </button>
293
+ </span>
294
+ </template>
295
+ <input
296
+ id="field-{{ field.name }}"
297
+ type="text"
298
+ class="shamar-combobox__search"
299
+ x-ref="search"
300
+ x-model="query"
301
+ @input="onInput()"
302
+ @keydown="onKeydown($event)"
303
+ @focus="openDropdown()"
304
+ :disabled="disabled"
305
+ :placeholder="selectedOptions.length ? '' : placeholder"
306
+ autocomplete="off"
307
+ role="searchbox"
308
+ aria-autocomplete="list"
309
+ />
310
+ </div>
311
+ @else
312
+ <input
313
+ id="field-{{ field.name }}"
314
+ type="text"
315
+ class="shamar-combobox__search shamar-combobox__search--single"
316
+ x-ref="search"
317
+ x-model="query"
318
+ @input="onInput()"
319
+ @keydown="onKeydown($event)"
320
+ @focus="openDropdown()"
321
+ :disabled="disabled"
322
+ :placeholder="placeholder"
323
+ autocomplete="off"
324
+ role="searchbox"
325
+ aria-autocomplete="list"
326
+ />
327
+ <button
328
+ type="button"
329
+ class="shamar-combobox__clear"
330
+ x-show="selectedValues.length && !disabled"
331
+ x-cloak
332
+ @click.stop="clearSelection()"
333
+ aria-label="Clear selection"
334
+ >
335
+ <svg viewBox="0 0 20 20" fill="currentColor" width="14" height="14" aria-hidden="true">
336
+ <path d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" />
337
+ </svg>
338
+ </button>
339
+ @endif
340
+ <button
341
+ type="button"
342
+ class="shamar-combobox__toggle"
343
+ @click.stop="toggleOpen()"
344
+ :disabled="disabled"
345
+ tabindex="-1"
346
+ aria-label="Toggle options"
347
+ >
348
+ <svg viewBox="0 0 20 20" fill="currentColor" width="16" height="16" aria-hidden="true">
349
+ <path fill-rule="evenodd" d="M5.22 8.22a.75.75 0 011.06 0L10 11.94l3.72-3.72a.75.75 0 111.06 1.06l-4.25 4.25a.75.75 0 01-1.06 0L5.22 9.28a.75.75 0 010-1.06z" clip-rule="evenodd" />
350
+ </svg>
351
+ </button>
352
+ </div>
353
+
354
+ <div
355
+ class="shamar-combobox__dropdown"
356
+ x-ref="list"
357
+ x-show="open"
358
+ x-cloak
359
+ role="listbox"
360
+ :aria-multiselectable="multiple.toString()"
361
+ >
362
+ <template x-for="(opt, index) in filteredOptions" :key="opt.value">
363
+ <button
364
+ type="button"
365
+ class="shamar-combobox__option"
366
+ :class="{ 'is-active': cursor === index, 'is-selected': isSelected(opt) }"
367
+ :data-combobox-index="index"
368
+ role="option"
369
+ :aria-selected="isSelected(opt).toString()"
370
+ @click.prevent="pick(opt)"
371
+ @mouseenter="cursor = index"
372
+ >
373
+ <span class="shamar-combobox__option-label" x-text="opt.label"></span>
374
+ <svg
375
+ class="shamar-combobox__option-check"
376
+ x-show="isSelected(opt)"
377
+ viewBox="0 0 20 20"
378
+ fill="currentColor"
379
+ width="14"
380
+ height="14"
381
+ aria-hidden="true"
382
+ >
383
+ <path fill-rule="evenodd" d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z" clip-rule="evenodd" />
384
+ </svg>
385
+ </button>
386
+ </template>
387
+ <div class="shamar-combobox__empty" x-show="filteredOptions.length === 0">
388
+ No options found
389
+ </div>
390
+ </div>
391
+
392
+ @if(field.multiple)
393
+ <template x-for="value in selectedValues" :key="'h-' + value">
394
+ <input type="hidden" name="{{ field.name }}[]" :value="value" />
395
+ </template>
396
+ @else
397
+ <input type="hidden" name="{{ field.name }}" :value="selectedValues[0] || ''" />
398
+ @endif
399
+ </div>
400
+ @endif
401
+ @elseif(field.type === 'file' || field.type === 'image')
402
+ <input
403
+ id="field-{{ field.name }}"
404
+ type="file"
405
+ name="{{ field.name }}{{ field.multiple ? '[]' : '' }}"
406
+ {{ field.required ? 'required' : '' }}
407
+ {{{ fieldInputAttrs(field) }}}
408
+ @change="state['{{ field.name }}'] = $event.target.files && $event.target.files.length ? ($event.target.files.length > 1 ? Array.from($event.target.files).map(f => f.name) : $event.target.files[0].name) : null; onFieldChange('{{ field.name }}')"
409
+ :disabled="isDisabled('{{ field.name }}')"
410
+ class="shamar-input shamar-input--file{{ formErrors && formErrors[field.name] ? ' is-invalid' : '' }}"
411
+ />
412
+ @else
413
+ <div class="shamar-input-affixes{{ (showStackedPrefix || showStackedSuffix) ? ' shamar-input-affixes--stacked' : '' }}">
414
+ @if(showStackedPrefix)
415
+ @if(field.prefixIcon)
416
+ @let(icon = field.prefixIcon)
417
+ <span class="shamar-input-affixes__outside shamar-input-affixes__outside--icon" aria-hidden="true" data-icon="{{ field.prefixIcon }}">
418
+ @include('shamar::partials/field-affix-icon')
419
+ </span>
420
+ @else
421
+ <span class="shamar-input-affixes__outside">{{ field.prefix }}</span>
422
+ @endif
423
+ @endif
424
+
425
+ @if(useGroup)
426
+ <div
427
+ class="shamar-input-group{{ showInlinePrefix ? ' has-prefix' : '' }}{{ showInlineSuffix ? ' has-suffix' : '' }}"
428
+ @if(field.revealable && field.type === 'password')
429
+ x-data="{ revealed: false }"
430
+ @endif
431
+ >
432
+ @if(showInlinePrefix)
433
+ @if(field.prefixIcon)
434
+ @let(icon = field.prefixIcon)
435
+ <span class="shamar-input-group__affix shamar-input-group__affix--icon" aria-hidden="true" data-icon="{{ field.prefixIcon }}">
436
+ @include('shamar::partials/field-affix-icon')
437
+ </span>
438
+ @elseif(field.prefix)
439
+ <span class="shamar-input-group__affix">{{ field.prefix }}</span>
440
+ @endif
441
+ @endif
442
+
443
+ @if(field.currency)
444
+ <input
445
+ id="field-{{ field.name }}"
446
+ type="text"
447
+ inputmode="decimal"
448
+ name="{{ field.name }}"
449
+ {{ field.required ? 'required' : '' }}
450
+ {{ field.readonly ? 'readonly' : '' }}
451
+ {{{ fieldInputAttrs(field) }}}
452
+ :value="currencyDisplay('{{ field.name }}')"
453
+ @focus="beginCurrencyEdit('{{ field.name }}')"
454
+ @input="onCurrencyInput('{{ field.name }}', $event)"
455
+ @change="onFieldChange('{{ field.name }}')"
456
+ @blur="endCurrencyEdit('{{ field.name }}'); onFieldBlur('{{ field.name }}')"
457
+ :disabled="isDisabled('{{ field.name }}')"
458
+ :placeholder='fieldMeta[{{ jsonAttr(field.name) }}]?.placeholder || {{ jsonAttr(field.placeholder ?? "") }}'
459
+ class="shamar-input{{ formErrors && formErrors[field.name] ? ' is-invalid' : '' }}"
460
+ />
461
+ @else
462
+ <input
463
+ id="field-{{ field.name }}"
464
+ @if(field.revealable && field.type === 'password')
465
+ :type="revealed ? 'text' : 'password'"
466
+ @else
467
+ type="{{ formInputType(field) }}"
468
+ @endif
469
+ name="{{ field.name }}"
470
+ {{ field.required ? 'required' : '' }}
471
+ {{ field.readonly ? 'readonly' : '' }}
472
+ {{{ fieldInputAttrs(field) }}}
473
+ x-model="state['{{ field.name }}']"
474
+ @input="onFieldInput('{{ field.name }}')"
475
+ @change="onFieldChange('{{ field.name }}')"
476
+ @blur="onFieldBlur('{{ field.name }}')"
477
+ :disabled="isDisabled('{{ field.name }}')"
478
+ :placeholder='fieldMeta[{{ jsonAttr(field.name) }}]?.placeholder || {{ jsonAttr(field.placeholder ?? "") }}'
479
+ class="shamar-input{{ formErrors && formErrors[field.name] ? ' is-invalid' : '' }}"
480
+ />
481
+ @endif
482
+
483
+ @if(field.revealable && field.type === 'password')
484
+ <button
485
+ type="button"
486
+ class="shamar-input-group__affix shamar-input-group__affix--action"
487
+ @click="revealed = !revealed"
488
+ :aria-label="revealed ? 'Hide password' : 'Show password'"
489
+ :aria-pressed="revealed.toString()"
490
+ >
491
+ <svg x-show="!revealed" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" width="16" height="16" aria-hidden="true">
492
+ <path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z" />
493
+ <path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
494
+ </svg>
495
+ <svg x-show="revealed" x-cloak viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" width="16" height="16" aria-hidden="true">
496
+ <path stroke-linecap="round" stroke-linejoin="round" d="M3.98 8.223A10.477 10.477 0 001.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.45 10.45 0 0112 4.5c4.756 0 8.773 3.162 10.065 7.498a10.523 10.523 0 01-4.293 5.774M6.228 6.228L3 3m3.228 3.228L3 3m3.228 3.228l3.65 3.65m7.894 7.894L21 21m-3.228-3.228l-3.65-3.65m0 0a3 3 0 10-4.243-4.243m4.242 4.242L9.88 9.88" />
497
+ </svg>
498
+ </button>
499
+ @elseif(field.copyable)
500
+ <button
501
+ type="button"
502
+ class="shamar-input-group__affix shamar-input-group__affix--action"
503
+ @click="navigator.clipboard.writeText(String(state['{{ field.name }}'] ?? ''))"
504
+ aria-label="Copy"
505
+ >
506
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" width="16" height="16" aria-hidden="true">
507
+ <path stroke-linecap="round" stroke-linejoin="round" d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9.75a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184" />
508
+ </svg>
509
+ </button>
510
+ @elseif(showInlineSuffix && field.suffixIcon)
511
+ @let(icon = field.suffixIcon)
512
+ <span class="shamar-input-group__affix shamar-input-group__affix--icon" aria-hidden="true" data-icon="{{ field.suffixIcon }}">
513
+ @include('shamar::partials/field-affix-icon')
514
+ </span>
515
+ @elseif(showInlineSuffix && field.suffix)
516
+ <span class="shamar-input-group__affix">{{ field.suffix }}</span>
517
+ @endif
518
+ </div>
519
+ @elseif(field.currency)
520
+ <input
521
+ id="field-{{ field.name }}"
522
+ type="text"
523
+ inputmode="decimal"
524
+ name="{{ field.name }}"
525
+ {{ field.required ? 'required' : '' }}
526
+ {{ field.readonly ? 'readonly' : '' }}
527
+ {{{ fieldInputAttrs(field) }}}
528
+ :value="currencyDisplay('{{ field.name }}')"
529
+ @focus="beginCurrencyEdit('{{ field.name }}')"
530
+ @input="onCurrencyInput('{{ field.name }}', $event)"
531
+ @change="onFieldChange('{{ field.name }}')"
532
+ @blur="endCurrencyEdit('{{ field.name }}'); onFieldBlur('{{ field.name }}')"
533
+ :disabled="isDisabled('{{ field.name }}')"
534
+ :placeholder='fieldMeta[{{ jsonAttr(field.name) }}]?.placeholder || {{ jsonAttr(field.placeholder ?? "") }}'
535
+ class="shamar-input{{ formErrors && formErrors[field.name] ? ' is-invalid' : '' }}"
536
+ />
537
+ @else
538
+ <input
539
+ id="field-{{ field.name }}"
540
+ type="{{ formInputType(field) }}"
541
+ name="{{ field.name }}"
542
+ {{ field.required ? 'required' : '' }}
543
+ {{ field.readonly ? 'readonly' : '' }}
544
+ {{{ fieldInputAttrs(field) }}}
545
+ x-model="state['{{ field.name }}']"
546
+ @input="onFieldInput('{{ field.name }}')"
547
+ @change="onFieldChange('{{ field.name }}')"
548
+ @blur="onFieldBlur('{{ field.name }}')"
549
+ :disabled="isDisabled('{{ field.name }}')"
550
+ :placeholder='fieldMeta[{{ jsonAttr(field.name) }}]?.placeholder || {{ jsonAttr(field.placeholder ?? "") }}'
551
+ class="shamar-input{{ formErrors && formErrors[field.name] ? ' is-invalid' : '' }}"
552
+ />
553
+ @endif
554
+
555
+ @if(showStackedSuffix)
556
+ @if(field.suffixIcon)
557
+ @let(icon = field.suffixIcon)
558
+ <span class="shamar-input-affixes__outside shamar-input-affixes__outside--icon" aria-hidden="true" data-icon="{{ field.suffixIcon }}">
559
+ @include('shamar::partials/field-affix-icon')
560
+ </span>
561
+ @else
562
+ <span class="shamar-input-affixes__outside">{{ field.suffix }}</span>
563
+ @endif
564
+ @endif
565
+ </div>
566
+ @if(field.datalist && field.datalist.length)
567
+ <datalist id="datalist-{{ field.name }}">
568
+ @each(option in field.datalist)
569
+ <option value="{{ option }}"></option>
570
+ @endeach
571
+ </datalist>
572
+ @endif
573
+ @if(field.maxLength)
574
+ <p class="shamar-field__counter" x-text="String(state['{{ field.name }}'] ?? '').length + ' / {{ field.maxLength }}'"></p>
575
+ @endif
576
+ @endif
577
+
578
+ @if(field.help)
579
+ <p class="shamar-field__help" x-text='fieldMeta[{{ jsonAttr(field.name) }}]?.help || {{ jsonAttr(field.help) }}'>{{ field.help }}</p>
580
+ @endif
581
+ @if(formErrors && formErrors[field.name])
582
+ <p class="shamar-field__error" role="alert">{{ formErrors[field.name] }}</p>
583
+ @endif
584
+ </div>
585
+ @endif