picocrank 1.22.0 → 1.22.2

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "picocrank",
3
- "version": "1.22.0",
3
+ "version": "1.22.2",
4
4
  "description": "A set of zero dependency Vue components built on the Femtocrank global CSS theme.",
5
5
  "main": "vite.config.js",
6
6
  "scripts": {
@@ -21,14 +21,14 @@
21
21
  "@hugeicons/core-free-icons": "^4.2.3",
22
22
  "@hugeicons/vue": "^1.0.7",
23
23
  "@vitejs/plugin-vue": "^6.0.8",
24
- "femtocrank": "^2.5.1",
24
+ "femtocrank": "^2.5.2",
25
25
  "unplugin-vue-components": "^32.1.0",
26
- "vite": "^8.2.0",
27
- "vue": "^3.5.40",
26
+ "vite": "^8.2.1",
27
+ "vue": "^3.5.41",
28
28
  "vue-router": "^5.2.0"
29
29
  },
30
30
  "devDependencies": {
31
- "semantic-release": "^25.0.8"
31
+ "semantic-release": "^25.0.9"
32
32
  },
33
33
  "homepage": "https://github.com/jamesread/picocrank#readme"
34
34
  }
package/router.js CHANGED
@@ -14,6 +14,7 @@ import { LayoutGridIcon } from '@hugeicons/core-free-icons';
14
14
  import { StatusIcon } from '@hugeicons/core-free-icons';
15
15
  import { PaintBoardIcon } from '@hugeicons/core-free-icons';
16
16
  import { UserIcon } from '@hugeicons/core-free-icons';
17
+ import { InformationCircleIcon } from '@hugeicons/core-free-icons';
17
18
 
18
19
  const routes = [
19
20
  {
@@ -136,6 +137,15 @@ const routes = [
136
137
  icon: StatusIcon,
137
138
  }
138
139
  },
140
+ {
141
+ name: 'DialogExample',
142
+ path: '/dialog-example',
143
+ component: () => import('./vue/views/DialogExample.vue'),
144
+ meta: {
145
+ title: 'Dialog',
146
+ icon: InformationCircleIcon,
147
+ }
148
+ },
139
149
  {
140
150
  name: 'TabsExample',
141
151
  path: '/tabs-example',
@@ -362,11 +362,35 @@ function selectItem(item) {
362
362
  close()
363
363
  }
364
364
 
365
+ function escapeHtml(value) {
366
+ return String(value)
367
+ .replace(/&/g, '&')
368
+ .replace(/</g, '&lt;')
369
+ .replace(/>/g, '&gt;')
370
+ .replace(/"/g, '&quot;')
371
+ .replace(/'/g, '&#39;')
372
+ }
373
+
365
374
  function highlightText(text, query) {
366
- if (!query || !text) return text
375
+ if (text == null || text === '') return ''
376
+
377
+ const source = String(text)
378
+ if (!query) return escapeHtml(source)
379
+
380
+ const regex = new RegExp(query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi')
381
+ let result = ''
382
+ let lastIndex = 0
383
+
384
+ for (const match of source.matchAll(regex)) {
385
+ const start = match.index
386
+ const matched = match[0]
387
+ result += escapeHtml(source.slice(lastIndex, start))
388
+ result += `<mark>${escapeHtml(matched)}</mark>`
389
+ lastIndex = start + matched.length
390
+ }
367
391
 
368
- const regex = new RegExp(`(${query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi')
369
- return text.replace(regex, '<mark>$1</mark>')
392
+ result += escapeHtml(source.slice(lastIndex))
393
+ return result
370
394
  }
371
395
 
372
396
  function addItem(item) {
package/vue/views/App.vue CHANGED
@@ -198,6 +198,7 @@
198
198
 
199
199
  navigation.value.addSection('Feedback & status', { name: 'nav-feedback' })
200
200
  navigation.value.addRouterLink('StatusExample', 'Status & notifications')
201
+ navigation.value.addRouterLink('DialogExample', 'Dialog')
201
202
 
202
203
  navigation.value.addSection('Patterns', { name: 'nav-patterns' })
203
204
  navigation.value.addCallback('Callback example', helloWorld, { icon: Pin02Icon })
@@ -0,0 +1,123 @@
1
+ <template>
2
+ <Section
3
+ title="Dialog"
4
+ subtitle="Native HTML dialog element with showModal()"
5
+ >
6
+ <p>
7
+ The <code>&lt;dialog&gt;</code> element provides accessible modal and
8
+ non-modal overlays without a third-party library. Femtocrank styles the
9
+ element globally; use <code>showModal()</code> for a modal with backdrop,
10
+ or <code>show()</code> for a non-modal panel.
11
+ </p>
12
+ </Section>
13
+
14
+ <Section title="Modal dialog" subtitle="Opens with showModal() and closes on backdrop click">
15
+ <div role="toolbar" class="dialog-actions">
16
+ <button type="button" @click="openModal">Open modal</button>
17
+ </div>
18
+
19
+ <dialog
20
+ ref="modalRef"
21
+ class="example-dialog"
22
+ @close="onModalClose"
23
+ @click="onBackdropClick($event, modalRef)"
24
+ >
25
+ <div class="dialog-panel" @click.stop>
26
+ <h3>Confirm action</h3>
27
+ <p>
28
+ This modal uses the native dialog API. Press Escape or click outside
29
+ to dismiss, or choose an action below.
30
+ </p>
31
+ <form method="dialog" class="dialog-form">
32
+ <div role="toolbar" class="dialog-actions">
33
+ <button type="submit" value="cancel" class="neutral">Cancel</button>
34
+ <button type="submit" value="confirm" class="good">Confirm</button>
35
+ </div>
36
+ </form>
37
+ </div>
38
+ </dialog>
39
+
40
+ <p v-if="modalResult" class="subtle">{{ modalResult }}</p>
41
+ </Section>
42
+
43
+ <Section title="Non-modal dialog" subtitle="Stays open while the page remains interactive">
44
+ <div role="toolbar" class="dialog-actions">
45
+ <button type="button" class="neutral" @click="openPanel">Open panel</button>
46
+ <button type="button" class="bad" :disabled="!panelRef?.open" @click="closePanel">
47
+ Close panel
48
+ </button>
49
+ </div>
50
+
51
+ <dialog ref="panelRef" class="example-dialog example-dialog-panel">
52
+ <div class="dialog-panel">
53
+ <h3>Side note</h3>
54
+ <p>
55
+ Non-modal dialogs use <code>show()</code> instead of
56
+ <code>showModal()</code>. They do not trap focus or dim the page.
57
+ </p>
58
+ <button type="button" class="neutral" @click="closePanel">Dismiss</button>
59
+ </div>
60
+ </dialog>
61
+ </Section>
62
+ </template>
63
+
64
+ <script setup>
65
+ import { ref } from 'vue'
66
+ import Section from '../components/Section.vue'
67
+
68
+ const modalRef = ref(null)
69
+ const panelRef = ref(null)
70
+ const modalResult = ref('')
71
+
72
+ function openModal() {
73
+ modalRef.value?.showModal()
74
+ }
75
+
76
+ function onModalClose(event) {
77
+ const value = event.target?.returnValue
78
+ if (value === 'confirm') {
79
+ modalResult.value = 'Action confirmed.'
80
+ } else {
81
+ modalResult.value = 'Dialog closed without confirming.'
82
+ }
83
+ }
84
+
85
+ function onBackdropClick(event, dialog) {
86
+ if (event.target === dialog) {
87
+ dialog.close('cancel')
88
+ }
89
+ }
90
+
91
+ function openPanel() {
92
+ panelRef.value?.show()
93
+ }
94
+
95
+ function closePanel() {
96
+ panelRef.value?.close()
97
+ }
98
+ </script>
99
+
100
+ <style scoped>
101
+ .dialog-actions {
102
+ display: flex;
103
+ flex-wrap: wrap;
104
+ gap: 0.5rem;
105
+ }
106
+
107
+ .dialog-panel h3 {
108
+ margin: 0 0 0.5rem;
109
+ }
110
+
111
+ .dialog-panel p {
112
+ margin: 0 0 1rem;
113
+ }
114
+
115
+ .dialog-form {
116
+ margin: 0;
117
+ }
118
+
119
+ .example-dialog-panel {
120
+ margin-block-start: 1rem;
121
+ max-width: 24rem;
122
+ }
123
+ </style>